How do I plot stacked histograms side by side in matplotlib? - python

I'm looking to plot two side-by-side stacked histograms (similar to the example image below) in matplotlib.
I've tried several variations on
bins = np.arange(10)
a1,b1,c1 =plt.hist([arr1,arr2,arr3],bins,stacked=True)
a2,b2,c2 =plt.hist([arr4,arr5,arr6],bins,stacked=True)
But can't seem to avoid getting the second plot to directly overlay the first.
Any ideas on how this could be resolved?

The picture shows a bar chart and not a histogram. I am pointing this out, not only because I am an obnoxious pedant, but also because I believe it could help you find the right tool :-)
Indeed, for your purpose plt.bar is probably a better pick than plt.hist.
Based on Scironic's suggestion, I modified this demonstration example to make stacked bars, like the ones on your figure.
Adding an offset to the position index (first argument in plt.bar()) is what prevents the bars from overlapping each other.
import numpy as np
import matplotlib.pyplot as plt
N = 5
men1 = (130, 90, 70, 64, 55)
men2 = (120, 85, 62, 50, 53)
men3 = (100, 70, 60, 45, 50)
ind = np.arange(N) + .15 # the x locations for the groups
width = 0.35 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(ind, men1, width, color='g')
rects2 = ax.bar(ind, men2, width, color='r')
rects3 = ax.bar(ind, men3, width, color='b')
women4 = (140, 90, 78, 65, 50)
women5 = (130, 80, 70, 60, 45)
women6 = (120, 60, 60, 55, 44)
xtra_space = 0.05
rects2 = ax.bar(ind + width + xtra_space , women1, width, color='orange')
rects2 = ax.bar(ind + width + xtra_space, women2, width, color='cyan')
rects2 = ax.bar(ind + width + xtra_space, women3, width, color='purple')
# add some text for labels, title and axes ticks
ax.set_ylabel('Population, millions')
ax.set_title('Population: Age Structure')
ax.set_xticks(ind+width+xtra_space)
ax.set_xticklabels( ('USA', 'Brazil', 'Russia', 'Japan', 'Mexico') )
plt.show()

Related

How to plot.bar base on other value than zero

Generally, the bar chart will show bottom on zero. which change the bottom, the bar move up or down.
import numpy as np
import matplotlib.pyplot as plt
N = 5
menMeans = (20, 35, 30, 35, 27)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars: can also be len(x) sequence
p1 = plt.bar(ind, menMeans, width, bottom=0,color='#d62728')
plt.ylabel('Scores')
plt.title('Scores by group and gender')
plt.xticks(ind, ('G1', 'G2', 'G3', 'G4', 'G5'))
plt.yticks(np.arange(0, 81, 10))
plt.legend('Men')
plt.savefig('bar.png')
plt.show()
while I want isn't moving up or down. the following code show the bottom of zero.
I want to show the chart based on value such as 25. if the data,such as 20, then it shows 5 below the 25 in the chart.
You can convert menMeans to numpy and then subtract the bottom. Subtracting 25, the example array would be [-5, 10, 5, 10, 2].
The x-axis can be moved to that height via ax.spines['bottom'].set_position(('data', bottom)). Similarly, the other spines can be made invisible (ax.spines[...].set_color('none')).
plt.tick_params() can remove the tick marks by setting their length to 0.
ax.text(x, y, text) can be used to set a text at a given position. Newlines can help to get an adequate padding independent of the y-axis.
import numpy as np
import matplotlib.pyplot as plt
N = 5
menMeans = (20, 35, 30, 35, 27)
menMeans = np.array(menMeans)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars: can also be len(x) sequence
bottom = 25
p1 = plt.bar(ind, menMeans-bottom, width, bottom=bottom, color='#d62728', label='Men')
plt.ylabel('Scores')
plt.title('Scores by group and gender')
plt.xticks(ind, ('G1', 'G2', 'G3', 'G4', 'G5'))
plt.yticks(np.arange(0, 81, 10))
plt.tick_params(axis='both', length=0)
ax = plt.gca()
ax.spines['left'].set_color('none')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position(('data', bottom))
ax.spines['top'].set_color('none')
for i, mean in zip(ind, menMeans):
ax.text(i, mean, f'{mean}\n' if mean >= bottom else f'\n{mean}', ha='center', va='center')
plt.legend()
plt.savefig('bar.png')
plt.show()

