构建结构化的多图网格#

在探索多维数据时,一个有用的方法是绘制多个相同图形的实例,这些实例基于数据集的不同子集。这种技术有时被称为“格子”或“网格”绘图,它与 “小倍数” 的概念相关。它允许观众快速提取关于复杂数据集的大量信息。Matplotlib 提供了对制作多轴图形的良好支持;seaborn 在此基础上直接将图形的结构与数据集的结构联系起来。

本教程本章中讨论的对象之上构建了 图级别 函数。在大多数情况下,您将希望使用这些函数。它们负责一些重要的簿记工作,这些工作同步了每个网格中的多个图。本章解释了底层对象的工作原理,这对于高级应用可能很有用。

条件小倍数#

当你想要在数据集的子集中分别可视化变量的分布或多个变量之间的关系时,FacetGrid 类非常有用。一个 FacetGrid 可以绘制多达三个维度:rowcolhue。前两个与生成的轴数组有明显的对应关系;将色调变量视为沿深度轴的第三个维度,其中不同级别使用不同颜色绘制。

每个 relplot()displot()catplot()lmplot() 都在内部使用这个对象,并且在完成时返回该对象,以便可以对其进行进一步的调整。

该类通过使用数据框和将形成网格的行、列或色调维度的变量名称来初始化一个 FacetGrid 对象。这些变量应该是分类的或离散的,然后变量在每个级别的数据将被用于该轴上的一个面。例如,假设我们想在 tips 数据集中检查午餐和晚餐之间的差异:

tips = sns.load_dataset("tips")
g = sns.FacetGrid(tips, col="time")
../_images/axis_grids_4_0.png

以这种方式初始化网格会设置 matplotlib 图形和轴,但不会在其中绘制任何内容。

在这个网格上可视化数据的主要方法是使用 FacetGrid.map() 方法。提供一个绘图函数和数据框中要绘制的变量名称。让我们使用直方图查看这些子集中小费的分布情况:

g = sns.FacetGrid(tips, col="time")
g.map(sns.histplot, "tip")
../_images/axis_grids_6_0.png

此函数将绘制图形并标注轴,希望一步完成最终的绘图。要制作关系图,只需传递多个变量名称。您还可以提供关键字参数,这些参数将被传递给绘图函数:

g = sns.FacetGrid(tips, col="sex", hue="smoker")
g.map(sns.scatterplot, "total_bill", "tip", alpha=.7)
g.add_legend()
../_images/axis_grids_8_0.png

有几个选项可以控制传递给类构造函数的网格外观。

g = sns.FacetGrid(tips, row="smoker", col="time", margin_titles=True)
g.map(sns.regplot, "size", "total_bill", color=".3", fit_reg=False, x_jitter=.1)
../_images/axis_grids_10_0.png

请注意,margin_titles 并未正式得到 matplotlib API 的支持,并且在所有情况下可能无法正常工作。特别是,目前它不能与位于图表外部的图例一起使用。

通过提供 每个 分面的高度以及宽高比来设置图形的大小:

g = sns.FacetGrid(tips, col="day", height=4, aspect=.5)
g.map(sns.barplot, "sex", "total_bill", order=["Male", "Female"])
../_images/axis_grids_12_0.png

面的默认排序是从 DataFrame 中的信息推导出来的。如果用于定义面的变量是分类类型,则使用分类的顺序。否则,面将按照类别级别的出现顺序排列。然而,可以通过适当的 *_order 参数指定任何面维度的顺序:

ordered_days = tips.day.value_counts().index
g = sns.FacetGrid(tips, row="day", row_order=ordered_days,
                  height=1.7, aspect=4,)
g.map(sns.kdeplot, "total_bill")
../_images/axis_grids_14_0.png

任何 seaborn 颜色调色板(即可以传递给 color_palette() 的内容)都可以提供。你也可以使用一个字典,将 hue 变量中的值名称映射到有效的 matplotlib 颜色:

pal = dict(Lunch="seagreen", Dinner=".7")
g = sns.FacetGrid(tips, hue="time", palette=pal, height=5)
g.map(sns.scatterplot, "total_bill", "tip", s=100, alpha=.5)
g.add_legend()
../_images/axis_grids_16_0.png

如果你有一个变量的多个层级,你可以沿着列绘制它们,但需要将它们“包裹”起来,以便它们跨越多个行。这样做时,你不能使用 row 变量。

