python matplotlib how make fixed width Bar chart - python

I am using this code to generate a Bar chart from a dynamic source the problem that i have is that when i have less then 10 Bars the width of the bars change and it is no the same layout here is the code :
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
from Processesing import dataProcess
def chartmak (dic) :
z = 0
D={}
D=dic
fig, ax = plt.subplots()
n = len(D)
ax.barh(range(n), D.values(), align='center', fc='#80d0f1', ec='w')
ax.set_yticks(range(n))
#this need more work to put the GB or the TB
ax.set_yticklabels(['{:3d} GB'.format(e) for e in D.values()], color='gray')
ax.tick_params(pad=10)
for i, (label, val) in enumerate(D.items()):
z+=1
ax.annotate(label.title(), xy=(10, i), fontsize=12, va='center')
for spine in ('top', 'right', 'bottom', 'left'):
ax.spines[spine].set_visible(False)
ax.xaxis.set_ticks([])
ax.yaxis.set_tick_params(length=0)
plt.gca().invert_yaxis()
plt.savefig("test.png")
plt.show()

You could just pad your dictionary entries if there are less than 10 as follows:
import matplotlib.pyplot as plt
def chartmak(dic):
entries = list(dic.items())
if len(entries) < 10:
entries.extend([('', 0)] * (10 - len(entries)))
values = [v for l, v in entries]
fig, ax = plt.subplots()
n = len(entries)
ax.barh(range(n), values, align='center', fc='#80d0f1', ec='w')
ax.set_yticks(range(n))
#this need more work to put the GB or the TB
ax.set_yticklabels(['' if e == 0 else '{:3d} GB'.format(e) for e in values], color='gray')
ax.tick_params(pad=10)
for i, (label, val) in enumerate(entries):
ax.annotate(label.title(), xy=(10, i), fontsize=12, va='center')
for spine in ('top', 'right', 'bottom', 'left'):
ax.spines[spine].set_visible(False)
ax.xaxis.set_ticks([])
ax.yaxis.set_tick_params(length=0)
plt.gca().invert_yaxis()
plt.show()
chartmak({'1': 10, '2':20, '3':30})
chartmak({'1': 10, '2':20, '3':30, '4':40, '5':80, '6':120, '7':100, '8':50, '9':30, '10':40})
This would show the following output:

Related

imshow subplot placement inside matplotlib figure

I have a Python script that draws a matrix of images, each image is read from disk and is 100x100 pixels. Current result is:
matrix of images
I don't know why Python adds vertical spacing between each row. I tried setting several parameters for plt.subplots. Rendering code is below:
fig, axs = plt.subplots(
gridRows, gridCols, sharex=True, sharey=False, constrained_layout={'w_pad': 0, 'h_pad': 0, 'wspace': 0, 'hspace': 0}, figsize=(9,9)
)
k = 0
for i in range(len(axs)):
for j in range(len(axs[i])):
if (k < paramsCount and dataset.iat[k,2]):
img = mpimg.imread(<some_folder_path>)
else:
img = mpimg.imread(<some_folder_path>)
ax = axs[i, j]
ax.imshow(img)
ax.axis('off')
if (i == 0): ax.set_title(dataset.iat[k,1])
if (j == 0): ax.text(-0.2, 0.5, dataset.iat[k,0], transform=ax.transAxes, verticalalignment='center', rotation='vertical', size=12)
axi = ax.axis()
rec = plt.Rectangle((axi[0], axi[2]), axi[1] - axi[0], axi[3] - axi[2], fill=False, lw=1, linestyle="dotted")
rec = ax.add_patch(rec)
rec.set_clip_on(False)
k = k + 1
plt.show()
Desired result is like:
desired result
Does anyone have ideas?
I'm sure there are many ways to do this other than the tashi answer, but the grid and subplot keywords are used in the subplot to remove the spacing and scale. In the loop process for each subplot, I set the graph spacing, remove the tick labels, and adjust the spacing by making the border dashed and the color gray. The title and y-axis labels are also added based on the loop counter value. Since the data was not provided, some of the data is written directly, so please replace it with your own data.
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(20220510)
grid = np.random.rand(4, 4)
gridRows, gridCols = 5, 10
titles = np.arange(5,51,5)
ylabels = [500,400,300,200,100]
fig, axs = plt.subplots(gridRows, gridCols,
figsize=(8,4),
gridspec_kw={'wspace':0, 'hspace':0},
subplot_kw={'xticks': [], 'yticks': []}
)
for i, ax in enumerate(axs.flat):
ax.imshow(grid, interpolation='lanczos', cmap='viridis', aspect='auto')
ax.margins(0, 0)
if i < 10:
ax.set_title(str(titles[i]))
if i in [0,10,20,30,40]:
ax.set_ylabel(ylabels[int(i/10)])
ax.set_xticklabels([])
ax.set_yticklabels([])
for s in ['bottom','top','left','right']:
ax.spines[s].set_linestyle('dashed')
ax.spines[s].set_capstyle("butt")
for spine in ax.spines.values():
spine.set_edgecolor('gray')
plt.show()
I realized it has to do with the dimensions passed to figsize. Since rows count is half the columns count, I need to pass figsize(width, width/2).

