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:
Related
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)
I am working with a plot that contains an uneven length of data. I created another group of females (green bars), and I would like to label these two female groups F1 and F2.
Here is my code:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
labels = ['G1', 'G2', 'G3', 'G4']
labels2 = ['F1', 'F2']
male = [1, 3, 10, 20]
female = [2, 7]
female_2 = [3, 11]
x_male = np.arange(len(male))
x_female = np.arange(len(female))
offset_male = np.zeros(len(male))
offset_female = np.zeros(len(female))
shorter = min(len(x_male), len(x_female))
width = 0.25 # the width of the bars
offset_male[:shorter] = width/2
offset_female[:shorter] = width/2
fig, ax = plt.subplots()
rects1 = ax.bar(x_male - offset_male, male, width, label='male')
rects2 = ax.bar(x_female + offset_female, female, width, label='female')
rects3 = ax.bar(x_female + 3 * offset_female, female_2, width, label='female')
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_xticks(x_male)
ax.set_xticklabels(labels)
ax.legend()
fig.tight_layout()
plt.show()
Do you have any idea how I can do it?
blend all ticks together
ax.set_xticks(list(x_male)+list(x_female + 3 * offset_female))
ax.set_xticklabels(labels+labels2)
I have 2 columns, sharing the same x-axis values, that I want to connect using vertical lines. This is the desired effect:
I was able to implement it in matplotlib:
for i, row in df.iterrows():
ax.plot([row['x']]*2, row[['y1', 'y2']], color='grey', lw=1, zorder=0, alpha=0.5)
How can I achieve this in Bokeh?
df = pd.DataFrame(np.random.normal(0, 5, (10, 2)), columns=['x','y'])
df_2 = df.copy()
df_2['y'] = df_2['y'] - 5
source = ColumnDataSource(df)
source_2 = ColumnDataSource(df_2)
myplot = figure(plot_width=600, plot_height=400, tools='hover,box_zoom,box_select,crosshair,reset')
myplot.circle('x', 'y', size=7, fill_alpha=0.5, source=source)
myplot.circle('x', 'y', size=7, fill_alpha=0.5, color='orange', source=source_2)
show(myplot, notebook_handle=True);
Bokeh code result:
Underlying data example: Y2 will always be larger than Y1.
You should use the segment glyph method:
from bokeh.plotting import figure, show
x = [1, 2, 3, 4, 5]
y1 = [6, 7, 2, 4, 5]
y2 = [10, 12, 11, 14, 13]
p = figure(plot_height=350)
p.segment(x, y1, x, y2, color="lightgrey", line_width=3)
p.circle(x, y1, color="blue", size=20)
p.circle(x, y2, color="red", size=20)
show(p)
This code passes the data directly to the glyph methods, but it would also be sensible to put everything in one ColumnDataSource that gets shared for all the glyphs.
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
Is there any way to center the yticklabels in the middle of a horizontal barplot that has both negative and positive values? I want the names of various y-values (stored in a list called 'names') to appear in the middle of a barplot (centered at x=0). Is this possible?
Demo code:
names = [1, 2, 3]
fig, axs = plt.subplots(3,1, figsize=(12, 24))
ind = np.arange(len(names)) # the x locations for the groups
width = 0.35 # the width of the bars
axs[0].barh(ind, [3, 4, 5], width, color='red', label='demo')
axs[0].set(yticks=ind + width, yticklabels=names)
plt.show()
You were close. You just need to use align=center and then rename the tick-labels as you are doing without shifting the ticks by 0.5. I replaced coef_names by names because the former was not defined in your provided code.
names = [1, 2, 3]
fig, axs = plt.subplots(3,1, figsize=(12, 24))
ind = np.arange(len(names)) # the x locations for the groups
width = 0.35 # the width of the bars
axs[0].barh(ind, [3, 4, 5], width, color='red', align='center',label='demo')
axs[0].set(yticks=ind , yticklabels=names)