How to add a horizontal mean line and annotation to each facet

Question:

I have a simple FacetGrid of 2 row and 1 column with lineplots denoting different categories for the facets – image below.

# lineplot for each Category over the last three years
g = sns.FacetGrid(df, row="Category", sharey=False, sharex=False, height=2.5, aspect = 3)
g = g.map(plt.plot, 'Date', 'Count')

enter image description here

How do I add a reference line and annotation showing the mean Count for each facet?

Sample Data

  • Read the sample dataframe with
df = pd.read_html('https://stackoverflow.com/q/59058641/7758804')[0]
df.Date = pd.to_datetime(df.Date).dt.date
Category Date Count
Group 1 2017-01-31 226
Group 1 2017-02-28 235
Group 1 2017-03-31 236
Group 1 2017-04-30 221
Group 1 2017-05-31 187
Group 1 2017-06-30 218
Group 1 2017-07-31 225
Group 1 2017-08-31 221
Group 1 2017-09-30 248
Group 1 2017-10-31 224
Group 1 2017-11-30 204
Group 1 2017-12-31 224
Group 1 2018-01-31 218
Group 1 2018-02-28 241
Group 1 2018-03-31 196
Group 1 2018-04-30 246
Group 1 2018-05-31 256
Group 1 2018-06-30 217
Group 1 2018-07-31 229
Group 1 2018-08-31 230
Group 1 2018-09-30 222
Group 1 2018-10-31 215
Group 1 2018-11-30 226
Group 1 2018-12-31 227
Group 1 2019-01-31 232
Group 1 2019-02-28 233
Group 1 2019-03-31 224
Group 1 2019-04-30 214
Group 1 2019-05-31 243
Group 1 2019-06-30 214
Group 1 2019-07-31 235
Group 1 2019-08-31 218
Group 1 2019-09-30 208
Group 2 2017-01-31 208
Group 2 2017-02-28 254
Group 2 2017-03-31 223
Group 2 2017-04-30 227
Group 2 2017-05-31 245
Group 2 2017-06-30 222
Group 2 2017-07-31 226
Group 2 2017-08-31 235
Group 2 2017-09-30 225
Group 2 2017-10-31 226
Group 2 2017-11-30 258
Group 2 2017-12-31 234
Group 2 2018-01-31 257
Group 2 2018-02-28 224
Group 2 2018-03-31 228
Group 2 2018-04-30 222
Group 2 2018-05-31 227
Group 2 2018-06-30 256
Group 2 2018-07-31 217
Group 2 2018-08-31 243
Group 2 2018-09-30 230
Group 2 2018-10-31 250
Group 2 2018-11-30 197
Group 2 2018-12-31 232
Group 2 2019-01-31 248
Group 2 2019-02-28 232
Group 2 2019-03-31 259
Group 2 2019-04-30 259
Group 2 2019-05-31 229
Group 2 2019-06-30 228
Group 2 2019-07-31 234
Group 2 2019-08-31 218
Group 2 2019-09-30 231
Asked By: ZKING

||

Answers:

g = sns.relplot(data=df, kind='line', x='Date', y='Count', row='Category', height=2.5, aspect=3, facet_kws={'sharey': True, 'sharex': False})
g.fig.tight_layout()

# draw lines:
for m, ax in zip(df.groupby('Category').Count.mean(), g.axes.ravel()):
    ax.hlines(m, *ax.get_xlim())
    ax.annotate(f'Mean: {m:0.0f}', xy=(ax.get_xlim()[1], m))

enter image description here

  • This also works for other figure-level plots like sns.catplot.
g = sns.catplot(data=df, kind='bar', x='Date', y='Count', row='Category', height=2.5, aspect=3)
g.set_xticklabels(rotation=90)

# draw lines:
for m, ax in zip(df.groupby('Category').Count.mean(), g.axes.ravel()):
    ax.hlines(m, *ax.get_xlim())
    ax.annotate(f'Mean: {m:0.0f}', xy=(ax.get_xlim()[1], m))

enter image description here


You can manually draw the horizontal line on each of the axes:

zip(list1, list2) is similar to [(list1[0], list2[0]), (list1[1], list2[1]),...]. In this code it means m is the mean, ax is the axis in the facets. ravel() turns n-dimension np.array into 1D array so you can zip. ax.hlines(y_val, x_min, x_max) draw a horizontal line at y_val from x_min to x_max. Here the two x values are provided by *ax.get_xlim().

g = sns.FacetGrid(df, row="Category", sharey=False, sharex=False, height=2.5, aspect = 3)
g = g.map(plt.plot, 'Date', 'Count')

# draw lines:
for m,ax in zip(df.groupby('Category').Count.mean(), g.axes.ravel()):
    ax.hlines(m,*ax.get_xlim())

Output:

enter image description here

Answered By: Quang Hoang
g = sns.relplot(data=df, kind='line', x='Date', y='Count', row='Category', height=2.5, aspect=3, facet_kws={'sharey': True, 'sharex': False})
g.fig.tight_layout()


def custom(y, **kwargs):
    ym = y.mean()
    plt.axhline(ym, color="orange", linestyle="dashed")
    plt.annotate(f"mean: {y.mean():.3f}", xy=(1,ym), 
                 xycoords=plt.gca().get_yaxis_transform(), ha="right")
    

g = g.map(custom, 'Count')

enter image description here


Just a single line is enough

g = sns.FacetGrid(df, row="Category", sharey=False, sharex=False, height=2.5, aspect = 3)
g = g.map(plt.plot, 'Date', 'Count')

# add this to get a horizontal line
g = g.map(lambda y, **kw: plt.axhline(y.mean(), color="k"), 'Count')

enter image description here

To make the line orange and dashed and add an annotation, you could do

def custom(y, **kwargs):
    ym = y.mean()
    plt.axhline(ym, color="orange", linestyle="dashed")
    plt.annotate(f"mean: {y.mean():.3f}", xy=(1,ym), 
                 xycoords=plt.gca().get_yaxis_transform(), ha="right")
    

g = sns.FacetGrid(df, row="Category", sharey=False, sharex=False, height=2.5, aspect = 3)
g = g.map(plt.plot, 'Date', 'Count')
    
g = g.map(custom, 'Count')

enter image description here