Seaborn: Can't use catplot with melt for different categories

Created on 21 Mar 2019  Â·  7Comments  Â·  Source: mwaskom/seaborn

I would have expected that I can visualize different categorical variables in a count plot using catplot and melt.
However, it looks like the data is converted to categorical before grouping for the columns, and so the categories are shared among all the count plots. That doesn't really make sense if the different columns correspond to different categories.
Am I overlooking something or is there a different way of doing this?

import pandas as pd
import seaborn as sns
ames = pd.read_excel("http://www.amstat.org/publications/jse/v19n3/decock/AmesHousing.xls")

cat_cols = ['MS Zoning', 'Street', 'Alley', 'Lot Shape', 'Land Contour',
       'Utilities', 'Lot Config', 'Land Slope', 'Neighborhood', 'Condition 1',
       'Condition 2', 'Bldg Type', 'House Style', 'Roof Style', 'Roof Matl',
       'Exterior 1st', 'Exterior 2nd', 'Mas Vnr Type', 'Exter Qual',
       'Exter Cond', 'Foundation', 'Bsmt Qual', 'Bsmt Cond', 'Bsmt Exposure',
       'BsmtFin Type 1', 'BsmtFin Type 2', 'Heating', 'Heating QC',
       'Central Air', 'Electrical', 'Kitchen Qual', 'Functional',
       'Fireplace Qu', 'Garage Type', 'Garage Finish', 'Garage Qual',
       'Garage Cond', 'Paved Drive', 'Pool QC', 'Fence', 'Misc Feature',
       'Sale Type', 'Sale Condition']
ames_cat = ames[cat_cols]
sns.catplot(x='value', col='variable', data=ames_cat.melt(), sharex=False, sharey=False, col_wrap=5, kind='count')
axisgrid categorical question

Most helpful comment

Indeed, catplot has some logic that unifies categories across the facets, because sharex (which just gets passed down to subplots) won't do it on its own. But it doesn't not do that if the user specifies sharex=False.

(Notes to myself: this is a bit tricky because it will have to figure out from the _CategoricalPlotter which way the plot is oriented, i.e. it's not as simple as depending on sharex).

Anyway you can use FacetGrid directly, which usually is risky because the categorical axis won't be shared, but that's what you want here:

g = sns.FacetGrid(ames_cat.melt(), col="variable", sharex=False, sharey=False, col_wrap=5)
g.map(sns.countplot, "value", order=None)

(If I understand what you're aiming for, this should do it).

All 7 comments

Indeed, catplot has some logic that unifies categories across the facets, because sharex (which just gets passed down to subplots) won't do it on its own. But it doesn't not do that if the user specifies sharex=False.

(Notes to myself: this is a bit tricky because it will have to figure out from the _CategoricalPlotter which way the plot is oriented, i.e. it's not as simple as depending on sharex).

Anyway you can use FacetGrid directly, which usually is risky because the categorical axis won't be shared, but that's what you want here:

g = sns.FacetGrid(ames_cat.melt(), col="variable", sharex=False, sharey=False, col_wrap=5)
g.map(sns.countplot, "value", order=None)

(If I understand what you're aiming for, this should do it).

It does! someone should collect all your issue replies and make a seaborn cook-book.
Thank you for your quick and insightful reply!.

I'm not sure if I understand your explanation as I'm using sharex=False and I still observe the behavior. But I have the solution I was looking for!

Ok possibly silly follow-up: Is this the idiomatic way to make the bars horizontal?

g = sns.FacetGrid(ames_cat.melt(), col="variable", sharex=False, sharey=False, col_wrap=5)
g.map(lambda x, **kwargs: sns.countplot(y=x, **kwargs), "value", order=None)

I'm going to keep this open because — while I think that Facet Grid plots should generally share x/y axes — share{x,y} is right there in the function signature and should do the right thing.

Ok possibly silly follow-up: Is this the idiomatic way to make the bars horizontal?

I've been meaning to let you pass None to .map() which would make this easier but it's not currently implemented. In the meantime that would work, or you could use .map_dataframe:

g = sns.FacetGrid(ames_cat.melt(), col="variable", sharex=False, sharey=False, col_wrap=5)
g.map_dataframe(sns.countplot, y="value", order=None)

The one disadvantage is that you'd need to set the axes labels yourself (g.set_axis_labels), though here that doesn't strike me as necessary.

Thank you, I'll look more into map_dataframe!

I'm having the same issue for countplot with dodge position. I'm using this dataset from Kaggle.

dictionary=dictionary.loc[~dictionary['Variable Name'].isin(['icu_admit_type', 'pred'])]
cols_categorical=dictionary.loc[dictionary['Data Type'] == "string"]['Variable Name'].tolist()
cols_categorical3=cols_categorical.copy()
cols_categorical3.append('hospital_death')

sharex=False works with the following code but the resulting plots are stacked

out=['bmi', 'apache_2_diagnosis','apache_3j_diagnosis']
a=[i for i in cols_categorical3 if i not in out]
dt=training_v2[a].melt(id_vars='hospital_death')
order=sorted(dt.variable.unique())
g = sns.FacetGrid(dt, col="variable", col_wrap=3,hue='hospital_death',height=5,sharex=False,sharey=False)
g.map(sns.countplot, "value")
g.add_legend()
[plt.setp(ax.get_xticklabels(), rotation=90) for ax in g.axes.flat]
g.fig.subplots_adjust(hspace=.9)
plt.show()

the following code provides dodge position but sharex=False does not work

out=['bmi', 'apache_2_diagnosis','apache_3j_diagnosis']
a=[i for i in cols_categorical3 if i not in out]
dt=training_v2[a].melt(id_vars='hospital_death')
sns.catplot(x='value',col='variable',col_wrap=3,hue='hospital_death',kind='count', data=dt,sharex=False,sharey=False)
plt.show()

any idea?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alexpetralia picture alexpetralia  Â·  3Comments

amelio-vazquez-reina picture amelio-vazquez-reina  Â·  3Comments

JanHomann picture JanHomann  Â·  3Comments

queryous picture queryous  Â·  4Comments

Bercio picture Bercio  Â·  3Comments