当输入是 DataFrame 时在 seaborn 中对箱线图进行分组

Posted

技术标签:

【中文标题】当输入是 DataFrame 时在 seaborn 中对箱线图进行分组【英文标题】:Grouping boxplots in seaborn when input is a DataFrame 【发布时间】:2014-10-06 17:39:49 【问题描述】:

我打算在pandas dataframe 中绘制多个列,所有列都使用groupbyseaborn.boxplot 中的另一列分组。对于matplotlib matplotlib: Group boxplots 中的类似问题,这里有一个很好的答案,但鉴于seaborn.boxplot 带有groupby 选项,我认为在seaborn 中执行此操作会容易得多。

这里我们使用一个失败的可重现示例:

import seaborn as sns
import pandas as pd
df = pd.DataFrame(
[
[2, 4, 5, 6, 1],
[4, 5, 6, 7, 2],
[5, 4, 5, 5, 1],
[10, 4, 7, 8, 2],
[9, 3, 4, 6, 2],
[3, 3, 4, 4, 1]
], columns=['a1', 'a2', 'a3', 'a4', 'b'])

#Plotting by seaborn
sns.boxplot(df[['a1','a2', 'a3', 'a4']], groupby=df.b)

我得到的是完全忽略 groupby 选项的东西:

然而,如果我用一个专栏来做这件事,这要归功于另一个 SO 问题Seaborn groupby pandas Series:

sns.boxplot(df.a1, groupby=df.b)

所以我想把我的所有列都放在一个图中(所有列的比例都差不多)。

编辑:

上面的 SO 问题已经过编辑,现在包含对此问题的“不干净”的答案,但如果有人对此问题有更好的想法,那就太好了。

【问题讨论】:

【参考方案1】:

正如其他答案所指出的,boxplot 函数仅限于绘制单个“层”箱线图,而groupby 参数仅在输入为系列并且您有第二个变量时才有效用于将观察结果分箱到每个框中..

但是,您可以使用kind="box" 来完成我认为您希望通过factorplot 函数实现的目标。但是,您首先必须将示例数据框“融合”成所谓的长格式或“整齐”格式,其中每一列都是一个变量,每一行都是一个观察值:

df_long = pd.melt(df, "b", var_name="a", value_name="c")

那么绘制就很简单了:

sns.factorplot("a", hue="b", y="c", data=df_long, kind="box")

【讨论】:

这偶尔会得到支持,但自 0.6 以来,sns.boxplot 中的 FWIW 嵌套箱线图已经成为可能。 这个melt 太疯狂了,超级出乎意料【参考方案2】:

您可以直接使用boxplot(我想当问题被问到时,这是不可能的,但seaborn 版本> 0.6 是这样)。

正如@mwaskom 所解释的,您必须将示例数据框“融化”成“长格式”,其中每一列都是一个变量,每一行都是一个观察值:

df_long = pd.melt(df, "b", var_name="a", value_name="c")

然后你只需绘制它:

sns.boxplot(x="a", hue="b", y="c", data=df_long)

【讨论】:

【参考方案3】:

Seaborn 的 groupby 函数采用 Series 而不是 DataFrame,这就是它不起作用的原因。

作为一种解决方法,您可以这样做:

fig, ax = plt.subplots(1,2, sharey=True)
for i, grp in enumerate(df.filter(regex="a").groupby(by=df.b)):
    sns.boxplot(grp[1], ax=ax[i])

它给出:

注意df.filter(regex="a") 等价于df[['a1','a2', 'a3', 'a4']]

   a1  a2  a3  a4
0   2   4   5   6
1   4   5   6   7
2   5   4   5   5
3  10   4   7   8
4   9   3   4   6
5   3   3   4   4

希望对你有帮助

【讨论】:

谢谢我接受了下面的答案,因为它在一个图中给出了所有的情节。【参考方案4】:

实际上并没有比您链接的答案更好,但我认为在 seaborn 中实现这一点的方法是使用 FacetGrid 功能,因为 groupby 参数仅针对传递给 boxplot 函数的 Series 定义。

这里有一些代码 - pd.melt 是必需的,因为(据我所知)构面映射只能将单个列作为参数,因此需要将数据转换为“长”格式。

g = sns.FacetGrid(pd.melt(df, id_vars='b'), col='b')
g.map(sns.boxplot, 'value', 'variable')

【讨论】:

如果你想要这样的情节其实不用直接使用FacetGrid,你也可以在这里使用factorplotcol=b。 (这没有错,只是比必要的工作多)。【参考方案5】:

