How to convert grouped bar chart from vertical to horizontal - python

How can this vertical grouped bar chart be changed to a horizontal bar chart (grouped, and stacked)? I need help to alter the code such that the bars are displayed horizontally instead of vertically.
import matplotlib.pyplot as plt
import numpy as np
N = 9
labels = ['L', 'S', 'S', 'M', 'W', 'W', 'S', 'R', 'C']
M_means = [1, 45, 28, 11, 4, 7, 1, 0.02, 0.3]
PO_means = [3, 58, 17, 8, 3, 8, 1, 0.06, 1]
K_means = [1, 44, 30, 11, 3, 7, 1, 0.01, 0.5]
x = np.arange(len(labels)) # the label locations
width = 0.30 # the width of the bars
fig, ax = plt.subplots(figsize=(15, 9))
rects1 = ax.bar(x - width, M_means, width, label='M S and K', color=('#b02a2a'))
rects2 = ax.bar(x, PO_means, width, label='P O S and K', color=('#055cad'))
rects3 = ax.bar(x + width, K_means, width, label='M K', color=('#0b7d53'))
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('% of workday', fontsize=32)
#ax.set_title('Scores by group and gender')
ax.set_xticks(x)
ax.set_xticklabels(labels, fontsize=32, rotation=15)
ax.legend(loc='upper right', frameon=False, fontsize=32, markerscale=2)
ax.bar_label(rects1, size = 32, padding=20, rotation=90)
ax.bar_label(rects2, size = 32, padding=20, rotation=90)
ax.bar_label(rects3, size = 32, padding=20, rotation=90)
plt.xticks(ha='center')
for tick in ax.xaxis.get_major_ticks():
tick.label.set_fontsize(32)
for tick in ax.yaxis.get_major_ticks():
tick.label.set_fontsize(32)
plt.ylim(0, 100)
plt.gca().spines['right'].set_color('none')
plt.gca().spines['top'].set_color('none')
#fig.tight_layout()
plt.show()

Functionally, only two changes are needed:
Change ax.bar to ax.barh
Swap set_x* methods with set_y* methods, e.g. set_xticks() -> set_yticks() and so on
Semantically, the variables x and width should also be renamed to y and height.
import matplotlib.pyplot as plt
import numpy as np
N = 9
labels = list('LSSMWWSRC')
M_means = [1, 45, 28, 11, 4, 7, 1, 0.02, 0.3]
K_means = [2, 40, 21, 18, 3, 3, 2, 0.52, 0.3]
PO_means = [3, 58, 17, 8, 3, 8, 1, 0.06, 1]
K = [1, 44, 30, 11, 3, 7, 1, 0.01, 0.5]
# rename x/width to y/height
y = np.arange(len(labels))
height = 0.30
fig, ax = plt.subplots()
# use ax.barh instead of ax.bar
rects1 = ax.barh(y - height, M_means, height, label='M S and K', color='#b02a2a')
rects2 = ax.barh(y, PO_means, height, label='P O S and K', color='#055cad')
rects3 = ax.barh(y + height, K_means, height, label='M K', color='#0b7d53')
# swap set_x* methods with set_y* methods
ax.set_xlabel('% of workday')
ax.set_yticks(y)
ax.set_yticklabels(labels)
ax.legend(loc='upper right', frameon=False, markerscale=2)
ax.bar_label(rects1, padding=10)
ax.bar_label(rects2, padding=10)
ax.bar_label(rects3, padding=10)
# ...

