seaborn grids

在探索多维数据时,一种有用的方法是绘制多个实例,用相同的图片类型展示数据的不同子集。这种技术有时被称为格子图或者小多图。它能够帮助观察者从复杂的数据集中快速提取出大量信息。Matplotlib为使用多个轴绘图提供了良好的支持,seaborn基于此构建,能够直接将图的结构与数据集的结构联系起来

图水平函数是基于本章讨论的对象构建的。在大多数情况下可以使用这些图水平函数。它们进行了很多重要的统计,使得每个格子中的图能够同步。这一章将解释底层对象的工作原理,从而辅助对它们的进阶应用

条件小多图

要想对数据集中的子集单独可视化某个变量的分布或者多个变量之间的关系,FacetGrid类非常有用。一个FacetGrid可以在至多三个维度上进行绘制:rowcolhue。前两个明显与产生的轴的排列相关,色调变量可以认为是沿深度轴的第三维,不同取值使用不同颜色绘制

relplot()displot()catplot()lmplot()内部都是使用这一对象,运行完毕后将这一对象返回,用来进行后续调整

这个类使用一个数据框初始化一个FacetGrid对象,变量名会形成网格的行、列和颜色维度。这些变量应该是类别型或离散型,然后变量每个取值的数据会沿该轴进行分面。例如,我们想要评估tips数据集中午餐和晚餐的区别:

1
2
tips = sns.load_dataset('tips')
g = sns.FacetGrid(tips, col='time')

像这样初始化各自会建立matplotlib的图和轴,但不会在上面绘制任何东西

在格子上可视化数据的主要方法是FacetGrid.map()。向这个方法传入一个绘图函数和数据框中的变量名就可以进行绘制。让我们用直方图看一下每个子集的小费分布:

1
2
g = sns.FacetGrid(tips, col='time')
g.map(sns.histplot, 'tip')

这个函数会绘制图像并且对轴进行注释,有望一步产生可用的图片。如果要绘制关系图,只需要传入多个变量名。也可以提供关键字参数,这些参数会被传递给绘图函数:

1
2
3
g = sns.FacetGrid(tips, col='sex', hue='smoker')
g.map(sns.scatterplot, 'total_bill', 'tip', alpha=0.7)
g.add_legend()

有些选项可以控制格子的样子,并且别传递到类的构造函数:

1
2
g = sns.FacetGrid(tips, row='smoker', col='time', margin_titles=True)
g.map(sns.regplot, 'size', 'total_bill', color='0.3', fit_reg=False, x_jitter=0.1)

注意,matplotlib API并不完全支持margin_titles,不一定在所有情况下都能正常使用。尤其是目前不能用于图外有图例的情况

图的大小通过提供每个子图的高度和纵横比进行设置:

1
2
g = sns.FacetGrid(tips, col='day', height=4, aspect=0.5)
g.map(sns.barplot, 'sex', 'total_bill', order=['Male', 'Female'])

分面的默认排序是根据数据框中的信息进行推断的。如果用于分面的变量是类别型,就使用类别的顺序。否则,分面会按照每个取值出现的顺序排列。然而,也可以使用*_order参数指定对应分面维度的顺序:

1
2
3
ordered_days = tips.day.value_counts().index
g = sns.FacetGrid(tips, row='days', row_order=ordered_days, height=1.7, aspect=4)
g.map(sns.kdeplot, 'total_bill')

FacetGrid可以接受各种seaborn调色板 (例如,可以传递给color_palette()的内容)。也可以使用字典定义hue变量中的取值与matplotlib颜色的映射关系:

1
2
3
4
pal = dict(Lunch='seagreen', Dinner='0.7')
g = sns.FacetGrid(tips, hue='time', palette=pal, height=5)
g.map(sns.scatterplot, 'total_bill', 'tip', s=100, alpha=0.5)
g.add_legend()

如果一个变量有很多种取值,可以按列绘制,但是使这些子图跨越多行。这样做的话不能使用row参数:

1
2
3
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='0.3', ci=None)

使用FacetGrid.map() (可以调用多次)绘制之后,可能需要对图像进行调整。FacetGrid对象有很多方法能够在高度抽象的水平上对图片进行操作。最常用的是FacetGrid.set(),其它更具体的方法包括FacetGrid.set_axis_labels()等,用来处理分面本身没有坐标轴标签的问题。例如:

1
2
3
4
5
6
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 (USD Dollars)', 'Tip')
g.set(xticks=[10, 30, 50], yticks=[2, 6, 10])
g.figure.subplots_adjust(wspace=0.02, hspace=0.02)

如果还需要进一步定制,可以直接使用底层的matplotlibFigureAxes对象,它们分别被保存为figureaxes_dict属性。在不使用行列分面时,可以使用ax属性直接获取单个轴:

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

使用自定义函数