Matplotlib fill_between edge

I need to create a plot as close to this picture as possible (given the generated dataframe code below):
And here's the output plot of my code:
What I am having problems with is:
The edge of fill_between is not sharp as in the picture. What I have is some kind of white shadow. How do I change the line between the fillings to match a target picture?
How do I aling legend color lines to the center, but not to the left border which my code does?
Here's my code:
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.cm as cm
import numpy as np
import pandas as pd
ncols = 10
figsize = (20, 5)
fontsize = 14
dti = pd.date_range('2013-01-01', '2020-12-31', freq='2W')
probabilities_in_time = np.random.random((ncols, len(dti)))
probabilities_in_time = probabilities_in_time / \
probabilities_in_time.sum(axis=0)
probabilities_in_time = pd.DataFrame(probabilities_in_time).T
probabilities_in_time.index = dti
cm_subsection = np.linspace(0, 1, ncols)
colors = [cm.coolwarm(x) for x in cm_subsection]
def plot_time_probabilities(probabilities_in_time, figsize):
plt.figure(figsize=figsize)
plt.yticks(np.arange(0, 1.2, 0.2), fontsize=fontsize)
plt.xticks(fontsize=fontsize)
draw_stack_plot(colors, probabilities_in_time)
set_grid()
set_legend()
plt.show()
def draw_stack_plot(colors, probabilities_in_time):
for i, color in enumerate(colors):
if i == 0:
plt.plot(probabilities_in_time[i], color=color)
plt.fill_between(probabilities_in_time.index,
probabilities_in_time[0], color=color)
else:
probabilities_in_time[i] += probabilities_in_time[i-1]
plt.fill_between(probabilities_in_time.index,
probabilities_in_time[i], probabilities_in_time[i-1],
color=color)
plt.plot(probabilities_in_time[i], label=' Probability: {}'.format(
i), color=color)
def set_grid():
ax = plt.gca()
ax.set_axisbelow(False)
ax.xaxis.grid(True, linestyle='-', lw=1)
def set_legend():
leg = plt.legend(loc='lower left', fontsize=14, handlelength=1.3)
for i in leg.legendHandles:
i.set_linewidth(12)
plot_time_probabilities(probabilities_in_time, figsize)
To set the legend in the center, you can set loc='center', or you can put the legend outside. To avoid that the legend handles grow to larger, you can leave out .set_linewidth(12) (this sets a very wide edge width of 12 points).
Shifting the colors by one position can help to show the fill borders more pronounced. To still have a correct legend, the label should then be added to fill_between.
The code below also tries to simplify part of the calls:
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
import pandas as pd
ncols = 10
figsize = (20, 5)
fontsize = 14
dti = pd.date_range('2013-01-01', '2020-12-31', freq='2W')
probabilities_in_time = np.random.random((ncols, len(dti)))
probabilities_in_time = probabilities_in_time / probabilities_in_time.sum(axis=0)
probabilities_in_time = pd.DataFrame(probabilities_in_time).T
probabilities_in_time.index = dti
cm_subsection = np.linspace(0, 1, ncols)
colors = cm.coolwarm(cm_subsection)
def plot_time_probabilities(probabilities_in_time, figsize):
plt.figure(figsize=figsize)
plt.yticks(np.arange(0, 1.2, 0.2), fontsize=fontsize)
plt.xticks(fontsize=fontsize)
draw_stack_plot(colors, probabilities_in_time)
set_grid()
set_legend()
# plt.margins(x=0, y=0)
plt.margins(x=0.02)
plt.tight_layout()
plt.show()
def draw_stack_plot(colors, probabilities_in_time):
current_probabilities = 0
for i, color in enumerate(colors):
plt.fill_between(probabilities_in_time.index,
probabilities_in_time[i] + current_probabilities, current_probabilities,
color=color, label=f' Probability: {i}')
current_probabilities += probabilities_in_time[i]
plt.plot(current_probabilities,
color=colors[0] if i <= 1 else colors[-1] if i >= 8 else colors[i - 1] if i < 5 else colors[i + 1])
def set_grid():
ax = plt.gca()
ax.set_axisbelow(False)
ax.xaxis.grid(True, linestyle='-', lw=1)
def set_legend():
leg = plt.legend(loc='lower left', fontsize=14, handlelength=1.3)
# leg = plt.legend(loc='upper left', bbox_to_anchor=(1.01, 1), fontsize=14, handlelength=1.3)
# for i in leg.legendHandles:
# i.set_linewidth(12)
plot_time_probabilities(probabilities_in_time, figsize)

