How to change width in matplotlib barh plot - python

I'll have only one record in my 1st List and count of that record in my 2nd List. I need to put them in a horizontal bar graph.
Value of List 1 in Y axis and value of List 2 as a label of the bar.
The below code is giving me the bar whose width I can't change. How I can change the width? It's looking awkward with this large width.
If not in matplotlib, can i use some other python library to achieve this?
import os
import cx_Oracle
import matplotlib.pyplot as plt
r_c = [Sales_report_2020]
a_i = [1311]
# Figure Size
fig, ax = plt.subplots(figsize =(12, 8))
#replace the None with integer 0
b = [0 if x is None else x for x in a_i]
print(b)
# Horizontal Bar Plot
plt.barh(r_c,b,height=0.5)
plt.ylim(-0.5, len(b) - 0.5)
ax.tick_params(width=0.5,length=6)
# Add annotation to bars
for i in ax.patches:
plt.text(i.get_width()+0.2, i.get_y()+0.27,
"{:1.0f}".format(i.get_width()),
fontsize = 10, fontweight ='bold',
color ='grey') #str(round((i.get_width()), 2))
# Add Plot Title
ax.set_title('DTP!',
loc ='center', )
# Add Text watermark
fig.text(0.9, 0.15, 'STPREPORT', fontsize = 12,
color ='grey', ha ='right', va ='bottom',
alpha = 0.7)
path = r"\\ssd.COM\View\Folder_Redirect\id\Documents\Conda_Envs"
os.chdir(path)
plt.savefig(path + '\BAR.png')
# Show Plot
plt.show()
conn.close()

The width and height parameters in barh can help you a little bit, but since there is only one record, matplotlib is probably adjusting the plot automatically to make it look bigger than you need. Adjusting your ylim should help you. I can't execute your code because I don't have the data, but the following illustrates what I mean:
For example,
plt.barh(1311, width=0.01, height=0.01)
gives you
while
plt.barh(1311, width=20, height=20)
gives you
But, if you set a better ylim, you can get a better-looking horizontal bar.
plt.barh(1311, width=0.5, height=20)
plt.ylim([1200, 1400])
Of course, you would edit these values to get your desired result.

Related

Limiting the size of legend in MatPlotLib in python, then allowing scrolling within the legend

I would like to limit the size of the legend in MatPlotLib to scale with a figure. After this, I would like to enable scrolling within the legend to see any cut off data. This is regarding two legends, each corresponding to a subplot. A picture is attached to show the exact setup:
In this image, you can see that the legends are overlapping each other, as well as being cut off by the bottom of the frame.
Here is the python code used to obtain the figure:
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
fig, axes = plt.subplots(2, figsize=(9,6))
x = [i for i in range(100)]
y_data = []
for i in range(1,15):
temp = []
for j in x:
temp.append(i * j)
y_data.append(temp)
for line in y_data:
axes[0].plot(x, line, '.')
axes[1].plot(x, line, '.')
axes[0].legend(x, bbox_to_anchor=(1.02, 0, 0.07, 1))
axes[1].legend(x, bbox_to_anchor=(1.02, 0, 0.07, 1))
plt.show()
I would like to modify this code so that the legend is smaller, and so that if there are a great amount of lines in the legend, the data that is not within the confines of the legend can be scrolled to.
I attempted to use the ideas here: Fix size of legend in matplotlib, but it did not seem to scale with two subplots. Setting the height and width in bbox_to_anchor also did not seem to constrain the legend - only move it.
How can I accomplish this?

matplotlib, formating space between bars/x labels [duplicate]

