Skip to content
Advertisement

How do you plot two different y-axes using a loop with twinx?

I have a pandas data frame, region, containing the prices of flats (Flat) and detached properties (Detached) in areas of the UK over time (the column Month). I’m trying to obtain plots of the change in price over time of both flats and detached properties, so that the plots have two different y axes – both of the average price but in different scales.

I’ve achieved this by using twinx(), however using the code below I get obviously get two figures. The first of these figures is exactly what I want, but I then get a second figure of blank plots. I have attached a screenshot of the kind of plot I want below.

When removing the second fig line fig, ax2 = ..., I get the error NameError: name 'ax2' is not defined. Also bringing the line ax2 = ax.twinx() outside of the loop gives the error AttributeError: 'numpy.ndarray' object has no attribute 'twinx'. I can’t seem to figure out how to get this plot to work without having the duplicate blank figure, any help is much appreciated.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

region_list = sorted(region['Area'].unique().tolist())

fig, ax = plt.subplots(nrows=len(region_list), figsize=(13.8,len(region_list)*7))
fig, ax2 = plt.subplots(nrows=len(region_list), figsize=(13.8,len(region_list)*7))

for i in region_list:
    ind = region_list.index(i)
    filt = region['Area'] == i
    ax2[ind] = ax[ind].twinx()
    ax[ind].plot(region.loc[filt]['Month'],region.loc[filt]['Flat'], color='red', marker='o')
    ax[ind].set_ylabel('Average price of flats', color='red', fontsize=14)
    ax2[ind].plot(region.loc[filt]['Month'],region.loc[filt]['Detached'],color='blue',marker='o')
    ax2[ind].set_ylabel('Average price of detached properties',color='blue',fontsize=14)
    ax[ind].set_title(i, size=14)
    ax[ind].xaxis.set_tick_params(labelsize=10)
    ax[ind].yaxis.set_tick_params(labelsize=10)

plt.tight_layout()

plot

Advertisement

Answer

When creating a secondary axis for a subplot, the result is a new object, and can’t be referenced using array indices like the subplot axes (unless you specifically add the new twin axes to an array).

You’ve probably seen the following:

# with one axis
fig, ax = plt.subplots()
ax2 = ax.twinx()
ax2.plot(...)

But with multiple subplots, the same logic applies:

# with one axis
fig, axes = plt.subplots(1, 2)

ax2 = axes[0].twinx()
ax2.plot(...) # secondary axis on subplot 0

ax2 = axes[1].twinx()
ax2.plot(...) # secondary axis on subplot 1

In your case:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

region_list = sorted(region['Area'].unique().tolist())

fig, ax = plt.subplots(nrows=len(region_list), figsize=(13.8,len(region_list)*7))

# don't add a second plot - this would be blank
# fig, ax2 = plt.subplots(nrows=len(region_list), figsize=(13.8,len(region_list)*7))

for i in region_list:
    ind = region_list.index(i)
    filt = region['Area'] == i

    # don't index into ax2
    # ax2[ind] = ax[ind].twinx()

    # instead, create a local variable ax2 which is the secondary axis
    # on the subplot ax[ind]
    ax2 = ax[ind].twinx()

    ax[ind].plot(region.loc[filt]['Month'],region.loc[filt]['Flat'], color='red', marker='o')
    ax[ind].set_ylabel('Average price of flats', color='red', fontsize=14)
    ax2.plot(region.loc[filt]['Month'],region.loc[filt]['Detached'],color='blue',marker='o')
    ax2.set_ylabel('Average price of detached properties',color='blue',fontsize=14)
    ax[ind].set_title(i, size=14)
    ax[ind].xaxis.set_tick_params(labelsize=10)
    ax[ind].yaxis.set_tick_params(labelsize=10)

plt.tight_layout()
Advertisement