Problem with handling multiple legends in subplots when you use plt.twinx() - python

I'm struggling to pass the list of my subplots with different scales on sides using plt.twinx() in each subplot to show all labels in a single legend box, but I get the following error:
AttributeError: 'list' object has no attribute 'get_label'
I have tried the following:
import matplotlib
print('matplotlib: {}'.format(matplotlib.__version__))
#matplotlib: 3.2.2
#Generate data
import pandas as pd
df = pd.DataFrame(dict(
Date = [1,2,3],
Male = [10,20,30],
Female = [20,30,10],
Others = [700,500,200]
))
print(df)
# Date Male Female Others
#0 1 10 20 700
#1 2 20 30 500
#2 3 30 10 200
# Create pandas stacked line plot
import numpy as np
import matplotlib.pyplot as plt
Userx = 'foo'
fig, ax = plt.subplots(nrows=2, ncols=1 , figsize=(20,10))
plt.subplot(211)
linechart1 = plt.plot(df['Date'], df['Male'], color='orange', marker=".", markersize=5, label=f"Leg1 for {Userx}" ) #, marker="o"
scatterchart2 = plt.scatter(df['Date'], df['Female'], color='#9b5777', marker='d', s=70 , label=f"Leg2 for {Userx}" )
plt.legend( loc='lower right', fontsize=15)
plt.ylabel('Scale1', fontsize=15)
plt.twinx()
linechart3, = plt.plot(df['Date'], df['Others'], color='black', marker=".", markersize=5, label=f"Leg3 for {Userx}" ) #, marker="o"
#lns123 = [linechart1,scatterchart2,linechart3]
#plt.legend(handles=lns123, loc='best', fontsize=15)
plt.legend( loc='best', fontsize=15)
plt.ylabel('Scale2', fontsize=15)
plt.xlabel('Timestamp [24hrs]', fontsize=15, color='darkred')
plt.ticklabel_format(style='plain')
#lns123 = [linechart1,scatterchart2,linechart3]
#plt.legend(handles=lns123, loc='best', fontsize=15)
#plt.legend(handles=[linechart1[0],scatterchart2[0],linechart3[0]], loc='best', fontsize=15)
plt.subplot(212)
barchart1 = plt.bar(df['Date'], df['Male'], color='green', label=f"Leg1 for {Userx}" , width=1, hatch='o' ) #, marker="o"
barchart2 = plt.bar(df['Date'], df['Female'], color='blue', label=f"Leg2 for {Userx}" , width=0.9, hatch='O') #, marker="o"
plt.ticklabel_format(style='plain')
plt.ylabel('Scale1', fontsize=15)
plt.twinx()
barchart3 = plt.bar(df['Date'], df['Others'], color='orange', label=f"Leg3 for {Userx}", width=0.9 , hatch='/', alpha=0.1 ) #, marker="o"
plt.ylabel('Scale2', fontsize=15)
plt.ticklabel_format(style='plain')
plt.xlabel('Timestamp [24hrs]', fontsize=15, color='darkred')
bar123 = [barchart1,barchart2,barchart3]
plt.legend(handles=bar123, loc='best', fontsize=15)
#plt.show(block=True)
plt.show()
I have tried the following solutions unsuccessfully:
ref1
ref2
using ,
How do I make a single legend for many subplots? This is not my answer or I couldn't figure it out how I can handle the labels with plt.twinx() since it gathered all legends of subplots to a single legend box, while I want to include/integrate into individual legends for each subplot as I marked in the photo.
currently, I just use/duplicate plt.legend( loc='lower right', fontsize=15) in subplot(211):
plt.subplot(211)
chart1 = ...
chart2 = ...
#here
plt.twinx()
chart3, = ...
#here
plt.subplot(212)
barchart1 = ...
barchart2 = ...
plt.twinx()
barchart3, = ...
bar123 = [barchart1,barchart2,barchart3]
plt.legend(handles=bar123, loc='best', fontsize=15)
and get the below output:
The interesting is I don't have this problem for bar plots plt.bar().