How do I increase the space between each bar with matplotlib barcharts, as they keep cramming them self to the centre. (this is what it currently looks)
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
def ww(self):#wrongwords text file
with open("wrongWords.txt") as file:
array1 = []
array2 = []
for element in file:
array1.append(element)
x=array1[0]
s = x.replace(')(', '),(') #removes the quote marks from csv file
print(s)
my_list = ast.literal_eval(s)
print(my_list)
my_dict = {}
for item in my_list:
my_dict[item[2]] = my_dict.get(item[2], 0) + 1
plt.bar(range(len(my_dict)), my_dict.values(), align='center')
plt.xticks(range(len(my_dict)), my_dict.keys())
plt.show()
Try replace
plt.bar(range(len(my_dict)), my_dict.values(), align='center')
with
plt.figure(figsize=(20, 3)) # width:20, height:3
plt.bar(range(len(my_dict)), my_dict.values(), align='edge', width=0.3)
The option align='edge' will eliminate white space on the left of the bar chart.
and width=0.3 set the bars' width smaller size than the default value.
For the labels along x-axis, they should be rotated 90 degrees to make them readable.
plt.xticks(range(len(my_dict)), my_dict.keys(), rotation='vertical')
There are 2 ways to increase the space between the bars
For reference here is the plot functions
plt.bar(x, height, width=0.8, bottom=None, *, align='center', data=None, **kwargs)
Decrease the width of the bar
The plot function has a width parameter that controls the width of the bar. If you decrease the width the space between the bars will automatically reduce. Width for you is set to 0.8 by default.
width = 0.5
Scale the x-axis so the bars are placed further apart from each other
If you want to keep the width constant you will have to space out where the bars are placed on x-axis. You can use any scaling parameter. For example
x = (range(len(my_dict)))
new_x = [2*i for i in x]
# you might have to increase the size of the figure
plt.figure(figsize=(20, 3)) # width:10, height:8
plt.bar(new_x, my_dict.values(), align='center', width=0.8)
This answer changes the space between bars and it also rotate the labels on the x-axis. It also lets you change the figure size.
fig, ax = plt.subplots(figsize=(20,20))
# The first parameter would be the x value,
# by editing the delta between the x-values
# you change the space between bars
plt.bar([i*2 for i in range(100)], y_values)
# The first parameter is the same as above,
# but the second parameter are the actual
# texts you wanna display
plt.xticks([i*2 for i in range(100)], labels)
for tick in ax.get_xticklabels():
tick.set_rotation(90)
set your x axis limits starting from slightly negative value to slightly larger value than the number of bars in your plot and change the width of the bars in the barplot command
for example I did this for a barplot with just two bars
ax1.axes.set_xlim(-0.5,1.5)

Make x-axes of all subplots same length on the page

I am new to matplotlib and trying to create and save plots from pandas dataframes via a loop. Each plot should have an identical x-axis, but different y-axis lengths and labels. I have no problem creating and saving the plots with different y-axis lengths and labels, but when I create the plots, matplotlib rescales the x-axis depending on how much space is needed for the y-axis labels on the left side of the figure.
These figures are for a technical report. I plan to place one on each page of the report and I would like to have all of the x-axes take up the same amount of space on the page.
Here is an MSPaint version of what I'm getting and what I'd like to get.
Hopefully this is enough code to help. I'm sure there are lots of non-optimal parts of this.
import pandas as pd
import matplotlib.pyplot as plt
import pylab as pl
from matplotlib import collections as mc
from matplotlib.lines import Line2D
import seaborn as sns
# elements for x-axis
start = -1600
end = 2001
interval = 200 # x-axis tick interval
xticks = [x for x in range(start, end, interval)] # create x ticks
# items needed for legend construction
lw_bins = [0,10,25,50,75,90,100] # bins for line width
lw_labels = [3,6,9,12,15,18] # line widths
def make_proxy(zvalue, scalar_mappable, **kwargs):
color = 'black'
return Line2D([0, 1], [0, 1], color=color, solid_capstyle='butt', **kwargs)
# generic image ID
img_path = r'C:\\Users\\user\\chart'
img_ID = 0
for line_subset in data:
# create line collection for this run through loop
lc = mc.LineCollection(line_subset)
# create plot and set properties
sns.set(style="ticks")
sns.set_context("notebook")
fig, ax = pl.subplots(figsize=(16, len(line_subset)*0.5)) # I want the height of the figure to change based on number of labels on y-axis
# Figure width should stay the same
ax.add_collection(lc)
ax.set_xlim(left=start, right=end)
ax.set_xticks(xticks)
ax.set_ylim(0, len(line_subset)+1)
ax.margins(0.05)
sns.despine(left=True)
ax.xaxis.set_ticks_position('bottom')
ax.set_yticks(line_subset['order'])
ax.set_yticklabels(line_subset['ylabel'])
ax.tick_params(axis='y', length=0)
# legend
proxies = [make_proxy(item, lc, linewidth=item) for item in lw_labels]
ax.legend(proxies, ['0-10%', '10-25%', '25-50%', '50-75%', '75-90%', '90-100%'], bbox_to_anchor=(1.05, 1.0),
loc=2, ncol=2, labelspacing=1.25, handlelength=4.0, handletextpad=0.5, markerfirst=False,
columnspacing=1.0)
# title
ax.text(0, len(line_subset)+2, s=str(img_ID), fontsize=20)
# save as .png images
plt.savefig(r'C:\\Users\\user\\Desktop\\chart' + str(img_ID) + '.png', dpi=300, bbox_inches='tight')
Unless you use an axes of specifically defined aspect ratio (like in an imshow plot or by calling .set_aspect("equal")), the space taken by the axes should only depend on the figure size along that direction and the spacings set to the figure.
You are therefore pretty much asking for the default behaviour and the only thing that prevents you from obtaining that is that you use bbox_inches='tight' in the savefig command.
bbox_inches='tight' will change the figure size! So don't use it and the axes will remain constant in size. `
Your figure size, defined like figsize=(16, len(line_subset)*0.5) seems to make sense according to what I understand from the question. So what remains is to make sure the axes inside the figure are the size you want them to be. You can do that by manually placing it using fig.add_axes
fig.add_axes([left, bottom, width, height])
where left, bottom, width, height are in figure coordinates ranging from 0 to 1. Or, you can adjust the spacings outside the subplot using subplots_adjust
plt.subplots_adjust(left, bottom, right, top)
To get matching x axis for the subplots (same x axis length for each subplot) , you need to share the x axis between subplots.
See the example here https://matplotlib.org/examples/pylab_examples/shared_axis_demo.html

