可视化统计关系#

统计分析是了解数据集中变量之间的关系以及这些关系如何依赖于其他变量的过程。可视化可以成为此过程的核心组成部分,因为当数据正确可视化时,人类视觉系统可以观察到表明关系的趋势和模式。

在本教程中,我们将讨论三个 Seaborn 函数。我们将最常使用的函数是 relplot()。这是一个用于使用两种常见方法(散点图和线图)可视化统计关系的 图形级函数relplot() 将一个 FacetGrid 与以下两个轴级函数之一结合

正如我们将看到的,这些函数非常有启发性,因为它们使用简单易懂的数据表示,但可以代表复杂的数据集结构。它们之所以能够做到这一点,是因为它们绘制二维图形,可以通过使用色调、大小和样式的语义来映射多达三个额外的变量。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(style="darkgrid")

用散点图关联变量#

散点图是统计可视化的支柱。它使用点云来描绘两个变量的联合分布,其中每个点代表数据集中的一次观测。这种描绘可以让眼睛推断出大量关于它们之间是否存在任何有意义的关系的信息。

在 Seaborn 中,有几种绘制散点图的方法。最基本的方法(当两个变量都是数值时应该使用)是 scatterplot() 函数。在 分类可视化教程 中,我们将看到专门的工具,用于使用散点图可视化分类数据。 scatterplot()relplot() 中的默认 kind(也可以通过设置 kind="scatter" 来强制使用)

tips = sns.load_dataset("tips")
sns.relplot(data=tips, x="total_bill", y="tip")
../_images/relational_4_0.png

虽然点在二维中绘制,但可以通过根据第三个变量对点进行颜色编码来添加另一个维度。在 Seaborn 中,这被称为使用“色调语义”,因为点的颜色具有意义

sns.relplot(data=tips, x="total_bill", y="tip", hue="smoker")
../_images/relational_6_0.png

为了强调类之间的差异,并提高可访问性,您可以为每个类使用不同的标记样式

sns.relplot(
    data=tips,
    x="total_bill", y="tip", hue="smoker", style="smoker"
)
../_images/relational_8_0.png

也可以通过独立更改每个点的色调和样式来表示四个变量。但应谨慎操作,因为眼睛对形状的敏感度远不如对颜色的敏感度

sns.relplot(
    data=tips,
    x="total_bill", y="tip", hue="smoker", style="time",
)
../_images/relational_10_0.png

在上面的示例中,色调语义是分类的,因此应用了默认的 定性调色板。如果色调语义是数值(具体来说,如果它可以转换为浮点数),则默认着色会切换到顺序调色板

sns.relplot(
    data=tips, x="total_bill", y="tip", hue="size",
)
../_images/relational_12_0.png

在这两种情况下,您都可以自定义调色板。有很多方法可以做到这一点。在这里,我们使用 cubehelix_palette() 的字符串接口来自定义顺序调色板

sns.relplot(
    data=tips,
    x="total_bill", y="tip",
    hue="size", palette="ch:r=-.5,l=.75"
)
../_images/relational_14_0.png

第三种语义变量会改变每个点的大小

sns.relplot(data=tips, x="total_bill", y="tip", size="size")
../_images/relational_16_0.png

matplotlib.pyplot.scatter() 不同,变量的字面值不会用于选择点的面积。相反,数据单位的值范围将被归一化为面积单位的范围。此范围可以自定义

sns.relplot(
    data=tips, x="total_bill", y="tip",
    size="size", sizes=(15, 200)
)
../_images/relational_18_0.png

scatterplot() API 示例中,展示了更多用于自定义不同语义如何在显示统计关系中使用的示例。

用线图强调连续性#

散点图非常有效,但没有通用的最佳可视化类型。相反,应根据数据集的具体情况以及您尝试使用图表来回答的问题来调整视觉表示。