Overlapping matplot Bar Graphs?

I'm learning matplot in python and I want to make a bar graph with side-by-side bars. My plot is currently overlapping for some reason.
Graph
import numpy as np
import matplotlib.pyplot as plt
n_groups = 7
means_frank = (82, 75, 86, 63, 90, 73 ,88)
means_alex = (91, 92, 80, 73, 83, 91, 71)
means_joe = (72, 42, 50, 33, 63, 34, 54)
fig = plt.figure()
ax = fig.add_subplot(111)
index = np.arange(n_groups)
bar_width = 0.27
opacity = 0.8
rects1 = ax.bar(index,means_frank,bar_width,color='b', label="Frank")
rects2 = ax.bar(index,means_alex,bar_width,color='g', label="Alex")
rects3 = ax.bar(index,means_joe,bar_width,color='r', label="Joe")
plt.ylabel('Scores')
plt.title('Test Scores')
plt.xticks([0, 5, 6], ["Assignments -->", "<-- Midterm", "Final"])
plt.legend()
plt.tight_layout()
plt.show()
How can I make these 3 different plots appear side by side instead of overlapped?
Thanks!
#ImportanceofBeingErnest helped with this link:
https://matplotlib.org/gallery/lines_bars_and_markers/barchart.html
The index argument must be modified to prevent overlap.

Barplot python yticks in equal range despite value (pseudo logarithmic)

I'm trying to manipulate my y axis in a barplot. Say I have this code
import numpy as np
import matplotlib.pyplot as plt
N = 11
men_means = (-500,0,1,2,5,10,20,50,100,200,500)
men_std = (-500,0,1,2,5,10,20,50,100,200,500)
ind = np.arange(N) # the x locations for the groups
width = 0.25 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(ind, men_means, width, color='r', yerr=men_std)
women_means = (-500,0,1,2,5,10,20,50,100,200,500)
women_std = (-500,0,1,2,5,10,20,50,100,200,500)
rects2 = ax.bar(ind + width, women_means, width, color='y', yerr=women_std)
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width)
ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7'))
ax.legend((rects1[0], rects2[0]), ('Men', 'Women'))
plt.show()
It will give me a y axis that is linear. Of course I could use 'symlog' to make the y-axis logarithmic (for my original data there are negative values as well) but what I really want is to have a 'pseudo logarithmic y axis', where the ticks are set to -500, -200, -100, -50, [..],0, 1, 2 ,5, 10, 20, [...], 500, but are equally distributed along the yaxis.
Can anyone help? :-)

why matplotlib color bar do not show any tick marks and labels

