How to add additional plots to a seaborn FacetGrid and specify colors
Question:
Is there a way to create a Seaborn line plot with all the lines gray and the mean as a red line? I’m trying to do this with relplot
but I don’t know how to separate the mean from the data (and it appears the mean isn’t being plotted?).
Make reproducible data frame
np.random.seed(1)
n1 = 100
n2 = 10
idx = np.arange(0,n1*2)
x, y, cat, id2 = [], [], [], []
x1 = list(np.random.uniform(-10,10,n2))
for i in idx:
x.extend(x1)
y.extend(list(np.random.normal(loc=0, scale=0.5, size=n2)))
cat.extend(['A', 'B'][i > n1])
id2.append(idx[i])
id2 = id2 * n2
id2.sort()
df1 = pd.DataFrame(list(zip(id2, x, y, cat)),
columns =['id2', 'x', 'y', 'cat']
)
Plotting attempt
g = sns.relplot(
data=df1, x='x', y='y', hue='id2',
col='cat', kind='line',
palette='Greys',
facet_kws=dict(sharey=False,
sharex=False
),
legend=False
)
Answers:
- It depends on the desired result. The
seaborn.relplot
documentation has an example for the fmri
dataset that only shows the mean
and the ci
, so the result depends on how you set the hue
and event
parameters.
- To specify a single color for all the lines, use
units
instead of hue
or style
(as pointed out by mwaskom), and then set color='grey'
.
- For the requirements of this OP, the accepted answer is the best option. However, in cases which require adding data from a source that isn’t the
data
used to create the relplot
, this solution may be more appropriate, as it allows for accessing each axes
of the figure, and adding something from a different data source.
import seaborn as sns
import pandas as pd
# load and select data only where event is stim
fm = sns.load_dataset('fmri').query("event == 'stim'")
# groupby to get the mean for each region by timepoint
fmg = fm.groupby(['region', 'timepoint'], as_index=False).signal.mean()
# plot the fm dataframe
g = sns.relplot(data=fm, col='region', x='timepoint', y='signal',
units='subject', kind='line', ci=None, color='grey', estimator=None)
# extract and flatten the axes from the figure
axes = g.axes.flatten()
# iterate through each axes
for ax in axes:
# extract the region
reg = ax.get_title().split(' = ')[1]
# select the data for the region
data = fmg[fmg.region.eq(reg)]
# plot the mean line
sns.lineplot(data=data, x='timepoint', y='signal', ax=ax, color='red', label='mean', lw=3)
# fix the legends
axes[0].legend().remove()
axes[1].legend(title='Subjects', bbox_to_anchor=(1, 1), loc='upper left')
Resources
- Also see the following answers for other ways to add information to a seaborn FacetGrid
I think you want units
in the call to relplot
and then add a layer of lineplot
using map
:
import seaborn as sns
import pandas as pd
fm = sns.load_dataset('fmri').query("event == 'stim'")
g = sns.relplot(
data=fm, kind='line',
col='region', x='timepoint', y='signal', units='subject',
estimator=None, color='.7'
)
g.data = fm # Hack needed to work around bug on v0.11, fixed in v0.12.dev
g.map(sns.lineplot, 'timepoint', 'signal', color='r', ci=None, lw=3)
I came here from Seaborn's relplot: Is there a way to assign a specific color to all lines and another color to another single line when using the hue argument? and can’t post there.
But I find the simplest solution is to simply pass a hue_order
as well as a palette
argument to the relplot
call. See below:
import seaborn as sbn
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
sim = ['a'] * 100 + ['b'] * 100 + ['c'] * 100
var = (['u'] * 50 + ['v'] * 50)*3
x = np.linspace(0, 50, 50)
x = np.hstack([x]*6)
y = np.random.rand(300)
df = pd.DataFrame({'x':x, 'y':y, 'sim':sim, 'var':var})
hueOrder = ["a", "b", "c"] # Specifies the order for the palette
hueColor = ["r", "r", "b"] # Specifies the colors for the hueOrder (just make two the same color, and one different)
sbn.relplot(data=df, x='x', y='y', kind='line', col='var', hue='sim', hue_order=hueOrder, palette=hueColor)
plt.show()
This is a bit different then the accepted answer. But again, I came here from another question that is closed. This also doesn’t use any for loops and should be more straight forward.
Is there a way to create a Seaborn line plot with all the lines gray and the mean as a red line? I’m trying to do this with relplot
but I don’t know how to separate the mean from the data (and it appears the mean isn’t being plotted?).
Make reproducible data frame
np.random.seed(1)
n1 = 100
n2 = 10
idx = np.arange(0,n1*2)
x, y, cat, id2 = [], [], [], []
x1 = list(np.random.uniform(-10,10,n2))
for i in idx:
x.extend(x1)
y.extend(list(np.random.normal(loc=0, scale=0.5, size=n2)))
cat.extend(['A', 'B'][i > n1])
id2.append(idx[i])
id2 = id2 * n2
id2.sort()
df1 = pd.DataFrame(list(zip(id2, x, y, cat)),
columns =['id2', 'x', 'y', 'cat']
)
Plotting attempt
g = sns.relplot(
data=df1, x='x', y='y', hue='id2',
col='cat', kind='line',
palette='Greys',
facet_kws=dict(sharey=False,
sharex=False
),
legend=False
)
- It depends on the desired result. The
seaborn.relplot
documentation has an example for thefmri
dataset that only shows themean
and theci
, so the result depends on how you set thehue
andevent
parameters. - To specify a single color for all the lines, use
units
instead ofhue
orstyle
(as pointed out by mwaskom), and then setcolor='grey'
. - For the requirements of this OP, the accepted answer is the best option. However, in cases which require adding data from a source that isn’t the
data
used to create therelplot
, this solution may be more appropriate, as it allows for accessing eachaxes
of the figure, and adding something from a different data source.
import seaborn as sns
import pandas as pd
# load and select data only where event is stim
fm = sns.load_dataset('fmri').query("event == 'stim'")
# groupby to get the mean for each region by timepoint
fmg = fm.groupby(['region', 'timepoint'], as_index=False).signal.mean()
# plot the fm dataframe
g = sns.relplot(data=fm, col='region', x='timepoint', y='signal',
units='subject', kind='line', ci=None, color='grey', estimator=None)
# extract and flatten the axes from the figure
axes = g.axes.flatten()
# iterate through each axes
for ax in axes:
# extract the region
reg = ax.get_title().split(' = ')[1]
# select the data for the region
data = fmg[fmg.region.eq(reg)]
# plot the mean line
sns.lineplot(data=data, x='timepoint', y='signal', ax=ax, color='red', label='mean', lw=3)
# fix the legends
axes[0].legend().remove()
axes[1].legend(title='Subjects', bbox_to_anchor=(1, 1), loc='upper left')
Resources
- Also see the following answers for other ways to add information to a seaborn FacetGrid
I think you want units
in the call to relplot
and then add a layer of lineplot
using map
:
import seaborn as sns
import pandas as pd
fm = sns.load_dataset('fmri').query("event == 'stim'")
g = sns.relplot(
data=fm, kind='line',
col='region', x='timepoint', y='signal', units='subject',
estimator=None, color='.7'
)
g.data = fm # Hack needed to work around bug on v0.11, fixed in v0.12.dev
g.map(sns.lineplot, 'timepoint', 'signal', color='r', ci=None, lw=3)
I came here from Seaborn's relplot: Is there a way to assign a specific color to all lines and another color to another single line when using the hue argument? and can’t post there.
But I find the simplest solution is to simply pass a hue_order
as well as a palette
argument to the relplot
call. See below:
import seaborn as sbn
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
sim = ['a'] * 100 + ['b'] * 100 + ['c'] * 100
var = (['u'] * 50 + ['v'] * 50)*3
x = np.linspace(0, 50, 50)
x = np.hstack([x]*6)
y = np.random.rand(300)
df = pd.DataFrame({'x':x, 'y':y, 'sim':sim, 'var':var})
hueOrder = ["a", "b", "c"] # Specifies the order for the palette
hueColor = ["r", "r", "b"] # Specifies the colors for the hueOrder (just make two the same color, and one different)
sbn.relplot(data=df, x='x', y='y', kind='line', col='var', hue='sim', hue_order=hueOrder, palette=hueColor)
plt.show()
This is a bit different then the accepted answer. But again, I came here from another question that is closed. This also doesn’t use any for loops and should be more straight forward.