The easiest solution is to load the data into a pandas.DataFrame, and then use pandas.DataFrame.plot with kind='barh'. This is easier because pandas uses matplotlib as the default plotting backend, and the API groups the bars automatically.
This reduces the code to 14 lines (not including imports).
When using 'barh', xlabel= applies to the y-axis. Therefore, xlabel='' removes the y-axis label.
Adjust figsize=(12, 10) if planning to use smaller / larger font sizes.
See Adding value labels on a matplotlib bar chart for additional details about using .bar_label.
Tested in python 3.10, pandas 1.4.2, matplotlib 3.5.1
import pandas as pd
import matplotlib.pylot as plt
# data
labels = ['L', 'S', 'S', 'M', 'W', 'W', 'S', 'R', 'C']
M_means = [1, 45, 28, 11, 4, 7, 1, 0.02, 0.3]
PO_means = [3, 58, 17, 8, 3, 8, 1, 0.06, 1]
K_means = [1, 44, 30, 11, 3, 7, 1, 0.01, 0.5]
# create a dict with the keys as the desired legend labels
data = {'labels': labels, 'M S and K': M_means, 'P O S and K': PO_means, 'M K': K_means}
# create dataframe
df = pd.DataFrame(data)
# plot: specify y=[...] if only certain columns are desired
ax = df.plot(kind='barh', x='labels', width=.85, figsize=(12, 10), xlabel='', color=['#b02a2a', '#055cad', '#0b7d53'])
ax.set_xlabel('% of workday', fontsize=15)
ax.set_xlim(0, 100)
ax.legend(loc='upper right', frameon=False, fontsize=15, markerscale=2)
for c in ax.containers:
ax.bar_label(c, label_type='edge', padding=1, size=15)
ax.tick_params(axis='both', which='both', labelsize=15)
ax.spines[['top', 'right']].set_visible(False)
Stacked
To manually create the stacked bar without pandas, see Horizontal stacked bar chart in Matplotlib
Use the parameter stacked=True
Some bar patches are to small for the label, so custom labels have been passed to the labels= parameter in .bar_label
Using := requires at least python 3.8. Otherwise use labels = [f'{v.get_width():.0f}' if v.get_width() > 1 else '' for v in c]
ax = df.plot(kind='barh', x='labels', width=.85, figsize=(12, 10), xlabel='',
color=['#b02a2a', '#055cad', '#0b7d53'], stacked=True)
ax.set_xlabel('% of workday', fontsize=15)
ax.set_xlim(0, 100)
ax.legend(loc='upper right', frameon=False, fontsize=15, markerscale=2)
for c in ax.containers:
# custom labels only show label size for values greater than 1
labels = [f'{w:.0f}' if (w := v.get_width()) > 1 else '' for v in c]
ax.bar_label(c, labels=labels, label_type='center', padding=1, size=15)
ax.tick_params(axis='both', which='both', labelsize=15)
ax.spines[['top', 'right']].set_visible(False)

Related

How to plot 4 figures per page with pdfpages in matplotlib?

