Related
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.
I wrote a code that read an excel sheet and plots a scatter figure with the following code:
fig, ax = plt.subplots(figsize=(13, 8))
scatter = ax.scatter(df.Date, df.TopAcc, c="blue", s=df.Param / 10000, alpha=0.2)
plot = ax.plot(dfmax.Date, dfmax.TopAcc, marker="o", c="red")
handles, labels = scatter.legend_elements(num=5, prop="sizes", alpha=0.2, color="blue")
legend = ax.legend(handles, labels, loc="lower right", title="# Parameters", )
plt.grid()
plt.show()
And I got the following figure
I have the following issues: How to prevent the legend balls from overlapping?
You can set columnspacing in the legend object:
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
fig, ax = plt.subplots(figsize=(13, 8))
df = pd.DataFrame(np.random.rand(20, 2), columns=['x', 'y'])
df['s'] = 5000 * np.random.rand(20)
scatter = ax.scatter(df.x, df.y, c="blue", s=df.s, alpha=0.2)
handles, labels = scatter.legend_elements(num=5, prop="sizes", alpha=0.2, color="blue")
legend = ax.legend(handles, labels, loc="lower right", title="# Parameters", ncol=6, columnspacing=3, bbox_to_anchor=(1, -0.12), frameon=False)
plt.grid()
plt.show()
My goal is to create plot with four subplots, where the bottom two are really just empty boxes where I will display some text. Unfortunately, all of my efforts to remove the y and x axis tick marks and labels have failed. I'm still new to matplotlib so I'm sure there's something simple that I'm missing. Here's what I'm trying and what I get:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, sharex=False, sharey=True, figsize=(6,6))
fig.add_subplot(111, frameon=False)
plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)
plt.title('Neuron Length')
plt.xlabel('Strain')
plt.ylabel('Neuron Length (um)')
aIP = fig.add_subplot(223, frameon=False)
aIP.annotate('Big Axes \nGridSpec[1:, -1]', (0.1, 0.5),
xycoords='axes fraction', va='center')
# First approach
aIP.axes.xaxis.set_ticks([])
aIP.axes.yaxis.set_ticks([])
# Second approach
ax = plt.gca()
ax.axes.yaxis.set_visible(False)
plt.show()
This is achieved by using plt.subplots() to draw four of them and remove the bottom left frame.
import matplotlib.pyplot as plt
import numpy as np
t = np.linspace(-np.pi, np.pi, 1000)
x1 = np.sin(2*t)
x2 = np.cos(2*t)
x3 = x1 + x2
fig,axes = plt.subplots(nrows=2,ncols=2,figsize=(6,6), sharex=True, sharey=True)
axes[0,0].plot(t, x1, linewidth=2)
axes[0,1].plot(t, x2, linewidth=2)
axes[1,1].plot(t, x3, linewidth=2)
axes[1,0].axis('off') # off
axes[1,0].annotate('Big Axes \nGridSpec[1:, -1]', (0.1, 0.5), xycoords='axes fraction', va='center')
fig.suptitle('Neuron Length')
for ax in axes.flat:
ax.set(xlabel='Strain', ylabel='Neuron Length (um)')
plt.show()
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()
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()