对于某些数据集,您可能希望了解一个变量随时间或类似的连续变量的变化情况。在这种情况下,绘制线图是一个不错的选择。在 Seaborn 中,这可以通过 lineplot() 函数直接完成,也可以通过设置 kind="line"relplot() 完成

dowjones = sns.load_dataset("dowjones")
sns.relplot(data=dowjones, x="Date", y="Price", kind="line")
../_images/relational_21_0.png

聚合和表示不确定性#

更复杂的数据集将对同一个 x 值具有多个测量值。Seaborn 中的默认行为是在每个 x 值上聚合多个测量值,方法是绘制平均值以及围绕平均值的 95% 置信区间

fmri = sns.load_dataset("fmri")
sns.relplot(data=fmri, x="timepoint", y="signal", kind="line")
../_images/relational_23_0.png

置信区间是使用自举法计算的,对于较大的数据集来说,这可能很耗时。因此,可以禁用它们

sns.relplot(
    data=fmri, kind="line",
    x="timepoint", y="signal", errorbar=None,
)
../_images/relational_25_0.png

另一个不错的选择(尤其是对于较大的数据来说)是通过绘制标准差而不是置信区间来表示每个时间点的分布范围

sns.relplot(
    data=fmri, kind="line",
    x="timepoint", y="signal", errorbar="sd",
)
../_images/relational_27_0.png

要完全关闭聚合,请将 estimator 参数设置为 None。当数据在每个点上有多个观测值时,这可能会产生奇怪的效果。

sns.relplot(
    data=fmri, kind="line",
    x="timepoint", y="signal",
    estimator=None,
)
../_images/relational_29_0.png

用语义映射绘制数据集#

lineplot() 函数与 scatterplot() 具有相同的灵活性:它可以通过修改绘图元素的色调、大小和样式来显示多达三个额外的变量。它使用与 scatterplot() 相同的 API,这意味着我们不需要停止并考虑控制线条和点在 matplotlib 中的外观的参数。

lineplot() 中使用语义还将决定数据的聚合方式。例如,添加具有两个级别的色调语义将把图表分成两条线和误差带,并为每条线着色以指示它们对应于数据中的哪个子集。

sns.relplot(
    data=fmri, kind="line",
    x="timepoint", y="signal", hue="event",
)
../_images/relational_31_0.png

向线图添加样式语义会默认更改线中的虚线模式

sns.relplot(
    data=fmri, kind="line",
    x="timepoint", y="signal",
    hue="region", style="event",
)
../_images/relational_33_0.png

但您可以通过在每个观测值处使用的标记来识别子集,可以与虚线一起使用,也可以替代虚线

sns.relplot(
    data=fmri, kind="line",
    x="timepoint", y="signal", hue="region", style="event",
    dashes=False, markers=True,
)
../_images/relational_35_0.png

与散点图一样,请注意使用多个语义绘制线图。虽然有时提供信息,但它们也可能难以解析和解释。但是,即使您只检查一个附加变量的变化,修改线条的颜色和样式也很有用。当打印到黑白或由色盲人士查看时,这可以让图表更容易访问

sns.relplot(
    data=fmri, kind="line",
    x="timepoint", y="signal", hue="event", style="event",
)
../_images/relational_37_0.png

当您处理重复测量数据时(也就是说,您有在多个时间点进行采样的单位),您也可以分别绘制每个采样单位,而无需通过语义区分它们。这可以避免图例过于拥挤

sns.relplot(
    data=fmri.query("event == 'stim'"), kind="line",
    x="timepoint", y="signal", hue="region",
    units="subject", estimator=None,
)
../_images/relational_39_0.png

lineplot() 中的默认颜色图和图例处理方式也取决于色调语义是分类的还是数值的

dots = sns.load_dataset("dots").query("align == 'dots'")
sns.relplot(
    data=dots, kind="line",
    x="time", y="firing_rate",
    hue="coherence", style="choice",
)
../_images/relational_41_0.png