I have the code below which produces the output I want.
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
plt.style.use('ggplot')
%matplotlib inline
data = dict({'Variable_Grouping':['Type_A', 'Type_A', 'Type_A', 'Type_C', 'Type_C', 'Type_C', 'Type_C', 'Type_D', 'Type_D', 'Type_E', 'Type_E', 'Type_E', 'Type_H', 'Type_H'], 'Variable':['a1', 'a2', 'a3', 'c1', 'c2', 'c3', 'c4', 'd1', 'd2', 'e1', 'e2', 'e3', 'h1', 'h2'], 'Count':[5, 3, 8, 4, 3, 9, 5, 3, 8, 5, 3, 8, 5, 3],'Percent':[0.0625, 0.125, 0.4375, 0.0, 0.125, 0.5, 0.02, 0.125, 0.03, 0.0625, 0.05, 0.44, 0.07, 0.023]})
to_plot = pd.DataFrame(data)
g = sns.FacetGrid(to_plot, col='Variable_Grouping', col_wrap = 2, sharex=False, sharey = False, height = 5, aspect = 1, margin_titles=True)
g=g.map(plt.bar, "Variable","Count").add_legend()
for ax, (_, subdata) in zip(g.axes, to_plot.groupby('Variable_Grouping')):
ax2=ax.twinx()
subdata.plot(x='Variable',y='Percent', ax = ax2, legend=True, color='g', label = 'Percent')
ax2.set_ylabel('Percent')
ax2.grid(False)
for ax in g.axes.flatten():
ax.tick_params(labelbottom=True, labelrotation = 90)
g.fig.suptitle('Analysis', fontsize=16, fontweight = 'demibold', y = 1.02)
g.fig.subplots_adjust(hspace=0.3, wspace=0.7, right = 0.9)
plt.show();
Now I am using matplotlib.backends.backend_pdf to plot the figures in pdf. I want 4 figures per page.
with PdfPages('Analysis.pdf') as pdf:
g = sns.FacetGrid(to_plot, col='Variable_Grouping', col_wrap = 2, sharex=False, sharey = False, height = 5, aspect = 1, margin_titles=True)
g=g.map(plt.bar, "Variable","Count").add_legend()
for ax, (_, subdata) in zip(g.axes, to_plot.groupby('Variable_Grouping')):
ax2=ax.twinx()
subdata.plot(x='Variable',y='Percent', ax = ax2, legend=True, color='g', label = 'Percent')
ax2.set_ylabel('Percent')
ax2.grid(False)
for ax in g.axes.flatten():
ax.tick_params(labelbottom=True, labelrotation = 90)
g.fig.suptitle('Analysis', fontsize=16, fontweight = 'demibold', y = 1.02)
g.fig.subplots_adjust(hspace=0.3, wspace=0.7, right = 0.9)
pdf.savefig(bbox_inches = 'tight')
plt.close();
The code above gives me all the plots in a single page as expected.
def grouper(iterable, n, fillvalue=None):
from itertools import zip_longest
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue=fillvalue)
if len(to_plot['Variable_Grouping'].unique()) < 4:
N_plots_per_page =len(to_plot['Variable_Grouping'].unique())
elif len(to_plot['Variable_Grouping'].unique()) >= 4:
N_plots_per_page = 4
with PdfPages('Analysis.pdf') as pdf:
for cols in grouper(to_plot['Variable_Grouping'].unique(), N_plots_per_page):
g = sns.FacetGrid(to_plot, col='Variable_Grouping', col_wrap = 2, sharex=False, sharey = False, height = 5, aspect = 1, margin_titles=True)
g=g.map(plt.bar, "Variable","Count").add_legend()
for ax, (_, subdata) in zip(g.axes, to_plot.groupby('Variable_Grouping')):
ax2=ax.twinx()
subdata.plot(x='Variable',y='Percent', ax = ax2, legend=True, color='g', label = 'Percent')
ax2.set_ylabel('Percent')
ax2.grid(False)
for ax in g.axes.flatten():
ax.tick_params(labelbottom=True, labelrotation = 90)
g.fig.suptitle('Analysis', fontsize=16, fontweight = 'demibold', y = 1.02)
g.fig.subplots_adjust(hspace=0.3, wspace=0.7, right = 0.9)
pdf.savefig(bbox_inches = 'tight')
plt.show()
plt.close();
In the code above I have tried using the grouper function (https://docs.python.org/3/library/itertools.html#itertools-recipes). This was also mentioned in Export huge seaborn chart into pdf with multiple pages and this repeats all the graphs in all the pages.
I wanted to enquire if there is an easy way to get 4 graphs per page or what's wrong with the above code I used using the grouper function which is repeating the graphs. Any help will be appreciated. Thanks.
The problem is, even you try to get the number of plots per page, you take the whole data inside the loop to plot with to_plot. You need to filter your to_plot with the cols you get by your grouper and your code will work.
The only changes I made is create the variable data_per_page and replace that with to_plot inside of sns.FaceGrid and in for ax, (_,subdata) in zip(...).
with PdfPages('Analysis.pdf') as pdf:
for cols in grouper(to_plot['Variable_Grouping'].unique(), N_plots_per_page):
data_per_page = to_plot.loc[to_plot['Variable_Grouping'].isin(cols)]
g = sns.FacetGrid(data_per_page, col='Variable_Grouping', col_wrap = 2, sharex=False, sharey = False, height = 5, aspect = 1, margin_titles=True)
g=g.map(plt.bar, "Variable","Count").add_legend()
for ax, (_,subdata) in zip(g.axes, data_per_page.groupby(['Variable_Grouping'])):
ax2=ax.twinx()
subdata.plot(x='Variable',y='Percent', ax = ax2, legend=True, color='g', label = 'Percent')
ax2.set_ylabel('Percent')
ax2.grid(False)
for ax in g.axes.flatten():
ax.tick_params(labelbottom=True, labelrotation = 90)
g.fig.suptitle('Analysis', fontsize=16, fontweight = 'demibold', y = 1.02)
g.fig.subplots_adjust(hspace=0.3, wspace=0.7, right = 0.9)
pdf.savefig(bbox_inches='tight')
plt.show()
plt.close()
As a result I get a pdf with 2 pages, on the first there are 4 plots, and on the second only 1.

Create stacked bar with matplotlib

I have data displayed in the following format:
values = np.array([10, 12,13, 5,20], [30, 7, 10, 25,2], [10, 12,13, 5,20]])
And I want to create a straight-up stacked bar chart like the following figure. Each element in the array belongs to a stacked bar.
I have searched to see how can I do this with matplotlib, but unfortunately, I still haven't found a way to do it. How can I do this?
AFAIK, there is now straightforward way to do it. You need to calculate exact position of bars yourself and then normalize it.
import numpy as np
import matplotlib.pyplot as plt
values = np.array([[10, 12,13, 5,20], [30, 7, 10, 25,2], [10, 12,13, 5,20]])
values_normalized = values/np.sum(values, axis=0)
bottom_values = np.cumsum(values_normalized, axis=0)
bottom_values = np.vstack([np.zeros(values_normalized[0].size), bottom_values])
text_positions = (bottom_values[1:] + bottom_values[:-1])/2
r = [0, 1, 2, 3, 4] # position of the bars on the x-axis
names = ['A', 'B', 'C', 'D', 'E'] # names of groups
colors = ['lightblue', 'orange', 'lightgreen']
for i in range(3):
plt.bar(r, values_normalized[i], bottom=bottom_values[i], color=colors[i], edgecolor='white', width=1, tick_label=['a','b','c','d','e'])
for xpos, ypos, yval in zip(r, text_positions[i], values[i]):
plt.text(xpos, ypos, "N=%d"%yval, ha="center", va="center")
# Custom X axis
plt.xticks(r, names, fontweight='bold')
plt.xlabel("group")
plt.show()
There is a source that tells how to add text on top of bars. I'm a bit in a hurry right now so I hope this is useful and I'll update my answer next day if needed.
I've updated my answer. Adding text on top of the bars is tricky, it requires some calculations of their vertical positions.
Btw, I have refactored the most of code that is in a link I shared.
Python 3.8
matplotlib 3.3.1
numpy 1.19.1
Chat Result
import matplotlib.pyplot as plt
import numpy as np
values = np.array([[10, 12, 13, 5, 20], [30, 7, 10, 25, 2], [10, 12, 13, 5, 20]])
row, column = values.shape # (3, 5)
x_type = [x+1 for x in range(column)]
ind = [x for x, _ in enumerate(x_type)]
values_normalized = values/np.sum(values, axis=0)
value1, value2, value3 = values_normalized[0,:], values_normalized[1,:], values_normalized[2,:]
# Create figure
plt.figure(figsize=(8, 6))
plt.bar(ind, value1, width=0.8, label='Searies1', color='#5B9BD5')
plt.bar(ind, value2, width=0.8, label='Searies2', color='#C00000', bottom=value1)
plt.bar(ind, value3, width=0.8, label='Searies3', color='#70AD47', bottom=value1 + value2)
# Show text
bottom_values = np.cumsum(values_normalized, axis=0)
bottom_values = np.vstack([np.zeros(values_normalized[0].size), bottom_values])
text_positions = (bottom_values[1:] + bottom_values[:-1])/2
c = list(range(column))
for i in range(3):
for xpos, ypos, yval in zip(c, text_positions[i], values[i]):
plt.text(xpos, ypos, yval, horizontalalignment='center', verticalalignment='center', color='white')
plt.xticks(ind, x_type)
plt.legend(loc='center', bbox_to_anchor=(0, 1.02, 1, 0.1), handlelength=1, handleheight=1, ncol=row)
plt.title('CHART TITLE', fontdict = {'fontsize': 16,'fontweight': 'bold', 'family': 'serif'}, y=1.1)
# Hide y-axis
plt.gca().axes.yaxis.set_visible(False)
plt.show()