In the spirit of DRY, based on I could figure out to solve the problem using get_legend_handles_labels() to "keep track of lines and labels" ref1 & ref2.
# Create pandas stacked line plot
import numpy as np
import matplotlib.pyplot as plt
Userx = 'foo'
#fig, ax = plt.subplots(nrows=2, ncols=1 , figsize=(20,10))
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(20,10))
#plt.subplot(111)
ax1 = plt.subplot(2, 1, 1)
ax1.plot(df['Date'], df['Male'], color='orange', marker=".", markersize=5, label=f"Leg1 for {Userx}" ) #, marker="o"
ax1.scatter(df['Date'], df['Female'], color='#9b5777', marker='d', s=70 , label=f"Leg2 for {Userx}" )
#ax1.ylabel('Scale1', fontsize=15)
ax1.set_ylabel("Scale1", fontsize=15)
ax1.ticklabel_format(style='plain')
ax11 = ax1.twinx()
ax11.plot(df['Date'], df['Others'], color='black', marker=".", markersize=5, label=f"Leg3 for {Userx}" ) #, marker="o"
#ax1.ylabel('Scale2', fontsize=15)
ax11.set_ylabel("Scale2", fontsize=15)
ax11.ticklabel_format(style='plain')
ax1.legend( loc='best', fontsize=15)
ax1.set_xlabel('Timestamp [24hrs]', fontsize=15)
#plt.subplot(212)
ax2 = plt.subplot(2, 1, 2)
ax2.bar(df['Date'], df['Male'], color='green', label=f"Leg1 for {Userx}", width=1 , hatch='o' ) #, marker="o"
ax2.bar(df['Date'], df['Female'], color='blue', label=f"Leg2 for {Userx}", width=0.9 , hatch='O' ) #, marker="o"
ax2.ticklabel_format(style='plain')
#ax2.ylabel('Scale2', fontsize=15)
ax2.set_ylabel("Scale2", fontsize=15)
ax22 = ax2.twinx()
ax22.bar(df['Date'], df['Others'], color='orange', label=f"Leg3 for {Userx}", width=0.9 , hatch='/', alpha=0.1 ) #, marker="o"
#ax2.ylabel('Scale1', fontsize=15)
ax22.set_ylabel("Scale1", fontsize=15)
ax22.ticklabel_format(style='plain')
#ax2.xlabel('Timestamp [24hrs]', fontsize=15, color='darkred')
ax2.set_xlabel('Timestamp [24hrs]', fontsize=15, color='darkred')
ax2.legend( loc='best', fontsize=15)
# ask matplotlib for the plotted objects and their labels
lines1, labels1 = ax1.get_legend_handles_labels()
lines11, labels11 = ax11.get_legend_handles_labels()
ax1.legend(lines1 + lines11, labels1 + labels11, loc='best', fontsize=15)
lines2, labels2 = ax2.get_legend_handles_labels()
lines22, labels22 = ax22.get_legend_handles_labels()
ax2.legend(lines2 + lines22, labels2 + labels22, loc='best', fontsize=15)
#plt.show(block=True)
plt.show()
It seems there is no easy way to solve the problem when you use 2D Lineplot plt.subplot(111) with plt.twinx() together, but it works for bar chatrs! So it needs to use ax1 = plt.subplot(2, 1, 1) and ax2 = plt.subplot(2, 1, 2) structure to solve the problem.

Related

Python Matplotlib - Secondary axis multiple legends

