seaborn categorical

关系型绘图教程介绍了如何利用不同的可视化方案展示数据集中多个变量的相互关系。在示例中主要关注了两个数值型变量之间的关系。如果两个变量中的一个是类别型,使用专门的可视化方案会更高效

Seaborn有很多不同的方法可以绘制包含类别型数据的相互关系。与relplot()scatterplot()或者lineplot()之间的关系一样,有两种绘图的方式。有很多绘制类别型数据的轴水平函数,也有一个图水平函数catplot()提供高层次的统一界面

可以认为各种类别型绘图函数属于三个不同的家族,下面会展开介绍。它们是:

类别型散点图:

  • stripplot() (默认kind='strip')
  • swarmplot() (kind='swarm')

类别型分布图:

  • boxplot() (kind='box')
  • violinplot() (kind='violin')
  • boxenplot() (kind='boxex')

类别型估计图:

  • pointplot() (kind='point')
  • barplot() (kind='bar')
  • countplot() (kind='count')

这些家族在不同粒度水平上展示数据。要根据想要回答的问题决定选择哪个函数。统一的API便于在不同类型的图片之间进行切换,从不同角度观察数据

在这个教程中,我们主要使用图水平函数catplot()。这个函数是上述一系列函数的高层次界面,在展示各种类型的图片时会参考上述函数,因此要准备好关于这些函数的详细文档

1
2
3
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_theme(style='ticks', color_codes=True)

类别型散点图

catplot()默认展示数据的方式是散点图。Seaborn中实际上有两种类别型散点图。使用散点图展示类别型数据的关键挑战是同一类别的所有点都会在类别标签对应的刻度上排成一列,两种散点图使用不同的方法解决这一挑战。stripplot()catplot()的默认类型,通过小的随机扰动调整各个点在类别轴上的位置:

1
2
tips = sns.load_dataset('tips')
sns.catplot(x='day', y='total_bill', data=tips)

jitter参数控制扰动的幅度,也可以直接关闭这一功能:

1
sns.catplot(x='day', y='total_bill', jitter=False, data=tips)

第二种方法是使用一种算法调整类别轴上点的位置,避免它们相互重叠。虽然这种方法只在小数据集上好用,但是提供了一种更好的可视化分布的方式。这种图有时被称为蜂群图,在seaborn中使用swarmplot()绘制,在catplot()中设置kind='swarm'调用这一方法:

1
sns.catplot(x='day', y='total_bill', kind='swarm', data=tips)

与关系型绘图类似,也可以使用hue语义向类别型绘图添加额外的维度 (类别型绘图目前不支持sizestyle语义)。每种类别型绘图函数处理hue语义的方式也不相同。对于散点图来说,只需要改变点的颜色:

1
sns.catplot(x='day', y='total_bill', hue='sex', kind='swarm', data=tips)

与数值型数据不同,类别变量的取值并不总是存在明显的排列顺序。一般而言,seaborn绘图函数会尽量根据数据推断类别的顺序。如果数据是pandas的Categorical类型,默认顺序与该类别顺序一致。如果传递给类别轴的变量看起来是数值型的,会按照数值进行排序,但是仍然会把该变量当做类别型依次绘制在类别轴上:

1
sns.catplot(x='size', y='total_bill', data=tips)

另一种默认的排序方式是按照各个类别在数据集中出现的顺序。也可以使用order参数在绘图时指定排序:

1
sns.catplot(x='smoker', y='tip', order=['No', 'Yes'], data=tips)

我们多次提到了类别轴的概念。在上面那些例子中,类别轴都是指的横轴。把类别轴放在纵轴上通常也很有用,尤其是在类别的名称比较长或者类别很多的情况下。只要将两个轴的变量进行对调就可以了:

1
sns.catplot(x='total_bill', y='day', hue='time', kind='swarm', data=tips)

同一类别内的数据分布

随着数据量的增加,类别散点图能够提供的关于每个类别内数据的分布信息非常有限。当发生这种情况时,有一些方法可以对数据的分布信息进行总结,从而有助于在各个类别之间进行比较

箱线图

首先是熟悉的boxplot()。这种类型的图片展示了数据分布的三个四分位值和两个极值。胡须 (whiskers)延伸至距离上下四分位1.5倍IQR的位置,超出这个范围的观测单独进行展示。这意味着箱线图中的每一个值都对应着数据中的一个真实观测:

1
sns.catplot(x='day', y='total_bill', kind='box', data=tips)

如果添加hue语义,语义变量每个取值的箱子沿类别轴排列,不会相互重叠:

1
sns.catplot(x='day', y='total_bill', hue='smoker', kind='box', data=tips)

这种行为称为躲闪 (dodging),是默认开启的,因为假定语义变量是嵌套在主类别变量中的。如果数据实际上不是这样,也可以关闭这个功能:

1
2
tips['weekend'] = tips['day'].isin(['Sat', 'Sun'])
sns.catplot(x='day', y='total_bill', hue='weekend', kind='box', dodge=False, data=tips)

