matplotlib bar chart: space out bars - python

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)

Related

How to change width in matplotlib barh plot

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.

Matplotlib - How to draw a line from the top of any bar to the y - axis?

I created a cumulative histogram. Now I want to draw a line from top of any bin to the y-axis in that histogram and show the value of it like this:
Can you show me the way to do?
Below is my code to draw that histogram:
plt.rcParams['ytick.right'] = plt.rcParams['ytick.labelright'] = True
plt.rcParams['ytick.left'] = plt.rcParams['ytick.labelleft'] = False
plt.figure(figsize=[8, 6])
plt.hist(df['days'], bins=range(0, 50, 1), color="dodgerblue", edgecolor='black'
,cumulative=-1, density=True
,histtype='barstacked')
plt.xlabel('Number of Days')
plt.ylabel('Density')
Thank you so much!
Oneliner:
plt.axhline(y, color='k', linestyle='dashed', linewidth=1)
Use this to add a horizontal line to your histogram.
Place your mean or value of y in place of y in the above code snippet.
Simply drawing a horizontal line rises two problems:
The line will be drawn on top of the bars, from the left to the right. To have it behind the bars, use zorder=0.
The line will still be visible at the far left, as there are no bars there. Changing the x-axis to a "tight" layout with plt.autoscale(enable=True, axis='x', tight=True) solves that.
To add a new tick at the specific y-position, you can take the list of existing ticks, create a list including the new tick and set those as the new ticks.
To change the color of the newly added tick, you first find its index in the list, and then change the color of the tick with that index.
One problem with this approach, is that the new tick might overlap with an existing tick. This could be solved by looping through the list and if an existing tick is nearer than some epsilon to the new tick, remove the existing tick. This is not yet implemented in the code example.
Alternatively, the tick value could be displayed to the left of the axis, on top of the horizontal line. Of course, that would lead to a problem in case there wouldn't be enough place for the text.
You might want to round the value of the special tick to the nearest hundredths to prevent that the other ticks also get displayed with more digits.
I created an example with simulated data:
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
df = pd.DataFrame({"days": np.random.normal(25, 10, 10000)})
plt.rcParams['ytick.right'] = plt.rcParams['ytick.labelright'] = True
plt.rcParams['ytick.left'] = plt.rcParams['ytick.labelleft'] = False
plt.figure(figsize=[8, 6])
bin_heights, _, _ = plt.hist(df['days'], bins=range(0, 50, 1), color="dodgerblue", edgecolor='black',
cumulative=-1, density=True,
histtype='barstacked')
plt.autoscale(enable=True, axis='both', tight=True) # use axis='x' to only set the x axis tight
special_y = bin_heights[15]
# draw a horizontal line, use zorder=0 so it is drawn behind the bars
plt.axhline(special_y, 0, 1, color='red', linestyle='dashed', linewidth=1, zorder=0)
plt.yticks(list(plt.yticks()[0]) + [special_y]) # add a tick in y for special_y
# find the index of special_y in the new ticks (ticks are sorted automatically)
index_special_y = list(plt.yticks()[0]).index(special_y)
plt.gca().get_yticklabels()[index_special_y].set_color('red') # change the color of the special tick
plt.xlabel('Number of Days')
plt.ylabel('Density')
plt.show()

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

Adding a colorbar and a line to multiple imshow() plots