matplotlib bar chart: space out bars

How do I increase the space between each bar with matplotlib barcharts, as they keep cramming them self to the centre. (this is what it currently looks)
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
def ww(self):#wrongwords text file
with open("wrongWords.txt") as file:
array1 = []
array2 = []
for element in file:
array1.append(element)
x=array1[0]
s = x.replace(')(', '),(') #removes the quote marks from csv file
print(s)
my_list = ast.literal_eval(s)
print(my_list)
my_dict = {}
for item in my_list:
my_dict[item[2]] = my_dict.get(item[2], 0) + 1
plt.bar(range(len(my_dict)), my_dict.values(), align='center')
plt.xticks(range(len(my_dict)), my_dict.keys())
plt.show()
Try replace
plt.bar(range(len(my_dict)), my_dict.values(), align='center')
with
plt.figure(figsize=(20, 3)) # width:20, height:3
plt.bar(range(len(my_dict)), my_dict.values(), align='edge', width=0.3)
The option align='edge' will eliminate white space on the left of the bar chart.
and width=0.3 set the bars' width smaller size than the default value.
For the labels along x-axis, they should be rotated 90 degrees to make them readable.
plt.xticks(range(len(my_dict)), my_dict.keys(), rotation='vertical')
There are 2 ways to increase the space between the bars
For reference here is the plot functions
plt.bar(x, height, width=0.8, bottom=None, *, align='center', data=None, **kwargs)
Decrease the width of the bar
The plot function has a width parameter that controls the width of the bar. If you decrease the width the space between the bars will automatically reduce. Width for you is set to 0.8 by default.
width = 0.5
Scale the x-axis so the bars are placed further apart from each other
If you want to keep the width constant you will have to space out where the bars are placed on x-axis. You can use any scaling parameter. For example
x = (range(len(my_dict)))
new_x = [2*i for i in x]
# you might have to increase the size of the figure
plt.figure(figsize=(20, 3)) # width:10, height:8
plt.bar(new_x, my_dict.values(), align='center', width=0.8)
This answer changes the space between bars and it also rotate the labels on the x-axis. It also lets you change the figure size.
fig, ax = plt.subplots(figsize=(20,20))
# The first parameter would be the x value,
# by editing the delta between the x-values
# you change the space between bars
plt.bar([i*2 for i in range(100)], y_values)
# The first parameter is the same as above,
# but the second parameter are the actual
# texts you wanna display
plt.xticks([i*2 for i in range(100)], labels)
for tick in ax.get_xticklabels():
tick.set_rotation(90)
set your x axis limits starting from slightly negative value to slightly larger value than the number of bars in your plot and change the width of the bars in the barplot command
for example I did this for a barplot with just two bars
ax1.axes.set_xlim(-0.5,1.5)

