0

When plotting with twinx how can multiple subplots be used?

%pylab inline
import pandas as pd
import seaborn as sns; sns.set()

df = pd.DataFrame({'dt':['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04'], 'category':['a', 'b', 'a', 'b'], 'foo':[10, 15, 8, 13], 'bar':[12, 8, 5, 18]})
df['dt'] = pd.to_datetime(df['dt'])

ax = sns.lineplot(x='dt', y='foo', data=df, hue='category')
ax.set_ylabel('asdf', fontsize=28)
ax.plot([], '-g', label = 'other axis in legend')
plt.legend(fontsize='x-large')
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles=handles[1:], labels=labels[1:], fontsize='large', loc='lower left')
plt.xticks(rotation=90, horizontalalignment='center', fontsize=28)
plt.xlabel('')
plt.yticks(fontsize=16)

ax2 = ax.twinx()
ax2 = sns.lineplot(x='dt', y='bar', data=df, ax=ax2, color='green')
plt.yticks(fontsize=16)
ax2.plot([], '-g', label = 'other axis in legend')
ax2.set_ylabel('ratio', fontsize=28)

plt.axvline(x=np.datetime64('2020-01-02'),color='k', linestyle='--', lw=4)
plt.text(x=np.datetime64('2020-01-02'), y=10, s=' foo-the-bar  ', fontsize=28, horizontalalignment='left')
plt.show()


d2 = pd.DataFrame({'dt':['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04'], 'category':['a', 'b', 'a', 'b'],'foo':[11, 16, 8, 14], 'bar':[11, 7, 4, 17]})
d2['dt'] = pd.to_datetime(d2['dt'])

ax = sns.lineplot(x='dt', y='foo', data=d2, hue='category')
ax.set_ylabel('something else', fontsize=28)
ax.plot([], '-g', label = 'other axis in legend')
plt.legend(fontsize='x-large')
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles=handles[1:], labels=labels[1:], fontsize='large', loc='lower left')
plt.xticks(rotation=90, horizontalalignment='center', fontsize=28)
plt.xlabel('')
plt.yticks(fontsize=16)
plt.axvline(x=np.datetime64('2020-01-02'),color='k', linestyle='--', lw=4)
plt.text(x=np.datetime64('2020-01-02'), y=10, s=' foo-the-bar  ', fontsize=28, horizontalalignment='left')


ax2 = ax.twinx()
ax2 = sns.lineplot(x='dt', y='bar', data=d2, ax=ax2, color='green')
plt.yticks(fontsize=16)
ax2.plot([], '-g', label = 'other axis in legend')
ax2.set_ylabel('ratio', fontsize=28)
plt.show()

It is more or less nice. But when adding in subplots to combine multiple measurements into a single figure sharing the x-axis (i.e. saving space and writing the dates over and over) the following fails to work and completely distorts the plot

ax0 = plt.subplot(211)
ax2 = ax0.twinx()
ax3 = plt.subplot(212)
ax4 = ax3.twinx()

ax = sns.lineplot(x='dt', y='foo', data=df, hue='category', ax=ax0)
ax.set_ylabel('asdf', fontsize=28)
ax.plot([], '-g', label = 'other axis in legend')
plt.legend(fontsize='x-large')
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles=handles[1:], labels=labels[1:], fontsize='large', loc='lower left')
plt.xticks(rotation=90, horizontalalignment='center', fontsize=28)
plt.xlabel('')
plt.yticks(fontsize=16)
plt.axvline(x=np.datetime64('2020-01-02'),color='k', linestyle='--', lw=4)
plt.text(x=np.datetime64('2020-01-02'), y=10, s=' foo-the-bar  ', fontsize=28, horizontalalignment='left')


#ax2 = ax.twinx()
ax2 = sns.lineplot(x='dt', y='bar', data=df, ax=ax2, color='green')
plt.yticks(fontsize=16)
ax2.plot([], '-g', label = 'other axis in legend')
ax2.set_ylabel('ratio', fontsize=28)
plt.show()


# TODO second plot is missing
Georg Heiler
  • 16,916
  • 36
  • 162
  • 292
  • Can you provide more information on how the second code "fails to work and completely distorts the plot"? It looks fine to me. – Diziet Asahi May 13 '20 at 12:36
  • The text labels are plotted way out of the plot (but only for the subplot version), and the vertical lines are lost. – Georg Heiler May 13 '20 at 12:37

1 Answers1

1

Problems always arise when trying to mix the object-oriented syntax and the pyplot interface.

pyplot functions (plt.XXX) only affect the current axes (generally the latest created, in your case ax4). When you have several axes, it is generally much better to use the OO functions so that there is no ambiguity about which axes you are working on.

Additionally, you might want to through a tight_layout() at the end of your code to automatically adjust the position of your axes to give enough room for your labels

plt.figure()
ax0 = plt.subplot(211)
ax2 = ax0.twinx()
ax3 = plt.subplot(212)
ax4 = ax3.twinx()

ax = sns.lineplot(x='dt', y='foo', data=df, hue='category', ax=ax0)
ax.set_ylabel('asdf', fontsize=28)
ax.plot([], '-g', label = 'other axis in legend')
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles=handles[1:], labels=labels[1:], fontsize='large', loc='lower left')
ax.set_xticklabels(ax.get_xticklabels(), rotation=90, horizontalalignment='center', fontsize=28)
ax.set_xlabel('')
ax.tick_params(axis='y', labelsize=16)
ax.axvline(x=np.datetime64('2020-01-02'),color='k', linestyle='--', lw=4)
ax.text(x=np.datetime64('2020-01-02'), y=10, s=' foo-the-bar  ', fontsize=28, horizontalalignment='left')


#ax2 = ax.twinx()
ax2 = sns.lineplot(x='dt', y='bar', data=df, ax=ax2, color='green')
ax2.tick_params(axis='y', labelsize=16)
ax2.plot([], '-g', label = 'other axis in legend')
ax2.set_ylabel('ratio', fontsize=28)

plt.tight_layout()
plt.show()

enter image description here

Diziet Asahi
  • 38,379
  • 7
  • 60
  • 75
  • Really cool. How can I also 1) set `sharex` to true and name the sub plots? – Georg Heiler May 13 '20 at 13:01
  • You can use `ax3 = plt.subplot(212, sharex=ax0)` to share the xaxis. Not sure what you mean by "name the subplots", maybe you want to put a title on top? `ax0.set_title("top subplot")` – Diziet Asahi May 13 '20 at 13:18
  • I figured out to name them https://stackoverflow.com/questions/25543978/matplotlib-annotate-subplots-in-a-figure-with-a-b-c however 1) is still unsolved for me. I want to shar the x axis, but your solution (I have it in my code already) does not seem to change anything and the x axis is plotted 2x. – Georg Heiler May 13 '20 at 13:20
  • `sharex` only ensures that the limits stay in sync between the two axes. If you want to hide one set of labels, check https://stackoverflow.com/questions/4209467/matplotlib-share-x-axis-but-dont-show-x-axis-tick-labels-for-both-just-one – Diziet Asahi May 13 '20 at 13:25
  • One question though: `ax.set_yticklabels(ax.get_yticklabels(), fontsize=16)` deletes the y axis labels. How can this be prevented? I just want to increase the font size. – Georg Heiler May 13 '20 at 14:12
  • 1
    You're right, this line does not work as intended. Use [`Axes.tick_params()`](https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.axes.Axes.tick_params.html) instead. I've corrected my code – Diziet Asahi May 13 '20 at 15:38