0

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: img The interesting is I don't have this problem for bar plots plt.bar().

Mario
  • 1,631
  • 2
  • 21
  • 51

1 Answers1

0

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.

img

Mario
  • 1,631
  • 2
  • 21
  • 51