How to limit lower error of bar plot to 0?

I calculated the rttMeans and rttStds arrays. However, the value of rttStds makes the lower error less than 0.
rttStds = [3.330311915835426, 3.3189677330174883, 3.3319538853150386, 3.325173772304221, 3.3374145232695813]
How to set lower error to 0 instead of -#?
The python bar plot code is bellow.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(rc={'figure.figsize':(18,16)},style='ticks',font_scale = 1.5,font='serif')
N = 5
ind = ['RSU1', 'RSU2', 'RSU3', 'RSU4', 'RSU5'] # the x locations for the groups
width = 0.4 # the width of the bars: can also be len(x) sequence
fig = plt.figure(figsize=(10,6))
ax = fig.add_subplot(111)
p1 = plt.bar(ind, rttMeans, width, yerr=rttStds, log=False, capsize = 16, color='green', hatch="/", error_kw=dict(elinewidth=3,ecolor='black'))
plt.margins(0.01, 0)
#Optional code - Make plot look nicer
plt.xticks(rotation=0)
i=0.18
for row in rttMeans:
plt.text(i, row, "{0:.1f}".format(row), color='black', ha="center")
i = i + 1
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
params = {'axes.titlesize':24,
'axes.labelsize':24,
'xtick.labelsize':28,
'ytick.labelsize':28,
'legend.fontsize': 24,
'axes.spines.right':False,
'axes.spines.top':False}
plt.rcParams.update(params)
plt.tick_params(axis="y", labelsize=28, labelrotation=20, labelcolor="black")
plt.tick_params(axis="x", labelsize=28, labelrotation=20, labelcolor="black")
plt.ylabel('RT Time (millisecond)', fontsize=24)
plt.title('# Participating RSUs', fontsize=24)
# plt.savefig('RSUs.pdf', bbox_inches='tight')
plt.show()
You can pass yerr as a pair [lower_errors, upper_errors] where you can control lower_errors :
lowers = np.minimum(rttStds,rttMeans)
p1 = plt.bar(ind, rttMeans, width, yerr=[lowers,rttStds], log=False, capsize = 16, color='green', hatch="/", error_kw=dict(elinewidth=3,ecolor='black'))
Output:

adjust the position of colorbar and equalize the size of subplots