在使用FacetGrid时,并不是只能传入已有的matplotlib函数和seaborn函数。然而,要想不报错,使用的函数需要遵循以下规则:

  1. 必须绘制在当前活跃的matplotlib的Axes上。这也是对matplotlib.pyplot命名空间里的函数的要求,如果想直接使用Axes的方法,可以调用matplotlib.pyplot.gca()获取当前的Axes
  2. 必须能够接受位置参数作为绘图数据。在FacetGrid内部会为传递给FacetGrid.map()的每一个命名的位置参数传入一个数据Series
  3. 必须接受colorlabel关键字参数,最好能充分利用这两个参数。在大多数情况下,使用**kwargs很容易获取一个常规的字典,并将其传递给底层绘图函数

来看一个能够用来绘图的最简单的例子。这个函数使用一个向量作为每个子图的数据:

1
2
3
4
5
6
7
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')

如果想绘制双变量图,应该定义一个函数,使它可以把x轴变量放在最前面,把y轴变量放在第二位:

1
2
3
4
5
6
7
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')

由于matplotlib.pyplot.scatter()可以接受colorlabel关键字参数,因此可以毫不费力地添加色调分面:

1
2
3
g = sns.FacetGrid(tips, hue='time', col='sex', height=4)
g.map(qqplot, 'total_bill', 'tip')
g.add_legend()

有时候,你可能不想按照colorlabel关键字变量默认的方式使用这两个参数。在这种情况下,你想要精确地捕捉这两个参数,按照自定义的方式处理它们。例如,这种方法允许我们使用本来不能用于FacetGrid API的matplotlib.pyplot.hexbin()

1
2
3
4
5
6
7
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])

绘制成对数据的相互关系

PairGrid也可以快速绘制一系列子图,每个子图使用相同的图像类型对数据进行可视化。在PairGrid中,每一行或者每一列被赋值了不同的变量,所以绘制的图片展示了数据集中变量两两之间的关系。这种风格的图有时候称为散点图矩阵,因为这是展示每个相关性最简单的方法,但是PairGrid并不局限于散点图

理解FacetGridPairGrid之间的差异非常重要。对于FacetGrid来说,每个子图展示的是同一种相互关系随另一个变量不同取值的变化。而PairGrid每个子图绘制的是不同的相互关系 (上三角和下三角的图是镜像关系)。使用PairGrid可以快速获得数据集中相互关系的高层次汇总

PairGrid类的基础用法与FacetGrid类似。首先初始化格子,然后向map方法传入绘图函数。pairplot()函数消除了一些复杂性,可以用于快速绘图:

1
2
3
iris = sns.load_dataset('iris')
g = sns.PairGrid(iris)
g.map(sns.scatterplot)

可以在对角线上使用另一个函数绘制,来展示每一列的单变量分布。注意,这种图里的坐标轴刻度并不对应计数或者密度:

1
2
3
g = sns.PairGrid(iris)
g.map_diag(sns.histplot)
g.map_offdiag(sns.scatterplot)

上图的一种常见用法是通过一个单独的类别变量为观测数据填充颜色。例如,鸢尾花数据集包含三个不同的鸢尾花品种,每个品种都有四个测量值,可以看看这三个品种有哪些不同:

1
2
3
4
g = sns.PairGrid(iris, hue='species')
g.map_diag(sns.histplot)
g.map_offdiag(sns.scatterplot)
g.add_legend()

默认使用数据集中所有的数值型变量绘图,但是也可以只关注特定变量之间的关系:

1
2
g = sns.PairGrid(iris, vars=['sepal_length', 'sepal_width'], hue='species')
g.map(sns.scatterplot)

还可以对上三角和下三角应用不同的绘图函数,来突出相关性的不同视角:

1
2
3
4
g = sns.PairGrid(iris)
g.map_upper(sns.scatterplot)
g.map_lower(sns.kedplot)
g.map_diag(sns.kdeplot, lw=3, legend=False)

格子呈正方形、对角线上的关系一致只是一种特殊情况,你可以在行和列中使用不同的变量进行绘制:

1
2
3
g = sns.PairGrid(tips, y_vars=['tip'], x_vars=['total_bill', 'size'], height=4)
g.map(sns.regplot, color='0.3')
g.set(ylim=(-1, 11), yticks=[0, 5, 10])

当然,图的美学属性也可以调整。例如,可以使用不同的调色板,并把关键字参数传入绘图函数:

1
2
3
g = sns.PairGrid(tips, hue='size', palette='GnBu_d')
g.map(plt.scatter, s=50, edgecolor='white')
g.add_legend()

PairGrid非常灵活,但是,要想快速可视化一个数据集,使用pairplot()更方便。这个函数默认使用散点图和直方图,也可以使用一些其它类型的图 (目前可以在对角线上绘制KDE,在非对角线上绘制回归图):

1
sns.pairplot(iris, hue='species', diag_kind='kde', height=2.5)

可以使用关键字参数控制美学映射,返回PairGrid实例用于后续调整:

1
g = sns.pairplot(iris, hue='species', palette='Set2', diag_kind='kde', height=2.5)