I have a plot that has a secondary axis. Axis 1 has two data sets plotted against it. Axis 2 has one data set.
I can get two legends (one from Axis 1 and one from Axis 2) like how I want them - one below the other outside the plot to the right.
I want the second data set from Axis 1 have its legend BELOW the above two legends. But it shows up besides the two.
How can I get this to work?
Below is my code:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(111)
t = np.arange(0.01, 10.0, 0.01)
s1 = np.exp(t)
ax1.plot(t, s1, 'b-',label='data1')
ax1.set_xlabel('time (s)')
ax1.legend(loc='lower left', bbox_to_anchor= (1.1, 0.7), ncol=2,
borderaxespad=0, frameon=False)
ax2 = ax1.twinx()
s2 = np.sin(2*np.pi*t)
ax2.plot(t, s2, 'r',label='data2')
ax2.legend(loc='lower left', bbox_to_anchor= (1.1, 0.6), ncol=2,
borderaxespad=0, frameon=False)
data3 = [10000]*len(t)
ax1.plot(t,data3,'k--',label='data3')
ax1.legend(loc='lower left', bbox_to_anchor= (1.1, 0.5), ncol=2,
borderaxespad=0, frameon=False)
plt.show()
When I change the y-values for bbox_to_anchor, instead of appearing in a column with the other two legends, 'data3' shows up in a row with either one of the two legends.
Thank you
R
Change ncol=2 to ncol=1 to constrain the legend items to the same column.
import numpy as np
import matplotlib.pyplot as plt
# constrained layout worked best for me, but you can change it back
fig = plt.figure(constrained_layout=True)
ax1 = fig.add_subplot(111)
t = np.arange(0.01, 10.0, 0.01)
s1 = np.exp(t)
ax1.plot(t, s1, 'b-',label='data1')
ax1.set_xlabel('time (s)')
ax1.legend(loc='lower left', bbox_to_anchor= (1.1, 0.7), ncol=1,
borderaxespad=0, frameon=False)
ax2 = ax1.twinx()
s2 = np.sin(2*np.pi*t)
ax2.plot(t, s2, 'r',label='data2')
ax2.legend(loc='lower left', bbox_to_anchor= (1.1, 0.6), ncol=1,
borderaxespad=0, frameon=False)
data3 = [10000]*len(t)
ax1.plot(t,data3,'k--',label='data3')
ax1.legend(loc='lower left', bbox_to_anchor= (1.1, 0.5), ncol=1,
borderaxespad=0, frameon=False)
plt.show()
You can manually build your legend using line handles and labels:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(111)
t = np.arange(0.01, 10.0, 0.01)
s1 = np.exp(t)
ax1.plot(t, s1, 'b-',label='data1')
ax1.set_xlabel('time (s)')
ax2 = ax1.twinx()
s2 = np.sin(2*np.pi*t)
ax2.plot(t, s2, 'r',label='data2')
lh2, l2 = ax2.get_legend_handles_labels()
data3 = [10000]*len(t)
ax1.plot(t,data3,'k--',label='data3')
lh1, l1 = ax1.get_legend_handles_labels()
ax1.legend([lh1[0]]+lh2+[lh1[1]],
[l1[0]]+l2+[l1[1]],
loc='lower left',
bbox_to_anchor= (1.1, 0.4),
ncol=1,
borderaxespad=0,
frameon=False)
Output:

Errorbar in Legend - Pandas Bar Plot