这并没有给这个对话增加太多内容,但是在与这个问题斗争的时间超过了保证(实际的集群不可用)之后,我想我会添加我的实现作为另一个示例。它有一个叠加的散点图(因为我的数据集有多烦人),使用索引显示融化,以及一些美学调整。我希望这对某人有用。

output_graph

这里没有使用列标题(我看到一个不同的线程想知道如何使用索引来做到这一点):

combined_array: ndarray = np.concatenate([dbscan_output.data, dbscan_output.labels.reshape(-1, 1)], axis=1)
cluster_data_df: DataFrame = DataFrame(combined_array)

if you want to use labelled columns:
column_names: List[str] = list(outcome_variable_names)
column_names.append('cluster')
cluster_data_df.set_axis(column_names, axis='columns', inplace=True)

graph_data: DataFrame = pd.melt(
    frame=cluster_data_df,
    id_vars=['cluster'],
    # value_vars is an optional param - by default it uses columns except the id vars, but I've included it as an example
    # value_vars=['outcome_var_1', 'outcome_var_2', 'outcome_var_3', 'outcome_var_4', 'outcome_var_5', 'outcome_var_6'] 
    var_name='psychometric_test',
    value_name='standard deviations from the mean'
)

生成的数据框(rows = sample_n x variable_n(在我的情况下为 1626 x 6 = 9756)):

index cluster psychometric_tst standard deviations from the mean
0 0.0 outcome_var_1 -1.276182
1 0.0 outcome_var_1 -1.118813
2 0.0 outcome_var_1 -1.276182
9754 0.0 outcome_var_6 0.892548
9755 0.0 outcome_var_6 1.420480

如果你想在熔体中使用索引:

graph_data: DataFrame = pd.melt(
    frame=cluster_data_df,
    id_vars=cluster_data_df.columns[-1],
    # value_vars=cluster_data_df.columns[:-1],
    var_name='psychometric_test',
    value_name='standard deviations from the mean'
)

这是图形代码: (完成列标题 - 只需注意 y-axis=value_name, x-axis = var_name, hue = id_vars):

# plot graph grouped by cluster
sns.set_theme(style="ticks")
fig = plt.figure(figsize=(10, 10))
fig.set(font_scale=1.2)
fig.set_style("white")

# create boxplot
fig.ax = sns.boxplot(y='standard deviations from the mean', x='psychometric_test', hue='cluster', showfliers=False,
                     data=graph_data)

# set box alpha:
for patch in fig.ax.artists:
    r, g, b, a = patch.get_facecolor()
    patch.set_facecolor((r, g, b, .2))

# create scatterplot
fig.ax = sns.stripplot(y='standard deviations from the mean', x='psychometric_test', hue='cluster', data=graph_data,
                       dodge=True, alpha=.25, zorder=1)

# customise legend:
cluster_n: int = dbscan_output.n_clusters
## create list with legend text
i = 0
cluster_info: Dict[int, int] = dbscan_output.cluster_sizes  # custom method
legend_labels: List[str] = []
while i < cluster_n:
    label: str = f"cluster i+1, n = cluster_info[i]"
    legend_labels.append(label)
    i += 1
if -1 in cluster_info.keys():
    cluster_n += 1
    label: str = f"Unclustered, n = cluster_info[-1]"
    legend_labels.insert(0, label)

## fetch existing handles and legends (each tuple will have 2*cluster number -> 1 for each boxplot cluster, 1 for each scatterplot cluster, so I will remove the first half)
handles, labels = fig.ax.get_legend_handles_labels()
index: int = int(cluster_n*(-1))
labels = legend_labels
plt.legend(handles[index:], labels[0:])
plt.xticks(rotation=45)
plt.show()

asds

请注意:我的大部分时间都花在了调试 melt 功能上。我主要收到错误"*only integer scalar arrays can be converted to a scalar index with 1D numpy indices array*"。我的输出要求我将结果变量值表和集群 (DBSCAN) 连接起来,并且我会在 concat 方法中的集群数组周围放置额外的方括号。所以我有一个列,其中每个值都是一个不可见的 List[int],而不是一个普通的 int。它非常小众,但也许会对某人有所帮助。

    列表项

【讨论】:

以上是关于当输入是 DataFrame 时在 seaborn 中对箱线图进行分组的主要内容,如果未能解决你的问题,请参考以下文章

Pandas DataFrame 到 Seaborn

如何在不命名 DataFrame 列的情况下使用 Seaborn.lmplot 函数?

从 pandas dataFrame 调整 seaborn 图

如何使用 seaborn 为我的 DataFrame 创建堆叠条形图 [重复]

Pandas Dataframe 到 Seaborn 分组条形图

Pandas DataFrame.hist Seaborn 等价物