How to work around the fact that FacetGrid doesn't have a style parameter?

Question:

I can’t figure out how to vary both hue and style in a seaborn FacetGrid mapped to lineplot, and still end up with a legend. Setup:

import numpy as np
import seaborn as sns
# add variables "group" and "stim_order" to the fMRI dataset, for demo purposes
df = sns.load_dataset('fmri')
df['group'] = df['subject'].map(lambda subj: int(subj.lstrip('s')) % 2)
df['stim_order'] = df['subject'].map({subj: np.random.choice([0, 1])
                                      for subj in df['subject'].unique()})

The following would be ideal, but isn’t implemented (FacetGrid has no “style” param):

g = sns.FacetGrid(data=df, row='region', col='event', hue='group', style='stim_order')
g.map(sns.lineplot, 'timepoint', 'signal')

This next approach also doesn’t work, because apparently kwargs passed to FacetGrid.map() cannot be references to columns in the data:

g = sns.FacetGrid(data=df, row='region', col='event', hue='group')
g.map(sns.lineplot, 'timepoint', 'signal', style='stim_order')
# fails with ValueError: Could not interpret input 'stim_order'

I can work around these limitations by not specifying “hue” when setting up the FacetGrid, and adding a new column “size” to the data, so that I can pass all the data-related parameters as positional args. But then I get no legend:

df['size'] = 1
g = sns.FacetGrid(data=df, row='region', col='event')
g.map(sns.lineplot, 'timepoint', 'signal', 'group', 'size', 'stim_order')

2x2 seaborn FacetGrid figure

Is there a way to get a plot like this, but that has a brief legend_out containing the hue and style mappings?

Asked By: drammock

||

Answers:

I found an approach that kinda works, but requires some tuning of the subplots_adjust and bbox_to_anchor numbers, in concert with the figure size:

import numpy as np
import seaborn as sns
# add variables "group" and "stim_order" to the fMRI dataset, for demo purposes
df = sns.load_dataset('fmri')
df['group'] = df['subject'].map(lambda subj: int(subj.lstrip('s')) % 2)
df['stim_order'] = df['subject'].map({subj: np.random.choice([0, 1])
                                      for subj in df['subject'].unique()})

df['size'] = 1
g = sns.FacetGrid(data=df, row='region', col='event')
g.map(sns.lineplot, 'timepoint', 'signal', 'group', 'size', 'stim_order')

g.fig.subplots_adjust(right=0.75)
g.axes[-1, -1].legend(loc='center left', bbox_to_anchor=(1.05, 1))

seaborn 2x2 FacetGrid with legend

However, it introduces an unnecessary legend element for “size” which shouldn’t be there, since size isn’t really mapped to anything (it’s always 1 in the dataset, only mapped as a workaround).

Answered By: drammock

You can use g.map_dataframe instead of g.map and get the behaviour you wish.

Using your code would result in:

g = sns.FacetGrid(data=df, row='region', col='event', hue='group')
g.map_dataframe(sns.lineplot, 'timepoint', 'signal', style='stim_order')
Answered By: AMH
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.