一个相关的函数boxenplot()绘图方式与箱线图类似,但是针对展示数据分布形状的更多信息进行了优化。最适合用于比较大的数据集:

1
2
diamonds = sns.load_dataset('diamonds')
sns.catplot(x='color', y='price', kind='boxen', data=diamonds.sort_values('color'))

小提琴图

另一种方法是violinplot(),它结合了箱线图和核密度估计:

1
sns.catplot(x='total_bill', y='day', hue='sex', kind='violin', data=tips)

这种方法使用核密度估计来提供更丰富的数据分布信息。另外,箱线图中的四分位和极值在小提琴图内部也有展示。缺点是,由于小提琴图使用了KDE,有些其它参数需要调整,比箱线图略微复杂:

1
sns.catplot(x='total_bill', y='day', hue='sex', kind='violin', bw=0.15, cut=0, data=tips)

当色调语义只有两种取值时,也可以将小提琴图进行切分,从而更加充分地利用空间:

1
sns.catplot(x='day', y='total_bill', hue='sex', kind='violin', split=True, data=tips)

最后,有些参数可以对小提琴图的内部构造进行调整,包括展示每一个数据点而不是总结性的箱线值:

1
sns.catplot(x='day', y='total_bill', hue='sex', kind='violin', inner='stick', split=True, palette='pastel', data=tips)

把箱线图或者小提琴图与swarmplot()或者stripplot()结合起来也很有用,既能展示每一个数据点,也可以展示分布情况的总结:

1
2
g = catplot(x='day', y='total_bill', kind='violin', inner=None, data=tips)
sns.swarmplot(x='day', y='total_bill', color='k', size=3, data=tips, ax=g.ax)

同一类别内的统计估计

还有一些应用情境可能不是要展示每个类别内数据的分布,而是要展示数据的中心趋势估计。Seaborn有两种主要的方式展示这一信息。重点是,这些函数的基础API与上面讨论过的那些是一致的

条形图

实现这一目标的常见图形是条形图。在seaborn中,barplot()对整个数据集应用一个函数 (默认是计算均值)来获得估计。如果每个类别中包含多个观测,会使用自举法计算估计的置信区间,绘制误差棒:

1
2
titanic = sns.load_dataset('titanic')
sns.catplot(x='sex', y='survived', hue='class', kind='bar', data=titanic)

条形图的一种特殊情况是绘制每个类别中的观测的数量而不是计算另一个变量的统计值。这类似于针对类别变量的直方图,可以使用countplot()函数轻松实现:

1
sns.catplot(x='deck', kind='count', palette='ch:.25', data=titanic)

barplot()countplot()都可以接受上面讨论的所有参数,也可以参考每个函数的详细文档中的其它参数:

1
sns.catplot(y='deck', hue='class', kind='count', palette='pastel', edgecolor='0.6', data=titanic)

点线图

pointplot()函数提供了可视化相同信息的另一种绘图风格。这个函数同样使用另一个轴上的高度代表估计值,但是不展示完整的条形,而是在对应位置绘制点和误差棒。另外,pointplot()会将相同hue类别的点连接起来。这样便于找到主要的相互关系随色调语义的变化,因为眼睛很擅长捕捉斜率的差异:

1
sns.catplot(x='sex', y='survived', hue='class', kind='point', data=titanic)

虽然类别型函数没有关系型函数中的style语义,但是在改变颜色的同时改变标记或者线型使得图片更易理解仍然是个好主意:

1
sns.catplot(x='class', y='survived', hue='sex', palette={'male': 'g', 'female': 'm'}, markers=['^', 'o'], linestyles=['-', '--'], kind='point', data=titanic)

使用宽数据绘图

虽然使用长数据或者干净数据更好,但是这些函数也可以用于各种格式的宽数据,比如pandas数据框和二维numpy数组。这些对象可以直接传递给data参数:

1
2
iris = sns.load_dataset('iris')
sns.catplot(data=iris, orient='h', kind='box')

另外,轴水平函数接受pandas向量或者numpy对象,而不是数据框中的变量名:

1
sns.violinplot(x=iris.species, y=iris.sepal_length)

设置上述函数绘制的图片大小和形状时,需要使用matplotlib命令对图进行初始化:

1
2
f, ax = plt.subplots(figsize=(7, 3))
sns.countplot(y='deck', data=titanic, color='c')

要想把类别图很好地插入到包含其他类型图片的复杂的图中,需要采取这种方式

通过分面展示多个关系

relplot()类似,catplot()是基于FacetGrid构建的,这就意味着可以方便地添加分面变量可视化更高维度的相关性:

1
sns.catplot(x='day', y='total_bill', hue='smoker', col='time', aspect=0.7, kind='swarm', data=tips)

要想对图像进一步定制,可以调用函数返回的FacetGrid对象的方法:

1
2
g = sns.catplot(x='fare', y='survived', row='class', kind='box', orient='h', height=1.5, aspect=4, data=titanic.query('fare > 0'))
g.set(xscale='log')