Is it possible to show the error bars in the legend?
(Like i draw in red)
They do not necessarily have to be the correct length, it is enough for me if they are indicated and recognizable.
My working sample:
import pandas as pd
import matplotlib.pyplot as plt
test = pd.DataFrame(data={'one':2000,'two':300,'three':50,'four':150}, index=['MAX'])
fig, ax = plt.subplots(figsize=(5, 3), dpi=230)
ax.set_ylim(-.12,.03)
# barplot
ax = test.loc[['MAX'],['one']].plot(position=5.5,color=['xkcd:camo green'], xerr=test.loc[['MAX'],['two']].values.T, edgecolor='black',linewidth = 0.3, error_kw=dict(lw=1, capsize=2, capthick=1),ax=ax,kind='barh',width=.025)
ax = test.loc[['MAX'],['one']].plot(position=7,color=['xkcd:moss green'], xerr=test.loc[['MAX'],['three']].values.T, edgecolor='black',linewidth = 0.3, error_kw=dict(lw=1, capsize=2, capthick=1),ax=ax,kind='barh',width=.025)
ax = test.loc[['MAX'],['one']].plot(position=8.5,color=['xkcd:light olive green'],xerr=test.loc[['MAX'],['four']].values.T, edgecolor='black',linewidth = 0.3, error_kw=dict(lw=1, capsize=2, capthick=1),ax=ax,kind='barh',width=.025)
# Legende
h0, l0 = ax.get_legend_handles_labels()
l0 = [r'MAX $1$', r'MAX $2$', r'MAX $3$']
legend = plt.legend(h0, l0, borderpad=0.15,labelspacing=0.1, frameon=True, edgecolor="xkcd:black", ncol=1, loc='upper left',framealpha=1, facecolor='white')
legend.get_frame().set_linewidth(0.3)
cur_axes = plt.gca()
cur_axes.axes.get_yaxis().set_ticklabels([])
cur_axes.axes.get_yaxis().set_ticks([])
plt.show()
I tried a few ways, no one works.
With Patch in legend_elements i get no lines for the errorbars, with the errorbar() function i can draw a figure with errorbars, but it semms not to work in the legend:
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
from matplotlib.lines import Line2D
legend_elements = [
Line2D([1,2], [5,4], color='b', lw=1, label='Line'),
Patch(facecolor='orange', edgecolor='r', label='Color Patch'),
matplotlib.pyplot.errorbar(3, 3, yerr=None, xerr=1, marker='s',mfc='xkcd:camo green', mec='black',
ms=20, mew=2, fmt='-', ecolor="black", elinewidth=2, capsize=3,
barsabove=True, lolims=False, uplims=False, xlolims=False, xuplims=False,
errorevery=2, capthick=None, label="error"),
]
test = pd.DataFrame(data={'one':2000,'two':300,'three':50,'four':150}, index=['MAX'])
fig, ax = plt.subplots(figsize=(5, 3), dpi=230)
ax.set_ylim(-.12,.03)
# barplot
ax = test.loc[['MAX'],['one']].plot(position=5.5,color=['xkcd:camo green'], xerr=test.loc[['MAX'],['two']].values.T, edgecolor='black',linewidth = 0.3, error_kw=dict(lw=1, capsize=2, capthick=1),ax=ax,kind='barh',width=.025)
ax = test.loc[['MAX'],['one']].plot(position=7,color=['xkcd:moss green'], xerr=test.loc[['MAX'],['three']].values.T, edgecolor='black',linewidth = 0.3, error_kw=dict(lw=1, capsize=2, capthick=1),ax=ax,kind='barh',width=.025)
ax = test.loc[['MAX'],['one']].plot(position=8.5,color=['xkcd:light olive green'],xerr=test.loc[['MAX'],['four']].values.T, edgecolor='black',linewidth = 0.3, error_kw=dict(lw=1, capsize=2, capthick=1),ax=ax,kind='barh',width=.025)
# Legende
h0, l0 = ax.get_legend_handles_labels()
l0 = [r'MAX $1$', r'MAX $2$', r'MAX $3$']
legend = plt.legend(h0, l0, borderpad=0.15,labelspacing=0.1, frameon=True, edgecolor="xkcd:black", ncol=1, loc='upper left',framealpha=1, facecolor='white')
legend.get_frame().set_linewidth(0.3)
ax.legend(handles=legend_elements, loc='center')
cur_axes = plt.gca()
cur_axes.axes.get_yaxis().set_ticklabels([])
cur_axes.axes.get_yaxis().set_ticks([])
#plt.show()
Implementation based on the idea of
r-beginners:
import pandas as pd
import matplotlib.pyplot as plt
test = pd.DataFrame(data={'one':2000,'two':300,'three':50,'four':150}, index=['MAX'])
fig, ax = plt.subplots(figsize=(5, 3), dpi=150)
ax.set_ylim(0, 6)
ax.set_xlim(0, 2400)
ax1 = ax.twiny()
ax1.set_xlim(0, 2400)
ax1.set_xticks([])
ax.barh(1, width=test['one'], color=['xkcd:camo green'], edgecolor='black',linewidth = 0.3, label='MAX1')
ax.barh(2, width=test['one'], color=['xkcd:moss green'], edgecolor='black',linewidth = 0.3, label='MAX2')
ax.barh(3, width=test['one'], color=['xkcd:light olive green'], edgecolor='black',linewidth = 0.3, label='MAX3')
ax1.errorbar(test['one'], 1, xerr=test['two'], color='k', ecolor='k', fmt=',', lw=1, capsize=2, capthick=1, label='MAX1')
ax1.errorbar(test['one'], 2, xerr=test['three'], color='k', ecolor='k', fmt=',', lw=1, capsize=2, capthick=1, label='MAX2')
ax1.errorbar(test['one'], 3, xerr=test['four'], color='k', ecolor='k', fmt=',', lw=1, capsize=2, capthick=1, label='MAX3')
handler, label = ax.get_legend_handles_labels()
handler1, label1 = ax1.get_legend_handles_labels()
label1 = ['' for l in label1]
ax.legend(handler, label, loc='upper left', handletextpad=1.5)
ax1.legend(handler1, label1, loc='upper left', handletextpad=1., markerfirst=False, framealpha=0.001)
plt.show()
Changes:
ax1 gets the same limit as ax
all strings from label1 are deleted
in ax1.legend() the order of handler and label is exchanged and with the handlertextpad the error bars are shifted to the right
The method I came up with was to draw 'ax.barh' and 'ax1.errorbar()' and then superimpose the legends of each on top of each other. On one side, I minimized the transparency so that the legend below is visible; the error bar looks different because I made it biaxial.
import pandas as pd
import matplotlib.pyplot as plt
test = pd.DataFrame(data={'one':2000,'two':300,'three':50,'four':150}, index=['MAX'])
fig, ax = plt.subplots(figsize=(5, 3), dpi=230)
ax.set_ylim(0, 15)
ax.set_xlim(0, 2400)
ax1 = ax.twiny()
ax.barh(5.5, width=test['one'], color=['xkcd:camo green'], edgecolor='black',linewidth = 0.3, label='MAX1')
ax.barh(7.0, width=test['one'], color=['xkcd:moss green'], edgecolor='black',linewidth = 0.3, label='MAX2')
ax.barh(8.5, width=test['one'], color=['xkcd:light olive green'], edgecolor='black',linewidth = 0.3, label='MAX3')
ax1.errorbar(test['one'], 5.5, xerr=test['two'], color='k', ecolor='k', capsize=3, fmt='|', label='MAX1')
ax1.errorbar(test['one'], 7.0, xerr=test['three'], color='k', ecolor='k', capsize=3, fmt='|', label='MAX2')
ax1.errorbar(test['one'], 8.5, xerr=test['four'], color='k', ecolor='k', capsize=3, fmt='|', label='MAX3')
handler, label = ax.get_legend_handles_labels()
handler1, label1 = ax1.get_legend_handles_labels()
ax.legend(handler, label, loc='upper left', title='mix legend')
ax1.legend(handler1, label1, loc='upper left', title='mix legend', framealpha=0.001)
plt.show()
You can add lines manually on the chart, adjusting the color, thickness and position you prefer. It is a very manual and laborious solution, but it should work.
# Draw line
import matplotlib.lines as ln
import numpy as np
# new clear axis overlay with 0-1 limits
ax2 = plt.axes([0,0,1,1], facecolor=(1,1,1,0))
x1,y1 = np.array([[0.18, 0.21], [0.831, 0.831]])
line1 = ln.Line2D(x1, y1, lw=1, color='black', alpha=1)
x2,y2 = np.array([[0.18, 0.21], [0.783, 0.783]])
line2 = ln.Line2D(x2, y2, lw=1, color='black', alpha=1)
x3,y3 = np.array([[0.18, 0.21], [0.732, 0.732]])
line3 = ln.Line2D(x3, y3, lw=1, color='black', alpha=1)
ax2.add_line(line1)
ax2.add_line(line2)
ax2.add_line(line3)
plt.show()