I've been trying to plot shape-file polygons with colors according to some data and add a color-bar. Below shown is the code that I've written for the purpose. It give the plot correctly, but there is no labels or tick marks for the color-bar.
fig, ax = plt.subplots(1,1,figsize=(12,10), subplot_kw={'projection': ccrs.PlateCarree()})
ptchs1 = []
for nshp in indx[0,:]:
ptchs = []
pts = np.array(shapes[nshp].points)
pts =pts[np.unique(np.where(~np.isnan(pts[:,:]))[0]),:]
prt = shapes[nshp].parts
par = list(prt) + [pts.shape[0]]
for pij in xrange(len(prt)):
ptchs.append(Polygon(pts[par[pij]:par[pij+1]]))
ptchs1.append(Polygon(pts[par[pij]:par[pij+1]]))
ind=np.where(indx==nshp)[1] ;
ax.add_collection(PatchCollection(ptchs,facecolor=my_cmap[np.where(indx==nshp)[1],:],edgecolor='k', linewidths=.5),ccrs.PlateCarree())
plt.text(xtext[ind], ytext[ind],dnme[ind][0:7],fontsize=8,color='k',fontweight='bold', ha='center',va='center',transform=ccrs.Geodetic())
ax.set_xlim(np.round(mnbbx[0]).astype(int)-0.5,np.round(mxbbx[2]).astype(int)+0.5,2)
ax.set_ylim(np.round(mnbbx[1]).astype(int)-0.5,np.round(mxbbx[3]).astype(int)+0.5,2)
m = cm.ScalarMappable(cmap=cmap)
m.set_array([])
m.set_clim(-0.5, 14+0.5)
divider = make_axes_locatable(ax)
cax = divider.append_axes("right",aspect=20,size="5%",pad=0.05,map_projection=ccrs.PlateCarree())
cb=fig.colorbar(m,cax=cax,norm=norm)
cb.set_ticks(np.arange(0,14))
cb.set_label('RainFall(mm/day)', rotation=90)
cb.set_ticklabels([1,5,10,20,30,40,70,100,130,160,190,220,250])
xticks = np.arange(np.round(mnbbx[0]).astype(int)-0.5,np.round(mxbbx[2]).astype(int)+0.5,2)
yticks = np.arange(np.round(mnbbx[1]).astype(int)-0.5,np.round(mxbbx[3]).astype(int)+0.5,2)
gl = ax.gridlines(crs=ccrs.PlateCarree(),draw_labels=True,linewidth=0.7, color='black', alpha=1)
gl.xlabels_bottom = False ; gl.ylabels_right = False
degree_locator = mticker.MaxNLocator(nbins=4) #it will give gridlines of 4*4 size
gl.xlocator = degree_locator
gl.ylocator = degree_locator
_DEGREE_SYMBOL = u'\u00B0'
gl.xformatter = LONGITUDE_FORMATTER #it will change lon to degree format
gl.yformatter = LATITUDE_FORMATTER
gl.ylabel_style = {'size': 12, 'color': 'black'} # here we can adjust color and size of ticks
gl.xlabel_style = {'color': 'black', 'size': 12 }
plt.show()
Can any one help me that why no color-bar tick mark and labels are not produced?
I have updated my color bar plotting with this piece of code, it plots correctly, but the main plot and color bar size is different (either big or small)
col_bnd=[0,1, 5, 10, 20, 30, 40, 70, 100, 130, 160, 190, 220, 250,300]
norm = colors.BoundaryNorm(col_bnd, cmap.N)
col_bnd=[1, 5, 10, 20, 30, 40, 70, 100, 130, 160, 190, 220, 250]
ax1, ax2 = mpl.colorbar.make_axes(ax, shrink=0.68,aspect=20,pad=0.05)
cbar = mpl.colorbar.ColorbarBase(ax1, cmap=cmap,norm=norm,ticks=col_bnd,boundaries=None,format='%1i') #mpl.colors.Normalize(vmin=-0.5, vmax=1.5))
cbar.set_clim(0, 300)
cbar.set_label('RainFall(mm/day)', rotation=90)
can anyone tell, how to make both main plot and color bar same size?
[Edit]
As a commented below, it seems that colorbars need some different way of showing labels. You can try this:
cb.set_yticklabels([1,5,10,20,30,40,70,100,130,160,190,220,250])
You should call the 'legend' function for each artist you create at your code if you are doing other plottings. For instance, you have the ax artist and you set all the necessary info on it, except the legend.
So you should call it before the plt.show() so it can generate the legend properly:
ax.legend()
fig.legend()
And so on.
The documentation has very good guides for the legend and artists function:
http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.legend
http://matplotlib.org/users/legend_guide.html
http://matplotlib.org/users/artists.html

How to plot bar graphs with same X coordinates side by side ('dodged')