Can't change width when annotating bar

I'm creating a stacked horizontal bar graph with 3 segments using the code below:
import matplotlib.pyplot as plt
import numpy as np
def create_stacked_hbar(data):
fig, ax = plt.subplots(figsize=(10, 10))
ylabels = list(data.keys())
labels = ['a', 'b', 'c', 'd', 'e', 'f']
c = []
v = []
for key, val in data.items():
c.append(key)
v.append(val)
v = np.array(v)
print(v)
plt.barh(range(len(c)), v[:,0], width=1, color='red',
edgecolor='w',linewidth=2, tick_label=ylabels, label=labels[0])
plt.barh(range(len(c)), v[:,1], width=1, left=v[:,0], color='orange',
edgecolor='w', linewidth=2, label=labels[1])
plt.barh(range(len(c)), v[:,2], width=1, left=(v[:,0]+v[:,1]), color='yellow',
edgecolor='w', linewidth=2, label=labels[2])
for p in ax.patches:
left, bottom, width, height = p.get_bbox().bounds
if width != 0.0:
ax.annotate(str(int(width)), xy=(left+width/2, bottom+height/2),
ha='center', va='center', size = 12)
plt.legend(bbox_to_anchor=(0, -0.15), loc=3, prop={'size': 14}, frameon=False)
plt.yticks(np.arange(len(ylabels)), ylabels)
plt.show()
data = {'A': [8, 7, 2], 'B': [0, 2, 0],
'C': [3, 2, 4], 'D': [0, 4, 0],
'E': [0, 1, 1], 'F': [0, 1, 0],
'G': [0, 0, 0]}
create_stacked_hbar(data)
The issue is that in attempting to set width = 1 in the bars throws a type error:
TypeError: <lambda>() got multiple values for argument 'width'
removing width allows to the code to work, but I do need to increase the width of the bars in the chart. I suspect this has to do with the annotation code I use in this case. Does anyone have any suggestions on getting around this?
Also note I am unable to use the "dataframe.plot.barh(data, stacked=True)" method via pandas to generate this chart.
You are making a horizontal bar plot, the width parameter corresponds to the data, so in your example you are passing both v[:,0] and 1 as width. If you are trying to specify the height because you do not desire whitespace between the bars you need to set height=1, consider this example:
import numpy as np
import matplotlib.pyplot as plt
# Seeded for reproducing
np.random.seed(1)
v1 = abs(np.random.randn(10))
v2 = abs(np.random.randn(10))
v3 = abs(np.random.randn(10))
c = range(10)
plt.title("Sample bar plot")
plt.barh(c, v1, height=1, alpha=0.8, color='r')
plt.barh(c, v2, height=1, left=v1, alpha=0.8, color='b')
plt.barh(c, v3, height=1, left=v1+v2, alpha=0.8, color='g')
plt.show()
This will give you
    