Python: Suplots with secondary-axis

I wrote the following code below to do the following graph:
fig, ax = plt.subplots(figsize=(8, 6))
ax.patch.set_facecolor('white')
ax.plot(df.index, df.X1.values, 'b',
label='NMA', linewidth=1.5)
ax.set_ylabel('Index')
ax2 = ax.twinx()
ax2.plot(df.index, df.Y.values, 'r--',
label='Rate', linewidth=1.5)
ax2.set_ylabel('Rate')
lines = ax.get_lines() + ax2.get_lines()
lgd = ax.legend(lines, [line.get_label() for line in lines],
loc='lower center', ncol=2, bbox_to_anchor=(0.5, -0.15),
frameon=False)
ax.set_title('Economic Rate and Index',
weight='bold')
for i in range(5):
plt.axvspan(Dates['Peak'][i], Dates['Trough'][i],
facecolor='grey', alpha=0.5)
plt.grid(False)
plt.savefig('C:\\test.pdf',
bbox_extra_artists=(lgd,), bbox_inches='tight')
I am having a hard time to reproduce this figure in a subplot (2X2). The only thing I would change in each of the subplots is the blue line (X1 in df... for X2, X3...). How can I have a 2X2 subplot of the above graph? Of Course I would only keep one legend at the bottom of the subplots. Thanks for the help.
The data is here and the "Dates" to reproduce the gray bars here.
This is how you could create a 2x2 raster with twinx each:
import matplotlib.pyplot as plt
fig, ((ax1a, ax2a), (ax3a, ax4a)) = plt.subplots(2, 2)
ax1b = ax1a.twinx()
ax2b = ax2a.twinx()
ax3b = ax3a.twinx()
ax4b = ax4a.twinx()
ax1a.set_ylabel('ax1a')
ax2a.set_ylabel('ax2a')
ax3a.set_ylabel('ax3a')
ax4a.set_ylabel('ax4a')
ax1b.set_ylabel('ax1b')
ax2b.set_ylabel('ax2b')
ax3b.set_ylabel('ax3b')
ax4b.set_ylabel('ax4b')
plt.tight_layout()
plt.show()
Result:

Matplotlib plot disappears after annotating?