Following my previous question that didn't get any answer, I tried to solve my problem of adding colorbar instead of legend to my plots. There are couple of problems that I couldn't solve yet.
Update:
I want to move the colorbar to the proper position on the right of the plot.
I generate two plots with the same instruction but the second one looks completely different and I couldn't understand what caused this problem.
Here is my code:
import numpy as np
import pylab as plt
from matplotlib import rc,rcParams
rc('text',usetex=True)
rcParams.update({'font.size':10})
import matplotlib.cm as cm
from matplotlib.ticker import NullFormatter
import matplotlib as mpl
def plot(Z_s,CWL,filter_id,spectral_type,model_mag,mag,plot_name):
f= ['U38','B','V','R','I','MB420','MB464','MB485','MB518','MB571','MB604','MB646','MB696','MB753','MB815','MB856','MB914']
wavetable=CWL/(1+Z_s)
dd=model_mag-mag
nplist=['E', 'Sbc', 'Scd', 'Irr', 'SB3', 'SB2']
minimum,maximum=(0.,16.)
Z = [[0,0],[0,0]]
levels = list(np.linspace(0, 1, len(f)))
NUM_COLORS = len(f)
cm = plt.get_cmap('gist_rainbow')
mycolor=[]
for i in range(NUM_COLORS):
mycolor.append( cm(1.*i/NUM_COLORS)) # color will now be an RGBA tuple
mymap = mpl.colors.LinearSegmentedColormap.from_list('mycolors',mycolor)
CS3 = plt.contourf(Z, levels, cmap=mymap)
plt.clf()
FILTER=filter_id
SED=spectral_type
for (j,d) in enumerate(nplist):
bf=(SED==j)
if (j<3):
k=j
i_subplot = k + 1
fig = plt.figure(1, figsize=(5,5))
ax = fig.add_subplot(3,1,i_subplot)
for i in range(len(f)):
bb=np.where(FILTER[bf]==i)[0]
r=mycolor[i][0]
g=mycolor[i][1]
b=mycolor[i][2]
ax.scatter(wavetable[bb], dd[bb], s=1, color=(r,g,b))
if (k<2):
ax.xaxis.set_major_formatter( NullFormatter() )
ax.set_ylabel(r'$\Delta$ MAG',fontsize=10)
else:
ax.set_xlabel(r'WL($\AA$)',fontsize=10)
ax.set_ylabel(r'$\Delta$ MAG',fontsize=10)
fig.subplots_adjust(wspace=0,hspace=0)
ax.axhline(y=0,color='k')
ax.set_xlim(1000,9000)
ax.set_ylim(-3,3)
ax.set_xticks(np.linspace(1000, 9000, 16, endpoint=False))
ax.set_yticks(np.linspace(-3, 3, 4, endpoint=False))
ax.text(8500,2.1,nplist[j], {'color': 'k', 'fontsize': 10})
fontsize=8
for tick in ax.xaxis.get_major_ticks():
tick.label1.set_fontsize(fontsize)
for tick in ax.yaxis.get_major_ticks():
tick.label1.set_fontsize(fontsize)
if (j==2):
cbar_ax = fig.add_axes([0.9, 0.15, 0.05, 0.7])
cbar=plt.colorbar(CS3, cax=cbar_ax, ticks=range(0,len(f)),orientation='vertical')
cbar.ax.get_yaxis().set_ticks([])
for s, lab in enumerate(f):
cbar.ax.text( 0.08,(0.95-0.01)/float(len(f)-1) * s, lab, fontsize=8,ha='left')
fname = plot_name+'.'+nplist[0]+'.'+nplist[1]+'.'+nplist[2]+'.pdf'
plt.savefig(fname)
plt.close()
else:
k=j-3
i_subplot = k + 1
fig = plt.figure(1, figsize=(5,5))
ax = fig.add_subplot(3,1,i_subplot)
for i in range(len(f)):
bb=np.where(FILTER[bf]==i)[0]
r=mycolor[i][0]
g=mycolor[i][1]
b=mycolor[i][2]
ax.scatter(wavetable[bb], dd[bb], s=1, color=(r,g,b))
if (k<2):
ax.xaxis.set_major_formatter( NullFormatter() )
ax.set_ylabel(r'$\Delta$ MAG',fontsize=10)
else:
ax.set_xlabel(r'WL($\AA$)',fontsize=10)
ax.set_ylabel(r'$\Delta$ MAG',fontsize=10)
fig.subplots_adjust(wspace=0,hspace=0)
ax.axhline(y=0,color='k')
ax.set_xlim(1000,9000)
ax.set_ylim(-3,3)
ax.set_xticks(np.linspace(1000, 9000, 16, endpoint=False))
ax.set_yticks(np.linspace(-3, 3, 4, endpoint=False))
ax.text(8500,2.1,nplist[j], {'color': 'k', 'fontsize': 10})
fontsize=8
for tick in ax.xaxis.get_major_ticks():
tick.label1.set_fontsize(fontsize)
for tick in ax.yaxis.get_major_ticks():
tick.label1.set_fontsize(fontsize)
if (j==5):
cbar_ax = fig.add_axes([0.9, 0.15, 0.05, 0.7])
cbar=plt.colorbar(CS3, cax=cbar_ax, ticks=range(0,len(f)),orientation='vertical')
cbar.ax.get_yaxis().set_ticks([])
for s, lab in enumerate(f):
cbar.ax.text( 0.08,(0.95-0.01)/float(len(f)-1) * s, lab , fontsize=8,ha='left')
fname = plot_name+'.'+nplist[3]+'.'+nplist[4]+'.'+nplist[5]+'.pdf'
plt.savefig(fname)
plt.close()
a=np.loadtxt('calibration.photometry.information.capak.cat')
Z_s=a[:,0]
CWL=a[:,1]
filter_id=a[:,2]
spectral_type=a[:,3]
model_mag=a[:,4]
mag=a[:,5]
plot_name='test'
plot(Z_s,CWL,filter_id,spectral_type,model_mag,mag,plot_name)
you can also download the data from here.
I will appreciate to get any help.
You can use plt.subplots() passing the gridspec_kw parameter to adjust the axes' aspect ratio in a very flexible way, and then select the top axes to include the colorbar.
I've worked on your code simplifying it quite a bit. Furthermore, I've changed many things in your code such as: PEP8, removed repeated calls to plt.savefig()and ax methods. The result is:
import numpy as np
import pylab as plt
from matplotlib import rc, rcParams, colors
rc('text', usetex=True)
rcParams['font.size'] = 10
rcParams['axes.labelsize'] = 8
def plot(Z_s, CWL, filter_id, spectral_type, model_mag, mag, plot_name):
f= ['U38', 'B', 'V', 'R', 'I', 'MB420', 'MB464', 'MB485', 'MB518',
'MB571', 'MB604', 'MB646', 'MB696', 'B753', 'MB815', 'MB856',
'MB914']
wavetable = CWL/(1+Z_s)
dd = model_mag-mag
nplist = ['E', 'Sbc', 'Scd', 'Irr', 'SB3', 'SB2']
minimum, maximum = (0., 16.)
Z = [[0, 0],[0, 0]]
levels = list(np.linspace(0, 1, len(f)+1))
NUM_COLORS = len(f)
cmap = plt.get_cmap('gist_rainbow')
mycolor = []
for i in range(NUM_COLORS):
mycolor.append(cmap(1.*i/NUM_COLORS))
mymap = colors.LinearSegmentedColormap.from_list('mycolors', mycolor)
CS3 = plt.contourf(Z, levels, cmap=mymap)
coords = CS3.get_array()
coords = coords[:-1] + np.diff(coords)/2.
FILTER = filter_id
SED = spectral_type
dummy = 2
xmin = 1000
xmax = 9000
ymin = -3
ymax = 3
fig, axes = plt.subplots(nrows=5, figsize=(5, 6),
gridspec_kw=dict(height_ratios=[0.35, 0.05, 1, 1, 1]))
fig2, axes2 = plt.subplots(nrows=5, figsize=(5, 6),
gridspec_kw=dict(height_ratios=[0.35, 0.05, 1, 1, 1]))
fig.subplots_adjust(wspace=0, hspace=0)
fig2.subplots_adjust(wspace=0, hspace=0)
axes_all = np.concatenate((axes[dummy:], axes2[dummy:]))
dummy_axes = np.concatenate((axes[:dummy], axes2[:dummy]))
for ax in axes_all:
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
ax.axhline(y=0, color='k')
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
ax.set_xticks([])
ax.set_yticks(np.linspace(ymin, ymax, 4, endpoint=False))
ax.set_ylabel(r'$\Delta$ MAG', fontsize=10)
axes[-1].set_xticks(np.linspace(xmin, xmax, 16, endpoint=False))
axes2[-1].set_xticks(np.linspace(xmin, xmax, 16, endpoint=False))
plt.setp(axes[-1].xaxis.get_majorticklabels(), rotation=30)
plt.setp(axes2[-1].xaxis.get_majorticklabels(), rotation=30)
axes[-1].set_xlabel(r'WL($\AA$)', fontsize=10)
axes2[-1].set_xlabel(r'WL($\AA$)', fontsize=10)
for ax in dummy_axes:
for s in ax.spines.values():
s.set_visible(False)
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
ax.set_xticks([])
ax.set_yticks([])
for axes_i in [axes, axes2]:
cbar = plt.colorbar(CS3, ticks=[], orientation='horizontal',
cax=axes_i[0])
for s, lab in enumerate(f):
cbar.ax.text(coords[s], 0.5, lab, fontsize=8, va='center',
ha='center', rotation=90,
transform=cbar.ax.transAxes)
for (j, d) in enumerate(nplist):
bf = (SED==j)
if (j<3):
k = j
ax = axes[k+dummy]
ax.text(8500, 2.1, nplist[j], {'color': 'k', 'fontsize': 10})
for i in range(len(f)):
bb = np.where(FILTER[bf]==i)[0]
ax.scatter(wavetable[bb], dd[bb], s=1, color=mycolor[i])
else:
k = j-3
ax = axes2[k+dummy]
ax.text(8500, 2.1, nplist[j], {'color': 'k', 'fontsize': 10})
for i in range(len(f)):
bb = np.where(FILTER[bf]==i)[0]
ax.scatter(wavetable[bb], dd[bb], s=1, color=mycolor[i])
fname = '.'.join([plot_name, nplist[0], nplist[1], nplist[2], 'png'])
fig.savefig(fname)
fname = '.'.join([plot_name, nplist[3], nplist[4], nplist[5], 'png'])
fig2.savefig(fname)
if __name__=='__main__':
a = np.loadtxt('calibration.photometry.information.capak.cat')
Z_s = a[:, 0]
CWL = a[:, 1]
filter_id = a[:, 2]
spectral_type = a[:, 3]
model_mag = a[:, 4]
mag = a[:, 5]
plot_name = 'test'
plot(Z_s, CWL, filter_id, spectral_type, model_mag, mag, plot_name)
which gives:

Group labels in matplotlib barchart using Pandas MultiIndex

I have a pandas DataFrame with a MultiIndex:
group subgroup obs_1 obs_2
GroupA Elem1 4 0
Elem2 34 2
Elem3 0 10
GroupB Elem4 5 21
and so on. As noted in this SO question this is actually doable in matplotlib, but I'd rather (if possible) use the fact that I already know the hierarchy (thanks to the MultiIndex). Currently what's happening is that the index is shown as a tuple.
Is such a thing possible?
If you have just two levels in the MultiIndex, I believe the following will be easier:
plt.figure()
ax = plt.gca()
DF.plot(kind='bar', ax=ax)
plt.grid(True, 'both')
minor_XT = ax.get_xaxis().get_majorticklocs()
DF['XT_V'] = minor_XT
major_XT = DF.groupby(by=DF.index.get_level_values(0)).first()['XT_V'].tolist()
DF.__delitem__('XT_V')
ax.set_xticks(minor_XT, minor=True)
ax.set_xticklabels(DF.index.get_level_values(1), minor=True)
ax.tick_params(which='major', pad=15)
_ = plt.xticks(major_XT, (DF.index.get_level_values(0)).unique(), rotation=0)
And a bit of involving, but more general solution (doesn't matter how many levels you have):
def cvt_MIdx_tcklab(df):
Midx_ar = np.array(df.index.tolist())
Blank_ar = Midx_ar.copy()
col_idx = np.arange(Midx_ar.shape[0])
for i in range(Midx_ar.shape[1]):
val,idx = np.unique(Midx_ar[:, i], return_index=True)
Blank_ar[idx, i] = val
idx=~np.in1d(col_idx, idx)
Blank_ar[idx, i]=''
return map('\n'.join, np.fliplr(Blank_ar))
plt.figure()
ax = plt.gca()
DF.plot(kind='bar', ax=ax)
ax.set_xticklabels(cvt_MIdx_tcklab(DF), rotation=0)
I think that there isn't a nice and standard way of plotting multiindex dataframes. I found the following solution by #Stein to be aesthetically pleasant. I've adapted his example to your data:
import pandas as pd
import matplotlib.pyplot as plt
from itertools import groupby
import numpy as np
%matplotlib inline
group = ('Group_A', 'Group_B')
subgroup = ('elem1', 'elem2', 'elem3', 'elem4')
obs = ('obs_1', 'obs_2')
index = pd.MultiIndex.from_tuples([('Group_A','elem1'),('Group_A','elem2'),('Group_A','elem3'),('Group_B','elem4')],
names=['group', 'subgroup'])
values = np.array([[4,0],[43,2],[0,10],[5,21]])
df = pd.DataFrame(index=index)
df['obs_1'] = values[:,0]
df['obs_2'] = values[:,1]
def add_line(ax, xpos, ypos):
line = plt.Line2D([xpos, xpos], [ypos + .1, ypos],
transform=ax.transAxes, color='gray')
line.set_clip_on(False)
ax.add_line(line)
def label_len(my_index,level):
labels = my_index.get_level_values(level)
return [(k, sum(1 for i in g)) for k,g in groupby(labels)]
def label_group_bar_table(ax, df):
ypos = -.1
scale = 1./df.index.size
for level in range(df.index.nlevels)[::-1]:
pos = 0
for label, rpos in label_len(df.index,level):
lxpos = (pos + .5 * rpos)*scale
ax.text(lxpos, ypos, label, ha='center', transform=ax.transAxes)
add_line(ax, pos*scale, ypos)
pos += rpos
add_line(ax, pos*scale , ypos)
ypos -= .1
ax = df.plot(kind='bar',stacked=False)
#Below 2 lines remove default labels
ax.set_xticklabels('')
ax.set_xlabel('')
label_group_bar_table(ax, df)
Which produces:
How to create a grouped bar chart of a hierarchical dataset with 2 levels
You can create a subplot for each group and stick them together with wspace=0. The width of each subplot must be corrected according to the number of subgroups by using the width_ratios argument in the gridspec_kw dictionary so that all the columns have the same width.
Then there are limitless formatting choices to make. In the following example, I choose to draw horizontal grid lines in the background and a separation line between the groups by using the minor tick marks.
import numpy as np # v 1.19.2
import pandas as pd # v 1.1.3
import matplotlib.pyplot as plt # v 3.3.2
# Create sample DataFrame with MultiIndex
df = pd.DataFrame(dict(group = ['GroupA', 'GroupA', 'GroupA', 'GroupB'],
subgroup = ['Elem1', 'Elem2', 'Elem3', 'Elem4'],
obs_1 = [4, 34, 0, 5],
obs_2 = [0, 2, 10, 21]))
df.set_index(['group', 'subgroup'], inplace=True)
# Create figure with a subplot for each group with a relative width that
# is proportional to the number of subgroups
groups = df.index.levels[0]
nplots = groups.size
plots_width_ratios = [df.xs(group).index.size for group in groups]
fig, axes = plt.subplots(nrows=1, ncols=nplots, sharey=True, figsize=(6, 4),
gridspec_kw = dict(width_ratios=plots_width_ratios, wspace=0))
# Loop through array of axes to create grouped bar chart for each group
alpha = 0.3 # used for grid lines, bottom spine and separation lines between groups
for group, ax in zip(groups, axes):
# Create bar chart with horizontal grid lines and no spines except bottom one
df.xs(group).plot.bar(ax=ax, legend=None, zorder=2)
ax.grid(axis='y', zorder=1, color='black', alpha=alpha)
for spine in ['top', 'left', 'right']:
ax.spines[spine].set_visible(False)
ax.spines['bottom'].set_alpha(alpha)
# Set and place x labels for groups
ax.set_xlabel(group)
ax.xaxis.set_label_coords(x=0.5, y=-0.15)
# Format major tick labels for subgroups
ax.set_xticklabels(ax.get_xticklabels(), rotation=0, ha='center')
ax.tick_params(axis='both', which='major', length=0, pad=10)
# Set and format minor tick marks for separation lines between groups: note
# that except for the first subplot, only the right tick mark is drawn to avoid
# duplicate overlapping lines so that when an alpha different from 1 is chosen
# (like in this example) all the lines look the same
if ax.is_first_col():
ax.set_xticks([*ax.get_xlim()], minor=True)
else:
ax.set_xticks([ax.get_xlim()[1]], minor=True)
ax.tick_params(which='minor', length=45, width=0.8, color=[0, 0, 0, alpha])
# Add legend using the labels and handles from the last subplot
fig.legend(*ax.get_legend_handles_labels(), frameon=False,
bbox_to_anchor=(0.92, 0.5), loc="center left")
title = 'Grouped bar chart of a hierarchical dataset with 2 levels'
fig.suptitle(title, y=1.01, size=14);
Reference: this answer by gyx-hh

Categories

Resources