Where removing the height=1 specification would give you
    

Showing two data sets of `ax2.set_xticklabels` in a subplot

I have an upper subplot that shows two data sets: orange and green.
The following code shows the xtick labels of the green data set,
in the ax2 axis of the upper subplot (axis labelled as X2 in the figure):
ind_pos_Pd3 = [0, 4, 8, 12, 16]
axarr[0].set_xticks(X1_green[ind_pos_Pd3])
ax2.set_xticks(X1_green[ind_pos_Pd3])
ax2.set_xticklabels(["%.2f" % i for i in X2_green[ind_pos_Pd3]])
On the contrary, the following code shows the the xtick labels of the orange data set,
in the ax2 axis of the upper subplot (axis labelled as X2 in the figure):
ind_pos_Bd3 = [0, 4, 8, 12, 16, 20, 24, 28, 32]
axarr[0].set_xticks(X1_orange[ind_pos_Bd3])
ax2.set_xticks(X1_orange[ind_pos_Bd3])
ax2.set_xticklabels(["%.2f" % i for i in X2_orange[ind_pos_Bd3]])
Is there a way to "mix" both schemes and achieve a ax2 axis divided into two panels: the "lower panel" is filled with the labels from the green data set, and the "upper panel" is filled with the labels from the orange data set ?
Something like:
I was thinking on this pseudo-code:
ind_pos_Bd3 = [0, 4, 8, 12, 16, 20, 24, 28, 32]
ind_pos_Pd3 = [0, 4, 8, 12, 16]
axarr[0].set_xticks(X1_orange[ind_pos_Bd3])
axarr[0].set_xticks(X1_green[ind_pos_Pd3])
ax2.set_xticks(X1_green[ind_pos_Pd3])
ax2.set_xticklabels(["%.2f \n %.2f" % i for i in zip(X2_orange[ind_pos_Bd3], X1_green[ind_pos_Pd3]])
However, the "%.2f \n %.2f" scheme is not working.
Minimal working example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
X1_orange = np.array([ 10., 30.1, 50.2, 70.3, 90.4, 110.51, 130.61, 150.71, 170.81,
190.91, 211.01, 231.11, 251.21, 271.31, 291.41, 311.52, 331.62, 351.72,
371.82, 391.92, 412.02, 432.12, 452.22, 472.32, 492.42, 512.53, 532.63,
552.73, 572.83, 592.93, 613.03, 633.13, 653.23])
X2_orange = np.array([ 2.56691976, 2.5781089 , 2.59624855, 2.62275805, 2.64568568, 2.66607658,
2.6959714 , 2.7231501 , 2.75529391, 2.78894345, 2.81573712, 2.84711104,
2.88437499, 2.9191375 , 2.95033337, 2.99340594, 3.02206115, 3.06383265,
3.08649135, 3.12707204, 3.18500195, 3.24240915, 3.25965166, 3.36137181,
3.35468811, 3.42661704, 3.46254097, 3.61136855, 3.65505401, 3.89043407,
3.80421353, 3.79380128, 4.01570509])
X1_green = np.array([ 10. , 30.1 , 50.2 , 70.3 , 90.4 , 110.51, 130.61, 150.71, 170.81,
190.91, 211.01, 231.11, 251.21, 271.31, 291.41, 311.52, 331.62])
X2_green = np.array([ 1.9894731 , 2.00259207, 2.01875725, 2.04333825, 2.07700656, 2.09629866,
2.14727031, 2.17488234, 2.2499103 , 2.2698862 , 2.31607409, 2.41452705,
2.50847008, 2.61117044, 2.70657103, 3.25283819, 3.31585812])
Y0_orange = np.array([-1.87483583, -1.82358431, -1.78627169, -1.75242213, -1.7299256 , -1.69363695,
-1.64623615, -1.59656948, -1.56967972, -1.55058869, -1.51874314, -1.45673839,
-1.40554361, -1.39904184, -1.35322104, -1.33906865, -1.30865871, -1.28099683,
-1.24897269, -1.19802619, -1.17268543, -1.13523614, -1.09290541, -1.05642197,
-1.00189406, -0.95390527, -0.90890049, -0.82522042, -0.76334378, -0.64504498,
-0.62782754, -0.47358849, -0.49772039])
Y0_green = np.array([-1.97113854, -1.92908192, -1.84404067, -1.75333855, -1.67575628, -1.58010168,
-1.48746063, -1.40770641, -1.31802444, -1.2302243 , -1.14927205, -1.04251178,
-0.91661452, -0.82924669, -0.65075739, -0.37715382, -0.21349827])
##### THREE SUBPLOTS::
###### Y0 plot:
f, axarr = plt.subplots(3, sharex=True, figsize=(11,5))
axarr[0].plot(X1_orange, Y0_orange, linestyle='--', marker="o", markersize=6, color='orange')
axarr[0].plot(X1_green, Y0_green, linestyle='--', marker="o", markersize=6, color='green')
axarr[0].set_ylabel('Y0', fontsize=15)
# Create a new axis:
axarr[0].grid()
ax2 = axarr[0].twiny()
# Make the ax1-ticks and ax1-tick-labels match the line color (blue):
axarr[0].tick_params('x', colors='blue')
# Make the ax2-ticks and ax2-tick-labels match the red color:
# this only controls the font and color of label
ax2.set_xlabel('x2', fontsize=14, color='red')
# this also adds the numbers on top of the tics,
# but sets the colors of the tics
ax2.tick_params('x', colors='orange')
# Set xlimits of ax2 the same as ax1
ax2.set_xlim(axarr[0].get_xlim())
# Set ticks at desired position
ind_pos_Bd3 = [0, 4, 8, 12, 16, 20, 24, 28, 32]
axarr[0].set_xticks(X1_orange[ind_pos_Bd3])
ax2.set_xticks(X1_orange[ind_pos_Bd3])
ax2.set_xticklabels(["%.2f" % i for i in X2_orange[ind_pos_Bd3]])
#ind_pos_Pd3 = [0, 4, 8, 12, 16]
#axarr[0].set_xticks(X1_green[ind_pos_Pd3])
#ax2.set_xticks(X1_green[ind_pos_Pd3])
#ax2.set_xticklabels(["%.2f" % i for i in X2_green[ind_pos_Pd3]])
# Just to align the Delta_V ylabel to the other 3 ylabel's
labelx = -0.075 # very close to the plot
axarr[0].yaxis.set_label_coords(labelx, 0.5, transform=None)
##### Y1 plot:
#f, axarr = plt.subplots(3, sharex=True)
axarr[1].set_ylabel('Y1', fontsize=15)
# Create a new axis:
axarr[1].grid()
ax2 = axarr[1].twiny()
# Make the ax1-ticks and ax1-tick-labels match the line color (blue):
axarr[1].tick_params('x', colors='blue')
# Make the ax2-ticks and ax2-tick-labels match the red color:
ax2.tick_params('x', colors='red')
ax2.set_xticklabels([]) # disable the tic labels
# Set xlimits of ax2 the same as ax1
ax2.set_xlim(axarr[1].get_xlim())
# Set ticks at desired position
ind_pos_Bd3 = [0, 4, 8, 12, 16, 20, 24, 28, 32]
axarr[1].set_xticks(X1_orange[ind_pos_Bd3])
ax2.set_xticks(X1_orange[ind_pos_Bd3])
# Label ticks of ax2 with values from X2
#ax2.set_xticklabels(["%.2f" % i for i in P])
axarr[1].yaxis.set_major_formatter(mtick.FormatStrFormatter('%1.e'))
#### Y2 plot:
axarr[2].set_ylabel('Y2', fontsize=15)
# Create a new axis:
axarr[2].grid()
ax2 = axarr[2].twiny()
# Make the ax1-ticks and ax1-tick-labels match the line color (blue):
axarr[2].tick_params('x', colors='blue')
# Make the ax2-ticks and ax2-tick-labels match the red color:
axarr[2].set_xlabel('X1', fontsize=14, color='blue')
# this also adds the numbers on top of the tics,
# but sets the colors of the tics
axarr[2].tick_params('x', colors='blue')
# Make the ax2-ticks and ax2-tick-labels match the red color:
ax2.tick_params('x', colors='red')
ax2.set_xticklabels([]) # disable the tic labels
# Set xlimits of ax2 the same as ax1
ax2.set_xlim(axarr[2].get_xlim())
# Set ticks at desired position
ind_pos_Bd3 = [0, 4, 8, 12, 16, 20, 24, 28, 32]
axarr[2].set_xticks(X1_orange[ind_pos_Bd3])
ax2.set_xticks(X1_orange[ind_pos_Bd3])
# Label ticks of ax2 with values from X2
axarr[2].yaxis.set_major_formatter(mtick.FormatStrFormatter('%1.e'))
plt.show()
The trick is to add a new twin axis for each row of tick labels you want.
You can then position them properly setting the pad parameter in tick_params.
Allow me to make your example a little smaller so it's easier to follow
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
fig, ax = plt.subplots(figsize=(12,2))
np.random.seed(42)
x0 = np.arange(50)
y0 = np.random.rand(50)
x1 = np.arange(30)
y1 = np.random.rand(30) + 0.7
ax.plot(x0, y0, ls='--', marker='o', ms=6, color='orange')
ax.grid(True)
ax.tick_params('x', colors='blue')
ax.set_xlabel('x1', fontsize=14, color='blue')
ax.set_ylabel('y', fontsize=14)
tax1 = ax.twiny()
tax1.set_xlim(ax.get_xlim())
tax1.tick_params('x', colors='orange', pad=14)
tax1.set_xlabel('x2', fontsize=14, color='red', labelpad=6)
tax2 = ax.twiny()
tax2.set_xlim(ax.get_xlim())
tax2.plot(x1, y1, ls='--', marker='o', ms=6, color='green')
tax2.tick_params('x', colors='green')
tax2.set_xticks([0, 10, 20, 30])
fig.savefig('img.png', pad_inches=0, bbox_inches='tight')
As you pointed out in the comments an issue with this plot is that you get green and orange tick lines and you would like to have them in red just like the x2 label.
You can hide the green tick lines setting length=0 in tick_params, draw the orange plot ticks in red first and then set labels color to orange.
Here's the modified code
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
fig, ax = plt.subplots(figsize=(12,2))
np.random.seed(42)
x0 = np.arange(50)
y0 = np.random.rand(50)
x1 = np.arange(30)
y1 = np.random.rand(30) + 0.7
ax.plot(x0, y0, ls='--', marker='o', ms=6, color='orange')
ax.grid(True)
ax.tick_params('x', colors='blue')
ax.set_xlabel('x1', fontsize=14, color='blue')
ax.set_ylabel('y', fontsize=14)
tax1 = ax.twiny()
tax1.set_xlim(ax.get_xlim())
tax1.tick_params('x', colors='red', pad=14)
tax1.set_xlabel('x2', fontsize=14, color='red', labelpad=6)
[x.set_color("orange") for x in tax1.get_xticklabels()]
tax2 = ax.twiny()
tax2.set_xlim(ax.get_xlim())
tax2.plot(x1, y1, ls='--', marker='o', ms=6, color='green')
tax2.tick_params('x', colors='green', pad=6, length=0)
tax2.set_xticks([0, 10, 20, 30])
fig.savefig('img2.png', pad_inches=0, bbox_inches='tight')

Plotting three sub plot bar chart with correct labelling

I cannot seem to make the plots work with labels correctly. The plots work in terms of generating three sub plots bar charts. But what I want to label each and every plot (3) with labels cr_lst. How do I ensure that I can label each of these bars with cr_lst and on each bar.
plt.figure(0)
width = 0.35 # the width of the bars
cr_lst = ['A', 'B', 'C', 'D']
A_lst = [1, 2, 3, 4]
B_lst = [2, 2, 6, 7]
A_lst = [8, 8, 6, 7]
ind = np.arange(len(A_lst)) # the x locations for the groups
f, axarr = plt.subplots(3, sharex=True)
axarr[0].set_title('Three plots\n')
axarr[0].set_ylabel('A')
axarr[1].set_ylabel('B')
axarr[2].set_ylabel('C')
axarr[0].set_ylim(ymin=0.001,ymax=max(A_lst)*1.10)
axarr[1].set_ylim(ymin=0.001,ymax=max(B_lst)*1.10)
axarr[2].set_ylim(ymin=0.001,ymax=max(B_lst)*1.10)
axarr[0].grid()
axarr[1].grid()
axarr[2].grid()
rects1 = axarr[0].bar(ind, A_lst, width, color='r', linewidth=1,alpha=0.8, label=cr_lst)
rects2 = axarr[1].bar(ind, B_lst, width, color='y', linewidth=1,alpha=0.8, label=cr_lst)
rects3 = axarr[2].bar(ind, C_lst, width, color='blue', linewidth=1, alpha=0.8, label=cr_lst)
plt.savefig("ByC.png")
I'd like to have the labels shown on the x-axis.
This will get you the labels under each bar on every axes:
width = 0.35 # the width of the bars
cr_lst = ['A', 'B', 'C', 'D']
x = range(len(cr_lst)) # the x locations for the groups
A_lst = [1, 2, 3, 4]
B_lst = [2, 2, 6, 7]
C_lst = [8, 8, 6, 7]
f, axarr = plt.subplots(3, sharex=False)
axarr[0].set_title('Three plots\n')
axarr[0].set_ylabel('A')
axarr[1].set_ylabel('B')
axarr[2].set_ylabel('C')
axarr[0].set_ylim(ymin=0.001,ymax=max(A_lst)*1.10)
axarr[1].set_ylim(ymin=0.001,ymax=max(B_lst)*1.10)
axarr[2].set_ylim(ymin=0.001,ymax=max(B_lst)*1.10)
axarr[0].grid()
axarr[1].grid()
axarr[2].grid()
rects1 = axarr[0].bar(x, A_lst, width, color='r', align='center', linewidth=1,alpha=0.8)
rects2 = axarr[1].bar(x, B_lst, width, color='y', align='center', linewidth=1,alpha=0.8)
rects3 = axarr[2].bar(x, C_lst, width, color='blue', align='center', linewidth=1, alpha=0.8)
for ax in axarr:
ax.set_xticks(x)
ax.set_xticklabels(cr_lst)
plt.savefig("ByC.png")
Note that share=False in plt.subplots. If you set it to True it hides all other labels but the lowest ax.
Also note the use of align='center' in .bar().
This yields:

Categories

Resources