fig1 = figure()
ax = fig1.add_subplot(111, autoscale_on = False)
ax.plot(data[:,0]*1E6, data[:,3]
,data[:,0]*1E6, data[:,4],'r')
plt.xlabel('Time (us)')
plt.ylabel('Voltage (V)')
plt.title('Time')
plt.grid()
ax.annotate('axes center', xy=(.5, .5), xycoords='axes fraction',
horizontalalignment='center', verticalalignment='center')
plt.gca().xaxis.set_major_locator(MaxNLocator(prune='lower'))
plt.savefig('Time.png',orientation='landscape', pad_inches=0.1)
plt.clf()
The plot disappears after annotating. Only thing that is left after saving is the annotation. Can someone give a suggestion how to save it after annotating.

Average line for bar chart in matplotlib

How do we draw an average line (horizontal) for a histogram in using matplotlib?
Right now, I'm able to draw the histogram without any issues.
Here is the code I'm using:
## necessary variables
ind = np.arange(N) # the x locations for the groups
width = 0.2 # the width of the bars
plt.tick_params(axis='both', which='major', labelsize=30)
plt.tick_params(axis='both', which='minor', labelsize=30)
ax2 = ax.twinx()
## the bars
rects1 = ax.bar(ind, PAAE1, width,
color='0.2',
error_kw=dict(elinewidth=2,ecolor='red'),
label='PAAE1')
rects2 = ax.bar(ind+width, PAAE2, width,
color='0.3',
error_kw=dict(elinewidth=2,ecolor='black'),
label='PAAE2')
rects3 = ax2.bar(ind+width+width, AAE1, width,
color='0.4',
error_kw=dict(elinewidth=2,ecolor='red'),
label='AAE1')
rects4 = ax2.bar(ind+3*width, AAE2, width,
color='0.5',
error_kw=dict(elinewidth=2,ecolor='black'),
label='AAE3')
maxi = max(dataset[2])
maxi1 = max(dataset[4])
f_max = max(maxi, maxi1)
lns = [rects1,rects2,rects3,rects4]
labs = [l.get_label() for l in lns]
ax.legend(lns, labs, loc='upper center', ncol=4)
# axes and labels
ax.set_xlim(-width,len(ind)+width)
ax.set_ylim(0, 100)
ax.set_ylabel('PAAE', fontsize=25)
ax2.set_ylim(0, f_max+500)
ax2.set_ylabel('AAE (mW)', fontsize=25)
xTickMarks = dataset[0]
ax.set_xticks(ind+width)
xtickNames = ax.set_xticklabels(xTickMarks)
plt.setp(xtickNames, rotation=90, fontsize=25)
I want to plot the average line for PAAE 1, 2 and AAE 1, 2.
What should I be using to plot the average line?
If you'd like a vertical line to denote the mean use axvline(x_value). This will place a vertical line that always spans the full (or specified fraction of) y-axis. There's also axhline for horizontal lines.
In other works, you might have something like this:
ax.axvline(data1.mean(), color='blue', linewidth=2)
ax.axvline(data2.mean(), color='green', linewidth=2)
As a more complete, but unnecessarily complex example (most of this is nicely annotating the means with curved arrows):
import numpy as np
import matplotlib.pyplot as plt
data1 = np.random.normal(0, 1, 1000)
data2 = np.random.normal(-2, 1.5, 1000)
fig, ax = plt.subplots()
bins = np.linspace(-10, 5, 50)
ax.hist(data1, bins=bins, color='blue', label='Dataset 1',
alpha=0.5, histtype='stepfilled')
ax.hist(data2, bins=bins, color='green', label='Dataset 2',
alpha=0.5, histtype='stepfilled')
ax.axvline(data1.mean(), color='blue', linewidth=2)
ax.axvline(data2.mean(), color='green', linewidth=2)
# Add arrows annotating the means:
for dat, xoff in zip([data1, data2], [15, -15]):
x0 = dat.mean()
align = 'left' if xoff > 0 else 'right'
ax.annotate('Mean: {:0.2f}'.format(x0), xy=(x0, 1), xytext=(xoff, 15),
xycoords=('data', 'axes fraction'), textcoords='offset points',
horizontalalignment=align, verticalalignment='center',
arrowprops=dict(arrowstyle='-|>', fc='black', shrinkA=0, shrinkB=0,
connectionstyle='angle,angleA=0,angleB=90,rad=10'),
)
ax.legend(loc='upper left')
ax.margins(0.05)
plt.show()

Categories