seaborn 中的一半(不分裂!)小提琴情节

Posted

技术标签:

【中文标题】seaborn 中的一半(不分裂!)小提琴情节【英文标题】:half (not split!) violin plots in seaborn 【发布时间】:2019-05-21 04:44:07 【问题描述】:

目前 seaborn 根据hue 变量通过设置split=True 提供functionality for split violinplots。我想制作一个“半”小提琴图,即每把小提琴的一半被省略的图。这样的图描绘了类似于每个连续变量的 pdf 的内容,仅绘制在每个分类变量的每条垂直线的一侧。

我设法欺骗seaborn 用绘制的值范围之外的额外数据点和额外的虚拟色调来绘制它,但我想知道这是否可以在不实际更改数据集的情况下完成,例如在sns.violinplot() 参数中。

例如,这张图:

由这个 sn-p 创建:

# imports
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

# load dataset from seaborn
datalist = sns.get_dataset_names()
dataset_name = 'iris'
if dataset_name in datalist:
    df = sns.load_dataset(dataset_name)
else:
    print("Dataset with name: " + dataset_name + " was not found in the available datasets online by seaborn.")

# prepare data
df2 = df.append([-999,-999,-999,-999,'setosa'])
df2['huecol'] = 0.0
df2['huecol'].iloc[-1]= -999

# plot
fig = plt.figure(figsize=(6,6))
sns.violinplot(x='species',y="sepal_width",
            split=True, hue ='huecol', inner = 'quartile',
            palette="pastel", data=df2, legend=False)
plt.title('iris')

# remove hue legend
leg = plt.gca().legend()
leg.remove()
plt.ylim([1,5.0])
plt.show()

【问题讨论】:

使用 matplotlib 代替 seaborn,但这可能会有所帮助:***.com/a/29781988 临时修改数据以生成绘图有什么问题? @jpp 也许可以制作一个山脊图seaborn.pydata.org/examples/kde_ridgeplot.html 【参考方案1】:

我一直在寻找与此类似的解决方案,但没有找到任何令人满意的解决方案。我最终多次调用seaborn.kdeplot,因为 violinplot 本质上是一个单边核密度图。

示例

categorical_kde_plot 的函数定义如下

categorical_kde_plot(
    df,
    variable="tip",
    category="day",
    category_order=["Thur", "Fri", "Sat", "Sun"],
    horizontal=False,
)

horizontal=True,输出将如下所示:

代码

import seaborn as sns
from matplotlib import pyplot as plt


def categorical_kde_plot(
    df,
    variable,
    category,
    category_order=None,
    horizontal=False,
    rug=True,
    figsize=None,
):
    """Draw a categorical KDE plot

    Parameters
    ----------
    df: pd.DataFrame
        The data to plot
    variable: str
        The column in the `df` to plot (continuous variable)
    category: str
        The column in the `df` to use for grouping (categorical variable)
    horizontal: bool
        If True, draw density plots horizontally. Otherwise, draw them
        vertically.
    rug: bool
        If True, add also a sns.rugplot.
    figsize: tuple or None
        If None, use default figsize of (7, 1*len(categories))
        If tuple, use that figsize. Given to plt.subplots as an argument.
    """
    if category_order is None:
        categories = list(df[category].unique())
    else:
        categories = category_order[:]

    figsize = (7, 1.0 * len(categories))

    fig, axes = plt.subplots(
        nrows=len(categories) if horizontal else 1,
        ncols=1 if horizontal else len(categories),
        figsize=figsize[::-1] if not horizontal else figsize,
        sharex=horizontal,
        sharey=not horizontal,
    )

    for i, (cat, ax) in enumerate(zip(categories, axes)):
        sns.kdeplot(
            data=df[df[category] == cat],
            x=variable if horizontal else None,
            y=None if horizontal else variable,
            # kde kwargs
            bw_adjust=0.5,
            clip_on=False,
            fill=True,
            alpha=1,
            linewidth=1.5,
            ax=ax,
            color="lightslategray",
        )

        keep_variable_axis = (i == len(fig.axes) - 1) if horizontal else (i == 0)

        if rug:
            sns.rugplot(
                data=df[df[category] == cat],
                x=variable if horizontal else None,
                y=None if horizontal else variable,
                ax=ax,
                color="black",
                height=0.025 if keep_variable_axis else 0.04,
            )

        _format_axis(
            ax,
            cat,
            horizontal,
            keep_variable_axis=keep_variable_axis,
        )

    plt.tight_layout()
    plt.show()


def _format_axis(ax, category, horizontal=False, keep_variable_axis=True):

    # Remove the axis lines
    ax.spines["top"].set_visible(False)
    ax.spines["right"].set_visible(False)

    if horizontal:
        ax.set_ylabel(None)
        lim = ax.get_ylim()
        ax.set_yticks([(lim[0] + lim[1]) / 2])
        ax.set_yticklabels([category])
        if not keep_variable_axis:
            ax.get_xaxis().set_visible(False)
            ax.spines["bottom"].set_visible(False)
    else:
        ax.set_xlabel(None)
        lim = ax.get_xlim()
        ax.set_xticks([(lim[0] + lim[1]) / 2])
        ax.set_xticklabels([category])
        if not keep_variable_axis:
            ax.get_yaxis().set_visible(False)
            ax.spines["left"].set_visible(False)


if __name__ == "__main__":
    df = sns.load_dataset("tips")

    categorical_kde_plot(
        df,
        variable="tip",
        category="day",
        category_order=["Thur", "Fri", "Sat", "Sun"],
        horizontal=True,
    )

【讨论】:

【参考方案2】:

答案很简单,不,不欺骗 seaborn 认为存在 hue 是不可能的。

This answer 展示了如何在 matplotlib 中执行此操作,原则上同样可以应用于 seaborn violinplots,即切掉一半的小提琴路径。

【讨论】:

感谢您的链接,但您能否提供与seaborn 相同的代码?谢谢! 更新:实际上应该不一样,而只是小提琴的一半。您链接中的情节是双面小提琴,这不是 TS 要求的。 @SergeyZakharov 问题中的代码显示了如何使用 seaborn 进行操作。我想我不需要重复它。链接的 matplotlib 代码分别生成小提琴的两侧;我想很明显,如果您只想要半把小提琴,您只需忽略答案的两个部分之一。 现在我明白了如何做到这一点。谢谢。

以上是关于seaborn 中的一半(不分裂!)小提琴情节的主要内容,如果未能解决你的问题,请参考以下文章

如何更好地适应 seaborn 小提琴情节?

python Seaborn - 制作叠加的beeswarm和小提琴情节

如何手动缩放 Seaborn Violinplot 的计数

如何忽略海底小提琴情节中的异常值? [复制]

R语言plotly可视化:plotly可视化分裂的分组小提琴图每个小提琴图内部分为两组数据每个分组占小提琴图的一半(Split violin plot in R with plotly)

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