1

I'm trying to plot a dataframe to a few subplots using pandas and matplotlib.pyplot. But I want to have the two columns use different y axes and have those shared between all subplots.

Currently my code is:

import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame({'Area':['A', 'A', 'A', 'B', 'B', 'C','C','C','D','D','D','D'],
              'Rank':[1,2,3,1,2,1,2,3,1,2,3,4],
              'Count':[156,65,152,70,114,110,195,92,44,179,129,76],
              'Value':[630,426,312,191,374,109,194,708,236,806,168,812]}
             )
df = df.set_index(['Area', 'Rank'])

fig = plt.figure(figsize=(6,4))

for i, l in enumerate(['A','B','C','D']):

if i == 0:
    sub1 = fig.add_subplot(141+i)

else:
    sub1 = fig.add_subplot(141+i, sharey=sub1)

df.loc[l].plot(kind='bar', ax=sub1)

This produces:

graph output

This works to plot the 4 graphs side by side which is what I want but both columns use the same y-axis I'd like to have the 'Count' column use a common y-axis on the left and the 'Value' column use a common secondary y-axis on the right.

Can anybody suggest a way to do this? My attempts thus far have lead to each graph having it's own independent y-axis.

jack-tee
  • 173
  • 1
  • 8

1 Answers1

1

To create a secondary y axis, you can use twinax = ax.twinx(). Once can then join those twin axes via the join method of an axes Grouper, twinax.get_shared_y_axes().join(twinax1, twinax2). See this question for more details.

The next problem is then to get the two different barplots next to each other. Since I don't think there is a way to do this using the pandas plotting wrappers, one can use a matplotlib bar plot, which allows to specify the bar position quantitatively. The positions of the left bars would then be shifted by the bar width.

import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame({'Area':['A', 'A', 'A', 'B', 'B', 'C','C','C','D','D','D','D'],
              'Rank':[1,2,3,1,2,1,2,3,1,2,3,4],
              'Count':[156,65,152,70,114,110,195,92,44,179,129,76],
              'Value':[630,426,312,191,374,109,194,708,236,806,168,812]}
             )
df = df.set_index(['Area', 'Rank'])

fig, axes = plt.subplots(ncols=len(df.index.levels[0]), figsize=(6,4), sharey=True)
twinaxes = []
for i, l in enumerate(df.index.levels[0]):
    axes[i].bar(df["Count"].loc[l].index.values-0.4,df["Count"].loc[l], width=0.4, align="edge" )
    ax2 = axes[i].twinx()
    twinaxes.append(ax2)
    ax2.bar(df["Value"].loc[l].index.values,df["Value"].loc[l], width=0.4, align="edge", color="C3" )
    ax2.set_xticks(df["Value"].loc[l].index.values)
    ax2.set_xlabel("Rank")


[twinaxes[0].get_shared_y_axes().join(twinaxes[0], ax) for ax in twinaxes[1:]]
[ax.tick_params(labelright=False) for ax in twinaxes[:-1]]

axes[0].set_ylabel("Count")
axes[0].yaxis.label.set_color('C0')
axes[0].tick_params(axis='y', colors='C0')
twinaxes[-1].set_ylabel("Value")
twinaxes[-1].yaxis.label.set_color('C3')
twinaxes[-1].tick_params(axis='y', colors='C3')
twinaxes[0].relim()
twinaxes[0].autoscale_view()

plt.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Perfect! It's unfortunate that it's not possible to do it more neatly through pandas but this looks great, thank you very much. May I ask what these lines `[twinaxes[0].get_shared_y_axes().join(twinaxes[0], ax) for ax in twinaxes[1:]] [ax.tick_params(labelright=False) for ax in twinaxes[:-1]]` are you using a list comprehension as a shortened version of a for loop? – jack-tee Jul 01 '17 at 10:51
  • Yep exactly, this is just a shortened version of a for-loop. – ImportanceOfBeingErnest Jul 01 '17 at 10:53