Matplotlib: Adjust legend location/position

I'm creating a figure with multiple subplots. One of these subplots is giving me some trouble, as none of the axes corners or centers are free (or can be freed up) for placing the legend. What I'd like to do is to have the legend placed somewhere in between the 'upper left' and 'center left' locations, while keeping the padding between it and the y-axis equal to the legends in the other subplots (that are placed using one of the predefined legend location keywords).
I know I can specify a custom position by using loc=(x,y), but then I can't figure out how to get the padding between the legend and the y-axis to be equal to that used by the other legends. Would it be possible to somehow use the borderaxespad property of the first legend? Though I'm not succeeding at getting that to work.
Any suggestions would be most welcome!
Edit: Here is a (very simplified) illustration of the problem:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, sharex=False, sharey=False)
ax[0].axhline(y=1, label='one')
ax[0].axhline(y=2, label='two')
ax[0].set_ylim([0.8,3.2])
ax[0].legend(loc=2)
ax[1].axhline(y=1, label='one')
ax[1].axhline(y=2, label='two')
ax[1].axhline(y=3, label='three')
ax[1].set_ylim([0.8,3.2])
ax[1].legend(loc=2)
plt.show()
What I'd like is that the legend in the right plot is moved down somewhat so it no longer overlaps with the line.
As a last resort I could change the axis limits, but I would very much like to avoid that.
I saw the answer you posted and tried it out. The problem however is that it is also depended on the figure size.
Here's a new try:
import numpy
import matplotlib.pyplot as plt
x = numpy.linspace(0, 10, 10000)
y = numpy.cos(x) + 2.
x_value = .014 #Offset by eye
y_value = .55
fig, ax = plt.subplots(1, 2, sharex = False, sharey = False)
fig.set_size_inches(50,30)
ax[0].plot(x, y, label = "cos")
ax[0].set_ylim([0.8,3.2])
ax[0].legend(loc=2)
line1 ,= ax[1].plot(x,y)
ax[1].set_ylim([0.8,3.2])
axbox = ax[1].get_position()
fig.legend([line1], ["cos"], loc = (axbox.x0 + x_value, axbox.y0 + y_value))
plt.show()
So what I am now doing is basically getting the coordinates from the subplot. I then create the legend based on the dimensions of the entire figure. Hence, the figure size does not change anything to the legend positioning anymore.
With the values for x_value and y_value the legend can be positioned in the subplot. x_value has been eyeballed for a good correspondence with the "normal" legend. This value can be changed at your desire. y_value determines the height of the legend.
Good luck!
After spending way too much time on this, I've come up with the following satisfactory solution (the Transformations Tutorial definitely helped):
bapad = plt.rcParams['legend.borderaxespad']
fontsize = plt.rcParams['font.size']
axline = plt.rcParams['axes.linewidth'] #need this, otherwise the result will be off by a few pixels
pad_points = bapad*fontsize + axline #padding is defined in relative to font size
pad_inches = pad_points/72.0 #convert from points to inches
pad_pixels = pad_inches*fig.dpi #convert from inches to pixels using the figure's dpi
Then, I found that both of the following work and give the same value for the padding:
# Define inverse transform, transforms display coordinates (pixels) to axes coordinates
inv = ax[1].transAxes.inverted()
# Inverse transform two points on the display and find the relative distance
pad_axes = inv.transform((pad_pixels, 0)) - inv.transform((0,0))
pad_xaxis = pad_axes[0]
or
# Find how may pixels there are on the x-axis
x_pixels = ax[1].transAxes.transform((1,0)) - ax[1].transAxes.transform((0,0))
# Compute the ratio between the pixel offset and the total amount of pixels
pad_xaxis = pad_pixels/x_pixels[0]
And then set the legend with:
ax[1].legend(loc=(pad_xaxis,0.6))
Plot:

Categories

Resources