attend = sns.load_dataset("attention").query("subject <= 12")
g = sns.FacetGrid(attend, col="subject", col_wrap=4, height=2, ylim=(0, 10))
g.map(sns.pointplot, "solutions", "score", order=[1, 2, 3], color=".3", errorbar=None)
../_images/axis_grids_18_0.png

一旦你使用 FacetGrid.map() 绘制了一个图(可以多次调用),你可能想要调整图的某些方面。在 FacetGrid 对象上也有许多方法用于在更高层次的抽象上操作图形。最通用的是 FacetGrid.set(),还有其他更专门的方法,如 FacetGrid.set_axis_labels(),它会考虑到内部分面没有轴标签的事实。例如:

with sns.axes_style("white"):
    g = sns.FacetGrid(tips, row="sex", col="smoker", margin_titles=True, height=2.5)
g.map(sns.scatterplot, "total_bill", "tip", color="#334488")
g.set_axis_labels("Total bill (US Dollars)", "Tip")
g.set(xticks=[10, 30, 50], yticks=[2, 6, 10])
g.figure.subplots_adjust(wspace=.02, hspace=.02)
../_images/axis_grids_20_0.png

为了进一步自定义,您可以直接使用底层的 matplotlib FigureAxes 对象,它们分别存储为 figureaxes_dict 成员属性。在创建没有行或列分面的图形时,您还可以使用 ax 属性直接访问单个轴。

g = sns.FacetGrid(tips, col="smoker", margin_titles=True, height=4)
g.map(plt.scatter, "total_bill", "tip", color="#338844", edgecolor="white", s=50, lw=1)
for ax in g.axes_dict.values():
    ax.axline((0, 0), slope=.2, c=".2", ls="--", zorder=0)
g.set(xlim=(0, 60), ylim=(0, 14))
../_images/axis_grids_22_0.png

使用自定义函数#

在使用 FacetGrid 时,您不仅限于现有的 matplotlib 和 seaborn 函数。然而,为了正常工作,您使用的任何函数都必须遵循一些规则:

  1. 它必须绘制到“当前活动”的 matplotlib Axes 上。这对于 matplotlib.pyplot 命名空间中的函数是成立的,如果你想直接使用其方法,可以调用 matplotlib.pyplot.gca() 来获取当前 Axes 的引用。

  2. 它必须接受它在位置参数中绘制的数据。在内部,FacetGrid 将为传递给 FacetGrid.map() 的每个命名位置参数传递一个 Series 数据。

  3. 它必须能够接受 colorlabel 关键字参数,理想情况下,它会对这些参数做一些有用的事情。在大多数情况下,捕获一个通用的 **kwargs 字典并将其传递给底层绘图函数是最简单的。

让我们来看一个可以用绘图的最小函数示例。这个函数将只为每个面片接受一个数据向量:

from scipy import stats
def quantile_plot(x, **kwargs):
    quantiles, xr = stats.probplot(x, fit=False)
    plt.scatter(xr, quantiles, **kwargs)

g = sns.FacetGrid(tips, col="sex", height=4)
g.map(quantile_plot, "total_bill")
../_images/axis_grids_24_0.png

如果我们想要制作一个双变量图,你应该编写函数,使其首先接受 x 轴变量,然后接受 y 轴变量:

def qqplot(x, y, **kwargs):
    _, xr = stats.probplot(x, fit=False)
    _, yr = stats.probplot(y, fit=False)
    plt.scatter(xr, yr, **kwargs)

g = sns.FacetGrid(tips, col="smoker", height=4)
g.map(qqplot, "total_bill", "tip")
../_images/axis_grids_26_0.png

因为 matplotlib.pyplot.scatter() 接受 colorlabel 关键字参数,并且正确地处理它们,我们可以毫不费力地添加一个色调面:

g = sns.FacetGrid(tips, hue="time", col="sex", height=4)
g.map(qqplot, "total_bill", "tip")
g.add_legend()
../_images/axis_grids_28_0.png

有时,尽管如此,您可能希望映射一个函数,该函数在使用 colorlabel 关键字参数时不会按预期工作。在这种情况下,您需要显式地捕获它们并在自定义函数的逻辑中处理它们。例如,这种方法将允许我们映射 matplotlib.pyplot.hexbin(),否则它与 FacetGrid API 的配合不佳:

def hexbin(x, y, color, **kwargs):
    cmap = sns.light_palette(color, as_cmap=True)
    plt.hexbin(x, y, gridsize=15, cmap=cmap, **kwargs)

with sns.axes_style("dark"):
    g = sns.FacetGrid(tips, hue="time", col="time", height=4)
g.map(hexbin, "total_bill", "tip", extent=[0, 50, 0, 10]);
../_images/axis_grids_30_0.png

绘制成对数据关系#

PairGrid 还允许你使用相同的绘图类型快速绘制一个小子图网格,以可视化每个数据。在 PairGrid 中,每行和每列都被分配给一个不同的变量,因此生成的图显示了数据集中每对变量之间的关系。这种样式的图有时被称为“散点图矩阵”,因为这是显示每种关系的常见方式,但 PairGrid 不仅限于散点图。

理解 FacetGridPairGrid 之间的区别很重要。在前者中,每个分面显示的是相同关系在不同变量水平下的条件。在后者中,每个图显示的是不同的关系(尽管上三角和下三角会有镜像图)。使用 PairGrid 可以让你快速、高层次地总结数据集中有趣的关系。

该类的基本用法与 FacetGrid 非常相似。首先初始化网格,然后将绘图函数传递给 map 方法,它将在每个子图上调用。还有一个伴随函数,pairplot(),它在牺牲一些灵活性的同时实现了更快的绘图。

iris = sns.load_dataset("iris")
g = sns.PairGrid(iris)
g.map(sns.scatterplot)
../_images/axis_grids_32_0.png

可以在对角线上绘制不同的函数来显示每列中变量的单变量分布。请注意,尽管如此,轴刻度不会对应于此图的计数或密度轴。

g = sns.PairGrid(iris)
g.map_diag(sns.histplot)
g.map_offdiag(sns.scatterplot)
../_images/axis_grids_34_0.png

一种非常常见的使用方式是根据一个单独的分类变量来为观测值着色。例如,鸢尾花数据集包含三种不同鸢尾花各四个测量值,因此你可以看到它们之间的差异。

g = sns.PairGrid(iris, hue="species")
g.map_diag(sns.histplot)
g.map_offdiag(sns.scatterplot)
g.add_legend()
../_images/axis_grids_36_0.png

默认情况下,数据集中的每个数值列都会被使用,但如果你想的话,你可以专注于特定的关系。

g = sns.PairGrid(iris, vars=["sepal_length", "sepal_width"], hue="species")
g.map(sns.scatterplot)
../_images/axis_grids_38_0.png

在上三角和下三角中使用不同的函数也是可能的,这样可以强调关系的不同方面。

g = sns.PairGrid(iris)
g.map_upper(sns.scatterplot)
g.map_lower(sns.kdeplot)
g.map_diag(sns.kdeplot, lw=3, legend=False)
../_images/axis_grids_40_0.png

对角线上具有恒等关系的方形网格实际上只是一个特例,您可以在行和列中使用不同的变量进行绘制。

g = sns.PairGrid(tips, y_vars=["tip"], x_vars=["total_bill", "size"], height=4)
g.map(sns.regplot, color=".3")
g.set(ylim=(-1, 11), yticks=[0, 5, 10])
../_images/axis_grids_42_0.png

当然,美学属性是可配置的。例如,你可以使用不同的调色板(例如,显示 hue 变量的顺序)并将关键字参数传递给绘图函数。

g = sns.PairGrid(tips, hue="size", palette="GnBu_d")
g.map(plt.scatter, s=50, edgecolor="white")
g.add_legend()
../_images/axis_grids_44_0.png

PairGrid 是灵活的,但为了快速查看数据集,使用 pairplot() 可能会更容易。此函数默认使用散点图和直方图,尽管还会添加其他几种图(目前,您还可以在非对角线上绘制回归图,在对角线上绘制KDE图)。

sns.pairplot(iris, hue="species", height=2.5)
../_images/axis_grids_46_0.png

你也可以通过关键字参数控制绘图的美观性,并且它会返回 PairGrid 实例以便进一步调整。

g = sns.pairplot(iris, hue="species", palette="Set2", diag_kind="kde", height=2.5)
../_images/axis_grids_48_0.png