可能会出现这种情况,即使 hue 变量是数值的,它也无法用线性颜色比例尺很好地表示。这是因为这里的 hue 变量的级别是按对数尺度缩放的。您可以通过传递列表或字典来为每条线提供特定的颜色值

palette = sns.cubehelix_palette(light=.8, n_colors=6)
sns.relplot(
    data=dots, kind="line",
    x="time", y="firing_rate",
    hue="coherence", style="choice", palette=palette,
)
../_images/relational_43_0.png

或者您可以更改颜色图的归一化方式

from matplotlib.colors import LogNorm
palette = sns.cubehelix_palette(light=.7, n_colors=6)
sns.relplot(
    data=dots.query("coherence > 0"), kind="line",
    x="time", y="firing_rate",
    hue="coherence", style="choice",
    hue_norm=LogNorm(),
)
../_images/relational_45_0.png

第三个语义(大小)会改变线条的宽度

sns.relplot(
    data=dots, kind="line",
    x="time", y="firing_rate",
    size="coherence", style="choice",
)
../_images/relational_47_0.png

虽然 size 变量通常是数值的,但也可以用线条的宽度来映射分类变量。这样做时要谨慎,因为很难区分“粗”和“细”线条以外的线条。但是,当线条具有高频可变性时,虚线可能难以感知,因此在这种情况下使用不同的宽度可能更有效

sns.relplot(
    data=dots, kind="line",
    x="time", y="firing_rate",
    hue="coherence", size="choice", palette=palette,
)
../_images/relational_49_0.png

控制排序和方向#

因为 lineplot() 假设您最常尝试绘制 y 作为 x 的函数,所以默认行为是在绘图之前按 x 值对数据进行排序。但是,可以禁用此功能

healthexp = sns.load_dataset("healthexp").sort_values("Year")
sns.relplot(
    data=healthexp, kind="line",
    x="Spending_USD", y="Life_Expectancy", hue="Country",
    sort=False
)
../_images/relational_51_0.png

也可以沿着 y 轴进行排序(和聚合)。

sns.relplot(
    data=fmri, kind="line",
     x="signal", y="timepoint", hue="event",
    orient="y",
)
../_images/relational_53_0.png

使用 facets 显示多个关系#

在本教程中,我们强调了,虽然这些函数可以一次显示多个语义变量,但这并不总是有效的。但是,如果您想了解两个变量之间的关系如何取决于多个其他变量呢?

最好的方法可能是制作多个绘图。因为relplot() 基于 FacetGrid,这很容易做到。为了显示附加变量的影响,不要将其分配给绘图中的语义角色之一,而是使用它来“facet”可视化。这意味着您制作多个轴并在每个轴上绘制数据的子集。

sns.relplot(
    data=tips,
    x="total_bill", y="tip", hue="smoker", col="time",
)
../_images/relational_55_0.png

您还可以通过这种方式显示两个变量的影响:一个通过在列上进行分面,另一个通过在行上进行分面。当您开始在网格中添加更多变量时,您可能需要减小图形大小。请记住,FacetGrid 的大小由每个 facet的高度和纵横比参数化。

sns.relplot(
    data=fmri, kind="line",
    x="timepoint", y="signal", hue="subject",
    col="region", row="event", height=3,
    estimator=None
)
../_images/relational_57_0.png

当您想检查跨多个变量级别的影响时,将该变量在列上进行分面,然后将 facet “包装”到行中可能是一个好主意。

sns.relplot(
    data=fmri.query("region == 'frontal'"), kind="line",
    x="timepoint", y="signal", hue="event", style="event",
    col="subject", col_wrap=5,
    height=3, aspect=.75, linewidth=2.5,
)
../_images/relational_59_0.png

这些可视化,有时被称为“格子”绘图或“小倍数”,非常有效,因为它们以一种易于人眼检测整体模式和模式偏差的格式呈现数据。虽然您应该利用 scatterplot()relplot() 提供的灵活性,但始终牢记,多个简单的绘图通常比一个复杂的绘图更有效。