How to use secondary Y-axis for multiple subplots in seaborn

I have two data frame which has multiple columns. I have turned DataFrame into long-form using pd.wide_to_long

df1 = pd.wide_to_long(df_1.reset_index(), ['dvr','r'], i='index', j='Sample ID').reset_index()
df2 = pd.wide_to_long(df_2.reset_index(), ['dvlnr','r'], i='index', j='Sample ID').reset_index()

I want to create subplots and am inclined to use seaborn due to its elegant formatting features. Here is my code to generate a subplot with secondary Y-Axis.

ax= sns.relplot(data=df1, x='r', y='dvlnr', col='Sample ID',  col_wrap=2, kind="line", height=4, aspect=1.5)
ax1 = plt.twinx()
ax1= sns.relplot(data=df2, x='r', y='dvr', ax=ax, col='Sample ID',  col_wrap=2, kind="line", height=4, aspect=1.5) 

But I always get ValueError: Could not interpret value 'dvr' for parameter 'y'

I have checked a few examples [eg1], eg2 but I am able to figure out a solution as I am looking for multiple subplots with secondary Axis. Is there any way to solve this? Thanks in Advance!


Solution 1:

One problem is that you seem to be mixing df1 and df2. Shouldn't you be using y='dvlnr only with df2 (and vice versa)?

Another problem is that sns.relplot doesn't return an ax. Instead, it returns a FacetGrid. And for a FacetGrid you can't call twinx(). You'd need to catch the FacetGrid via g = sns.relplot(data=df1, ...) and loop through the axes, calling twinx on each of them and create individual scatterplots. If the data ranges are similar enough, you could merge the dataframes and use hue to plot them in one go.

Here is some sample code for the case when the data ranges are too different to be plotted on the same y-axis:

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

df1 = pd.DataFrame({'r': np.random.randint(1, 31, 200),
                    'dvr': np.random.uniform(100, 1000, 200),
                    'Sample ID': np.random.randint(1, 5, 200)})
df2 = pd.DataFrame({'r': np.random.randint(1, 31, 300),
                    'dvlnr': np.random.uniform(1, 10, 300),
                    'Sample ID': np.random.randint(1, 5, 300)})

g = sns.relplot(data=df1, x='r', y='dvr', col='Sample ID', col_wrap=2, kind="line", height=4, aspect=1.5,
                color='dodgerblue')

for sample_id, ax in g.axes_dict.items():  # axes_dict is new in seaborn 0.11.2
    ax1 = ax.twinx()
    sns.lineplot(data=df2[df2['Sample ID'] == sample_id], x='r', y='dvlnr', color='crimson', ax=ax1)

plt.tight_layout()
plt.show()

combining sns.relplot via ax.twinx()

If the ranges of the y-data are similar enough, you can merge the dataframes and plot everything in one go using hue=. (This approach would also work with more dataframes.)

  • a new column (e.g. 'source') needs to be added to indicate the original dataframe
  • corresponding columns in both dataframes need the same name

Here is some example code:

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

df1 = pd.DataFrame({'r1': np.random.randint(1, 31, 200),
                    'dvr1': np.random.uniform(100, 1000, 200),
                    'Sample ID': np.random.randint(1, 5, 200)})
df2 = pd.DataFrame({'r1': np.random.randint(1, 31, 300),
                    'dvlnr1': np.random.uniform(300, 1200, 300),
                    'Sample ID': np.random.randint(1, 5, 300)})

# add an extra column to tell to which df the data belongs
df1['source'] = 'dvr'
# the corresponding columns in both df need to have the same name for the merge
df2 = df2.rename(columns={'dvlnr1': 'dvr1'})
df2['source'] = 'dvrnr'
df_merged = pd.concat([df1, df2]).reset_index()

g = sns.relplot(data=df_merged, x='r1', y='dvr1', hue='source', col='Sample ID', col_wrap=2,
                kind="line", height=4, aspect=1.5, palette='turbo')
plt.subplots_adjust(bottom=0.06, left=0.06)  # plt.tight_layout() doesn't work due to legend
plt.show()

sns.relplot with hue on newly created column for merged dfs