使用seaborn分割不同范围的小提琴图

Posted

技术标签:

【中文标题】使用seaborn分割不同范围的小提琴图【英文标题】:split violinplot with different ranges using seaborn 【发布时间】:2022-01-23 06:28:06 【问题描述】:

我正在尝试使用 seaborn 中的分割小提琴图绘制具有不同范围的两个变量。

这是我到目前为止所做的:

from matplotlib import pyplot as plt
import seaborn as sns
import numpy as np

df1 = pd.read_csv('dummy_metric1.csv')
df2 = pd.read_csv('dummy_metric2.csv')

fig, ax2 = plt.subplots()

sns.set_style('white')
palette1 = 'Set2'
palette2 = 'Set1'
colors_list = ['#78C850', '#F08030',  '#6890F0',  '#A8B820',  '#F8D030', '#E0C068', '#C03028', '#F85888', '#98D8D8']

ax1 = sns.violinplot(y=df1.Value,x=df1.modality,hue=df1.metric, palette=palette1, inner="stick")
xlim = ax1.get_xlim()
ylim = ax1.get_ylim()
for violin in ax1.collections:
    bbox = violin.get_paths()[0].get_extents()
    x0, y0, width, height = bbox.bounds
    violin.set_clip_path(plt.Rectangle((x0, y0), width / 2, height, transform=ax1.transData))
ax1.set_xlim(xlim)
ax1.set_ylim(ylim)
ax1.set_title("dummy")
ax1.set_ylabel("metric1")
ax1.set_xlabel("Modality")
ax1.set_xticklabels(ax1.get_xticklabels(), rotation=45, ha='right')
ax1.legend_.remove()

ax2 = ax1.twinx() 

ax2 = sns.violinplot(y=df2.Value,x=df2.modality,hue=df2.metric, palette=palette2, inner=None)
xlim = ax2.get_xlim()
ylim = ax2.get_ylim()
for violin in ax2.collections:
    bbox = violin.get_paths()[0].get_extents()
    x0, y0, width, height = bbox.bounds
    violin.set_clip_path(plt.Rectangle((x0, y0), width / 2, height, transform=ax2.transData))
ax2.set_xlim(xlim)
ax2.set_ylim(ylim)
ax2.set_ylabel("Metric2")
ax2.set_xticklabels(ax2.get_xticklabels(), rotation=45, ha='right')
ax2.legend_.remove()

fig.tight_layout()
plt.show()

但是,我无法使用 ax2 小提琴的正确部分。这是输出。

当我做violin.set_clip_path(plt.Rectangle((width/2, y0), width / 2, height, transform=ax2.transData)) 时,我得到了这个结果:

有人可以解释我缺少什么吗?另外,我怎样才能拥有inner="stick"

TIA

【问题讨论】:

【参考方案1】:

这是一种使用split=True 和虚拟数据强制拆分空半部分的方法。对于左半部分,metric 设置为1 用于真实数据,2 用于虚拟数据。右半边反之亦然。我们需要确保所有数据框对modality 列使用相同的分类顺序,以避免混淆。

from matplotlib import pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np

sns.set_style('white')
df1 = pd.DataFrame('modality': pd.Categorical.from_codes(np.random.randint(0, 3, 30), ['a', 'b', 'c']),
                    'Value': np.random.rand(30) * 25 + 50)
df1['metric'] = 1
df1_dummy = pd.DataFrame('modality': pd.Categorical.from_codes([0], ['a', 'b', 'c']), 'Value': [np.nan])
df1_dummy['metric'] = 2

df2 = pd.DataFrame('modality': pd.Categorical.from_codes(np.random.randint(0, 3, 100), ['a', 'b', 'c']),
                    'Value': np.random.randn(100).cumsum() / 10 + 1)
df2['metric'] = 2
df2_dummy = pd.DataFrame('modality': pd.Categorical.from_codes([0], ['a', 'b', 'c']), 'Value': [np.nan])
df2_dummy['metric'] = 1

ax1 = sns.violinplot(y='Value', x='modality', hue='metric', palette=['turquoise', 'red'],
                     inner="stick", split=True, data=pd.concat([df1, df1_dummy]))
ax1.legend_.remove()
ax1.set_ylabel('metric 1')

