seaborn: 'rows' and 'x_vars' at the same time

Question:

I want a seaborn multiplot that varies the x-axis variable by column, but varies the subset of data shown by row. I can use PairGrid to vary the variables graphed, and I can use FacetGrid to vary the subsets graphed, but I don’t see any facility to do both at once, even though it seems like a natural extension.

Is there a way to do this in seaborn currently? Or is this something that would need a feature request?

Here’s a mockup of what I’m trying to do:

label:A y:Y (plot M vs Y where label == A) (plot N vs Y where label == A)
label:B y:Y (plot M vs Y where label == B) (plot N vs Y where label == B)
x:M x:N

I’d also take the transpose of this scheme 🙂

an even better mockup

Asked By: bukzor

||

Answers:

This is not a feature that directly exists in seaborn (though it is likely to become one at some point).

That said, FacetGrid and PairGrid just instantiate different mappings between a dataframe and a figure (modulo the diagonal plots in PairGrid and a few features here and there). So a plot that is naturally expressed using one tool can generally made with the other, given a data reshaping.

So you could do something like

x_var = "body_mass_g"
col_var = "sex"
hue_var = "species"
y_vars = ["bill_length_mm", "bill_depth_mm"]

(
    df
    .melt([x_var, col_var, hue_var], y_vars)
    .pipe(
        (sns.relplot, "data"),
        x=x_var,
        y="value",
        hue=hue_var,
        col=col_var,
        row="variable",
        facet_kws=dict(sharey="row"),
        height=3.5,
    )
)

enter image description here

There’s your plot, basically, but the labels are a little confusing. Let’s improve that:

g = (
    df
    .melt([x_var, col_var, hue_var], y_vars)
    .pipe(
        (sns.relplot, "data"),
        x=x_var,
        y="value",
        hue=hue_var,
        col=col_var,
        row="variable",
        facet_kws=dict(sharey="row", margin_titles=True),
        height=3.5,
    )
    .set_titles(col_template="{col_var} = {col_name}", row_template="")
)
for (row_name, _), ax in g.axes_dict.items():
    ax.set_ylabel(row_name)

enter image description here

A little more cumbersome, but also not so hard to wrap up into a pretty general function:

def paired_column_facets(
    data: pd.DataFrame,  y_vars: list[str], other_vars: dict[str, str], **kwargs
) -> FacetGrid:
    g = (
        df
        .melt(list(other_vars.values()), y_vars)
        .pipe(
            (sns.relplot, "data"),
            **other_vars,
            y="value",
            row="variable",
            facet_kws=dict(sharey="row", margin_titles=True),
            **kwargs,
        )
        .set_titles(col_template="{col_var} = {col_name}", row_template="")
    )
    for (row_name, _), ax in g.axes_dict.items():
        ax.set_ylabel(row_name)
    return g
Answered By: mwaskom