I have this source code:
idx=0
b=plt.psd(dOD[:,idx],Fs=self.fs,NFFT=512)
B=np.zeros((2*len(self.Chan),len(b[0])))
B[idx,:]=20*log10(b[0])
c=plt.psd(dOD_filt[:,idx],Fs=self.fs,NFFT=512)
C=np.zeros((2*len(self.Chan),len(b[0])))
C[idx,:]=20*log10(c[0])
for idx in range(2*len(self.Chan)):
b=plt.psd(dOD[:,idx],Fs=self.fs,NFFT=512)
B[idx,:]=20*log10(b[0])
c=plt.psd(dOD_filt[:,idx],Fs=self.fs,NFFT=512)
C[idx,:]=20*log10(c[0])
## Calculate the color scaling for the imshow()
aux1 = max(max(B[i,:]) for i in range(size(B,0)))
aux2 = min(min(B[i,:]) for i in range(size(B,0)))
bux1 = max(max(C[i,:]) for i in range(size(C,0)))
bux2 = min(min(C[i,:]) for i in range(size(C,0)))
scale1 = 0.75*max(aux1,bux1)
scale2 = 0.75*min(aux2,bux2)
fig, axes = plt.subplots(nrows=2, ncols=1,figsize=(7,7))#,sharey='True')
fig.subplots_adjust(wspace=0.24, hspace=0.35)
ii=find(c[1]>frange)[0]
## Making the plots
cax=axes[0].imshow(B, origin = 'lower',vmin=scale2,vmax=scale1)
axes[0].set_ylim((0,2*len(self.Chan)))
axes[0].set_xlabel(' Frequency (Hz) ')
axes[0].set_ylabel(' Channel Number ')
axes[0].set_title('Pre-Filtered')
cax2=axes[1].imshow(C, origin = 'lower',vmin=scale2,vmax=scale1)
axes[1].set_ylim(0,2*len(self.Chan))
axes[1].set_xlabel(' Frequency (Hz) ')
axes[1].set_ylabel(' Channel Number ')
axes[1].set_title('Post-Filtered')
axes[0].annotate('690nm', xy=((ii+1)/2, len(self.Chan)/2-1),
xycoords='data', va='center', ha='right')
axes[0].annotate('830nm', xy=((ii+1)/2, len(self.Chan)*3/2-1 ),
xycoords='data', va='center', ha='right')
axes[1].annotate('690nm', xy=((ii+1)/2, len(self.Chan)/2-1),
xycoords='data', va='center', ha='right')
axes[1].annotate('830nm', xy=((ii+1)/2, len(self.Chan)*3/2-1 ),
xycoords='data', va='center', ha='right')
axes[0].axis('tight')
axes[1].axis('tight')
## Set up the xlim to aprox frange Hz
axes[0].set_xlim(left=0,right=ii)
axes[1].set_xlim(left=0,right=ii)
## Make the xlabels become the actual frequency number
tickslabel=np.zeros((ii))
ticks = r_[0:ii:5]
tickslabel = linspace(0.,2.,size(ticks))
axes[0].set_xticks(ticks)
axes[0].set_xticklabels(tickslabel)
axes[1].set_xticks(ticks)
axes[1].set_xticklabels(tickslabel)
## Draw a line to separate the two different wave lengths, and name each region
l1 = Line2D([0,ii],[28,10],ls=':',color='black')
axes[0].add_line(l1)
axes[1].add_line(l1)
This code generates this figure:
The fixed code to make the xticks looks properly are already inside the code, and the new plot is also shown.
How can I add a single colorbar (and give it a title) to both this subplots? (they are at same scale)
This colorbar should occupy the whole left side of the figure.
Inside the code there`s a place I try to draw a line in both figures (at the same place), but none of those are shown. Why is that?
If you need any more information about my code (like the size of the data entered, just ask).
Your ticks variable appears to be all zeros:
ticks=np.zeros((ii))
but it should enumerate X locations (in axis coordinates) where you'd like the tick marks to go. When you call set_xticklabels, the list gives the text to show for each tick.
Here's a simple example showing how xlim, set_xticks, and set_xticklabels interact:
from pylab import *
x = arange(128*128).reshape((128,128))
matshow(x)
xlim(right=64)
# xticks: where the xticks should go (indexes into x's columns)
xticks = r_[0:64:25]
gca().set_xticks(xticks)
# labels: text to show for each element of xticks
# here, we apply a multiplier just to show how the
# labels can differ from the xticks.
labels = ['%.1f' % (x,) for x in xticks * pi / 2]
gca().set_xticklabels(labels)
show()
As already mentioned, you need to have the xticks not be zeros. You could use something like
xticks = linspace(0.,2.,5)
which will give you 5 points between 0.0 and 2.0. To get a color bar use
fig.colorbar()
which is demonstrated at http://matplotlib.sourceforge.net/examples/pylab_examples/colorbar_tick_labelling_demo.html
Since the data has the same scale, adding a colorbar for either set of data should do the trick for you, but you might have to adjust its placement.

Categories

Resources