Background with range on seaborn based on two columns

Usually, is the tool for this, as shown in many examples in the mutli-plot grids tutorial. But you are using relplot to combine lineplot with a FacetGrid as it is suggested in the docs (last example) which lets you use some extra styling parameters.

Because relplot processes the data a bit differently than if you would first initiate a FacetGrid and then map a lineplot (you can check this with, using, ...) to plot the ranges is quite cumbersome as it requires editing the dataframe as well as the x- and y-axis labels.

The simplest way to plot the ranges is to loop through the grid.axes. This can be done with grid.axes_dict.items() which provides the column names (i.e. countries) that you can use to select the appropriate data for the bars (useful if the ranges were to differ, contrary to this example).

The default figure legend does not contain the complete legend including the key for ranges, but the first ax object does so that one displayed instead of the default legend in the following example. Note that I have edited the data you shared so that the min/max ranges make more sense:

import io
import pandas as pd              # v 1.1.3
import matplotlib.pyplot as plt  # v 3.3.2
import seaborn as sns            # v 0.11.0

 Country Model   Year    Costs   Min Max
    494 FR  1   1990    300     250     350
    495 FR  1   1995    250     200     300
    496 FR  1   2000    220     150     240
    497 FR  1   2005    210     189     270
    555 JPN 8   1990    280     250     350
    556 JPN 8   1995    240     200     300
    557 JPN 8   2000    200     150     240
    558 JPN 8   2005    200     189     270
df = pd.read_csv(io.StringIO(data), delim_whitespace=True)

# Create seaborn FacetGrid with line plots
grid = sns.relplot(data=df, x='Year', y='Costs', hue="Model", style="Model",height=3.9,
                   col="Country", kind='line', markers=True, palette="tab10")

# Loop through axes of the FacetGrid to plot bars for ranges and edit x ticks
for country, ax in grid.axes_dict.items():
    df_country = df[df['Country'] == country]
    cost_range = df_country['Max']-df_country['Min']['Year'], height=cost_range, bottom=df_country['Min'], 
           color="black", alpha=0.1, label="Min/max\nrange")

# Remove default seaborn figure legend and show instead full legend stored in first ax
grid.axes.flat[0].legend(bbox_to_anchor=(2.1, 0.5), loc="center left",
                         frameon=False, title=grid.legend.get_title().get_text());


Leave a Comment