import matplotlib.pyplot as plt
gridnumber = range(1,4)
b1 = plt.bar(gridnumber, [0.2, 0.3, 0.1], width=0.4,
label="Bar 1", align="center")
b2 = plt.bar(gridnumber, [0.3, 0.2, 0.2], color="red", width=0.4,
label="Bar 2", align="center")
plt.ylim([0,0.5])
plt.xlim([0,4])
plt.xticks(gridnumber)
plt.legend()
plt.show()
Currently b1 and b2 overlap each other. How do I plot them separately like so:
There is an example in the matplotlib site. Basically, you just shift the x values by width. Here is the relevant bit:
import numpy as np
import matplotlib.pyplot as plt
N = 5
menMeans = (20, 35, 30, 35, 27)
menStd = (2, 3, 4, 1, 2)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars
fig = plt.figure()
ax = fig.add_subplot(111)
rects1 = ax.bar(ind, menMeans, width, color='royalblue', yerr=menStd)
womenMeans = (25, 32, 34, 20, 25)
womenStd = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind+width, womenMeans, width, color='seagreen', yerr=womenStd)
# add some
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width / 2)
ax.set_xticklabels( ('G1', 'G2', 'G3', 'G4', 'G5') )
ax.legend( (rects1[0], rects2[0]), ('Men', 'Women') )
plt.show()
Below answer will explain each and every line of code in the simplest manner possible:
# Numbers of pairs of bars you want
N = 3
# Data on X-axis
# Specify the values of blue bars (height)
blue_bar = (23, 25, 17)
# Specify the values of orange bars (height)
orange_bar = (19, 18, 14)
# Position of bars on x-axis
ind = np.arange(N)
# Figure size
plt.figure(figsize=(10,5))
# Width of a bar
width = 0.3
# Plotting
plt.bar(ind, blue_bar , width, label='Blue bar label')
plt.bar(ind + width, orange_bar, width, label='Orange bar label')
plt.xlabel('Here goes x-axis label')
plt.ylabel('Here goes y-axis label')
plt.title('Here goes title of the plot')
# xticks()
# First argument - A list of positions at which ticks should be placed
# Second argument - A list of labels to place at the given locations
plt.xticks(ind + width / 2, ('Xtick1', 'Xtick3', 'Xtick3'))
# Finding the best position for legends and putting it
plt.legend(loc='best')
plt.show()
Sometimes could be tricky to find the right bar width. I usually use this np.diff to find the right dimension.
import numpy as np
import matplotlib.pyplot as plt
#The data
womenMeans = (25, 32, 34, 20, 25)
menMeans = (20, 35, 30, 35, 27)
indices = [5.5,6,7,8.5,8.9]
#Calculate optimal width
width = np.min(np.diff(indices))/3
fig = plt.figure()
ax = fig.add_subplot(111)
# matplotlib 3.0 you have to use align
ax.bar(indices-width,womenMeans,width,color='b',label='-Ymin',align='edge')
ax.bar(indices,menMeans,width,color='r',label='Ymax',align='edge')
ax.set_xlabel('Test histogram')
plt.show()
# matplotlib 2.0 (you could avoid using align)
# ax.bar(indices-width,womenMeans,width,color='b',label='-Ymin')
# ax.bar(indices,menMeans,width,color='r',label='Ymax')
This is the result:
What if my indices on my x axis are nominal values like names:
#
import numpy as np
import matplotlib.pyplot as plt
# The data
womenMeans = (25, 32, 34, 20, 25)
menMeans = (20, 35, 30, 35, 27)
indices = range(len(womenMeans))
names = ['Asian','European','North Amercian','African','Austrailian','Martian']
# Calculate optimal width
width = np.min(np.diff(indices))/3.
fig = plt.figure()
ax = fig.add_subplot(111)
ax.bar(indices-width/2.,womenMeans,width,color='b',label='-Ymin')
ax.bar(indices+width/2.,menMeans,width,color='r',label='Ymax')
#tiks = ax.get_xticks().tolist()
ax.axes.set_xticklabels(names)
ax.set_xlabel('Test histogram')
plt.show()
Here are two examples of creating a side-by-side bar chart when you have more than two "categories" in a group.
Manual Method
Manually set the position and width of each bar.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import ticker
coins = ['penny', 'nickle', 'dime', 'quarter']
worth = np.array([.01, .05, .10, .25])
# Coin values times *n* coins
# This controls how many bars we get in each group
values = [worth*i for i in range(1,6)]
n = len(values) # Number of bars to plot
w = .15 # With of each column
x = np.arange(0, len(coins)) # Center position of group on x axis
for i, value in enumerate(values):
position = x + (w*(1-n)/2) + i*w
plt.bar(position, value, width=w, label=f'{i+1}x')
plt.xticks(x, coins);
plt.ylabel('Monetary Value')
plt.gca().yaxis.set_major_formatter(ticker.FormatStrFormatter('$%.2f'))
plt.legend()
Pandas Method
If you put the data into a pandas DataFrame, pandas will do the hard stuff for you.
import pandas as pd
coins = ['penny', 'nickle', 'dime', 'quarter']
worth = [0.01, 0.05, 0.10, 0.25]
df = pd.DataFrame(worth, columns=['1x'], index=coins)
df['2x'] = df['1x'] * 2
df['3x'] = df['1x'] * 3
df['4x'] = df['1x'] * 4
df['5x'] = df['1x'] * 5
from matplotlib import ticker
import matplotlib.pyplot as plt
df.plot(kind='bar')
plt.ylabel('Monetary Value')
plt.gca().yaxis.set_major_formatter(ticker.FormatStrFormatter('$%.2f'))
plt.gca().xaxis.set_tick_params(rotation=0)
Pandas creates a similar figure...

Categories