ax2 = ax1.twinx()
sns.violinplot(y='Value', x='modality', hue='metric', palette=['turquoise', 'red'],
               inner="stick", split=True, data=pd.concat([df2, df2_dummy]), ax=ax2)
ax2.set_ylabel('metric 2')

plt.tight_layout()
plt.show()

PS:这是对原始代码的可能改编:

使用plt.Rectangle((x0+width/2, y0), width/2, height)将小提琴夹在ax2上 使用sns.violinplot()ax= 参数指示正确的子图 不改变两斧的 xlim 和 ylim 确保两个数据框对modality 使用相同的分类顺序 剪切“内部”行,对于ax1:循环遍历这些行,获取它们的x0x1,并将行缩短为x0(x0+x1)/2 ax2 类似:遍历行,获取它们的 x0x1,并将行缩短为 (x0+x1)/2x1 更新ax2的图例与ax1的图例结合,然后删除ax1的图例
from matplotlib import pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np

df1 = pd.DataFrame('modality': pd.Categorical.from_codes(np.random.randint(0, 3, 30), ['a', 'b', 'c']),
                    'Value': np.random.rand(30) * 25 + 50)
df1['metric'] = 1
df2 = pd.DataFrame('modality': pd.Categorical.from_codes(np.random.randint(0, 3, 100), ['a', 'b', 'c']),
                    'Value': np.random.randn(100).cumsum() / 10 + 1)
df2['metric'] = 2

fig, ax1 = plt.subplots()

sns.set_style('white')
palette1 = 'Set2'
palette2 = 'Set1'

sns.violinplot(y=df1.Value, x=df1.modality, hue=df1.metric, palette=palette1, inner="stick", ax=ax1)
for violin in ax1.collections:
    bbox = violin.get_paths()[0].get_extents()
    x0, y0, width, height = bbox.bounds
    violin.set_clip_path(plt.Rectangle((x0, y0), width / 2, height, transform=ax1.transData))
for line in ax1.lines:
    x = line.get_xdata()
    line.set_xdata([x[0], np.mean(x)])

ax1.set_ylabel("metric1")
ax1.set_xlabel("Modality")

ax2 = ax1.twinx()
sns.violinplot(y=df2.Value, x=df2.modality, hue=df2.metric, palette=palette2, inner="stick", ax=ax2)
ylim = ax2.get_ylim()
for violin in ax2.collections:
    bbox = violin.get_paths()[0].get_extents()
    x0, y0, width, height = bbox.bounds
    violin.set_clip_path(plt.Rectangle((x0 + width / 2, y0), width / 2, height, transform=ax2.transData))
for line in ax2.lines:
    x = line.get_xdata()
    line.set_xdata([np.mean(x), x[1]])
ax2.set_ylabel("Metric2")
ax2.set_xticklabels(ax2.get_xticklabels(), rotation=45, ha='right')
ax2.legend(handles=ax1.legend_.legendHandles + ax2.legend_.legendHandles, title='Metric')
ax1.legend_.remove()

fig.tight_layout()
plt.show()

【讨论】:

整洁!为我工作。但是,我希望能对我的方法出错的地方提供一些见解。必须是小事...... 如果没有可重复的数据,很难判断。当然,你需要为 ax2 小提琴剪下另一半,所以violin.set_clip_path(plt.Rectangle((x0+width/2, y0), width/2, height, transform=ax2.transData)。您还需要找到一种方法将相同的剪辑应用到“内部”线。我还认为您应该使用ax1 的x 限制来执行ax2.set_xlim(xlim),并确保“模态”列始终具有相同的分类顺序。

以上是关于使用seaborn分割不同范围的小提琴图的主要内容,如果未能解决你的问题,请参考以下文章

用ggplot2分割小提琴图

如何在seaborn小提琴图中为每个组分配不同的位置

如何手动缩放 Seaborn Violinplot 的计数

在 Seaborn / Matplotlib 的小提琴图上指定高于和低于中位数的颜色

seaborn使用Catplot函数可视化水平小提琴图(Make Horizontal Violin Plot with Catplot in Seaborn)

seaborn使用violinplot函数可视化水平小提琴图(Make Horizontal Violin Plot with violinplot in Seaborn)