Seaborn中文教程(二):分类数据可视化

在统计关系可视化教程中,我们学会了使用多种不同的方式来展示一个数据集中多个变量之间的关系。在一系列的例子中,我们聚焦于那些关系主要存在于两个数值型变量之间的情况。然而当其中一个变量是分类(离散)变量时,我们不妨使用更加有针对性的可视化方法。

seaborn中,有多种不同的方式来展示包含了分类数据的变量关系。正如relplot()scatterplot()/lineplot()之间的关系一样,我们可以使用catplot()函数来描述分类数据,也可以使用更多坐标轴级别的绘图函数来完成这些任务。catplot()提供了对这些axes-level的函数的整合,将他们放在了一个更高级别的统一的接口之中。

我们将分类可视化图形分为三类,并且之后我们会详细探讨他们:

  1. 分类散点图
  • stripplot(): 或catplot(kind="strip")
  • swarmplot(): 或catplot(kind="swarm")
  1. 分类分布图
  • boxplot(): 或catplot(kind="box")
  • violinplot(): 或catplot(kind="violin")
  • boxenplot(): 或catplot(kind="boxen")
  1. 分类统计估计图
  • pointplot(): 或catplot(kind="point")
  • barplot(): 或catplot(kind="bar")
  • countplot(): 或catplot(kind="count")

这些分类从不同的粒度来展示数据。想要知道哪个更合适,就需要我们认真思考我们到底要回答什么问题。catplot()提供的统一的API可以帮助我们轻松地在不同方法间切换并从不同的视角理解数据。

在这篇教程中,我们会主要使用图形级别的整合接口:catplot()。我们已经知道了这个函数是上边那一系列绘图方法的更高级别的整合接口,因此当我们详细讨论到了某种图形时,我们也会深涉及到这些稍底层的方法,这样我们就不会错过那些仅在某些方法中存在的API。

import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="ticks", color_codes=True)

一、分类散点图

catplot()默认的处理方式就是散点图。在绘制分类散点图时,我们会遇到一个挑战,当在同一个类别中出现大量取值相同或接近的观测数据时,他们会挤到一起。seaborn中有两种分类散点图,分别以不同的方式处理了这个问题。catplot()使用的默认方式是stripplot(),它给这些散点增加了一些随机的偏移量:

tips = sns.load_dataset("tips")
sns.catplot(x="day", y="total_bill", data=tips);
image

jitter参数控制着偏移量的大小,或者我们可以直接禁止他们偏移:

sns.catplot(x="day", y="total_bill", jitter=False, data=tips);
image

第二种解决方式使用算法避免了散点之间的重合。它提供了更好的方式来呈现观测点的分布,但是它仅适用于较小的数据集。这种图被叫做蜂群图,在seaborn中我们用swarmplot()或者catplot(kind="swarm")来绘制它:

sns.catplot(x="day", y="total_bill", kind="swarm", data=tips);
image

与关系图(relplot())类似,我们也可以使用hue参数来增加一个新的维度(但是分类图不支持sizestyle)。不同的分类图对于hue参数的处理不太一样,对于散点图而言,它仅仅控制散点的颜色就足够了:

sns.catplot(x="day", y="total_bill", hue="sex", kind="swarm", data=tips);
image

与连续数值型数据不同,如何在坐标轴上对分类变量进行排序并不总是那么显而易见。一般情况下,seaborn分类图函数会尝试推断数据集中分类的顺序。如果你的数据是pandasCategorical类型,那么默认的分类顺序可以在pandas中设置;如果数据看起来是数值型的(比如1,2,3...),那他们也会被排序。但是即使我们使用数字作为不同分类的标签,它们仍然是被当做分类型数据按顺序绘制在分类变量对应的坐标轴上的:

# 注意,2和4之间的距离与1和2之间的距离是一样的,它们是不同的分类,只会排序,但是并不会改变它们在坐标轴上的距离
sns.catplot(x="size", y="total_bill", kind="swarm", data=tips.query("size != 3"));
image

我们还可以通过order参数指定分类的顺序,当我们需要绘制多个分类变量图时这一点会很重要:

sns.catplot(x="smoker", y="tip", order=["No", "Yes"], data=tips);
image

我们前边涉及到过“分类坐标轴”,在这些例子中,我们的分类水平都与水平坐标轴绑定。但是有些时候我们把分类变量放在垂直坐标轴上会更有帮助(尤其是当分类名称较长或者分类较多时)。我们只需要交换x和y分配的变量即可:

sns.catplot(x="total_bill", y="day", hue="time", kind="swarm", data=tips);
image

二、分类分布图

当数据集的大小越来越大,分类散点图在表现不同分类的观测值的分布信息时就越发显得捉襟见肘。此时,我们有一些方法,能以清晰明了的对比方式来总结不同分类下的观测值分布信息。

箱线图

第一种就是我们的老朋友:箱线图。它能在图中展现出数据的上下四分位数、中文数以及一些极值。箱体上下方的须线会分别向上和向下延伸1.5倍IQR(上下四分位数之间的距离),落在这个区域之外的点会单独显示为离群点(异常值)。

sns.catplot(x="day", y="total_bill", kind="box", data=tips);
image

我们可以增加一个hue参数,这样就可以进一步增加一个维度来观察数据分布:

sns.catplot(x="day", y="total_bill", hue="smoker", kind="box", data=tips);
image

这种操作叫做“dodging”,它会默认保持打开,因为它假设hue参数对应的变量与坐标轴上的分类变量是相互嵌套的。假如事实并非如此,我们可以关闭“dodging”:

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

boxenplot()是一个与boxplot()相关的函数,它绘制的是与箱线图相似但是能展示更多关于数据分布形状的信息,它对大数据更加友好:

diamonds = sns.load_dataset("diamonds")
sns.catplot(x="color", y="price", kind="boxen", data=diamonds.sort_values("color"));
image

小提琴图

另一种方法是violinplot(),它将箱线图与核密度估计过程结合了起来:

sns.catplot(x="total_bill", y="day", hue="time", kind="violin", data=tips);
image

这种方法通过和密度估计提供了关于数据分布的更多信息。另外,箱线图中的分位数和须线也都在小提琴图中有所体现。但是由于小提琴图引入了KDE(核密度估计),所以相对于简单明了的箱线图,我们可能需要调整更多的参数。

sns.catplot(x="total_bill", y="day", hue="time",
            kind="violin", bw=.15, cut=0, data=tips);
image

当一个额外的分类变量仅有2个水平时,我们也可以将它赋给hue参数,并且设置split=True,这样我们可以更加充分地利用空间来表达更多信息:

sns.catplot(x="day", y="total_bill", hue="sex",
            kind="violin", split=True, data=tips);
image

在小提琴图中,我们有许多选项可以调整小提琴内部的内容,比如我们可以展示每个观测点的位置而非统计量:

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

我们还可以将swarmplot()stripplot()与箱线图或者小提琴图结合起来,这样我们就可以同时看到每个观测值的分布以及关于分布形态的统计量情况(分位数、极值等):

g = sns.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);
image

三、分类统计估计图

在某些应用场景中,相对于展示每个分类的数据分布,我们可能更想展示每个分类中数据的集中趋势估计(统计量,比如均值、中位数、方差等)。seaborn有两种方式来展示此类信息。需要知道的是,这些函数基本的API与前边提到的那些绘图函数是一致的。

条形图

一种常见的图形是条形图。在seaborn中,barplot()函数在整个数据集上运行,并且应用一个函数来获得那些统计量(默认为均值)。当每个分类中有多个观测值时,它还可以通过自助采样法计算出一个置信区间,并且通过误差棒的方式绘制出来。

titanic = sns.load_dataset("titanic")
sns.catplot(x="sex", y="survived", hue="class", kind="bar", data=titanic);
image

一个特例是我们想要展示每个分类下观测值(样本)的数量而非统计量。这就像是“属于分类变量而非连续变量的直方图”。在seaborn中,我们可以使用countplot()轻易地达成目的:

sns.catplot(x="deck", kind="count", palette="ch:.25", data=titanic);
image

barplot()countplot()在调用的时候支持所有我们在上边讨论过的选项(参数),同时一些额外支持的参数在它们各自的详细文档中可以找到:

sns.catplot(y="deck", hue="class", kind="count",
            palette="pastel", edgecolor=".6",
            data=titanic);
image

点图

我们也可以使用pointplot()来表现同样的信息。点图也使用高度来编码统计量,但是区别在于它不会画出一个完整的长条,而是用一个点以及置信区间来替代;另外,它还会将属于同一个hue分类的点连起来。这样,我们就可以很容易看到hue变量是如何影响坐标轴上的分类变量的(交互作用),因为不同线条的斜率简直是一目了然:

sns.catplot(x="sex", y="survived", hue="class", kind="point", data=titanic);
image

我们前边提到,分类绘图函数是没有style这一参数的(在relplot()中有)。但是我们同样可以修改线条和点的样式,使得我们的图片更有可读性,甚至可以在黑白色调下表现分类信息(考虑到色盲读者时,黑白色调会很有帮助):

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

四、绘制“宽表”数据

虽然我们推荐使用使用长数据或者整齐的数据(类似一维数组),但是宽表类的数据在seaborn中也是得到了支持的。宽表类数据是指二维的数据,比如pandasDataFrame或者numpy的二维数组。这些对象可以直接传递给data参数。

iris = sns.load_dataset("iris")
sns.catplot(data=iris, orient="h", kind="box")
<seaborn.axisgrid.FacetGrid at 0x1a209cb860>
image

另外,那些坐标轴级别的绘图函数(而非图形级别、整合后的API)可以接受类似向量的数据输入(比如pandasSeriesnumpy的一维数组):

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

想要控制上边我们讨论的图形的大小和形状,我们需要使用更底层的matplotlib设置项:

f, ax = plt.subplots(figsize=(7, 3))
sns.countplot(y="deck", data=titanic, color="c");
image

这就是当我们想要让一个分类图在一个复杂图片中与其他子图和平共处时需要采用的方法。

五、使用子图展示多重关系

relplot()一样,calplot()也是基于FacetGrid构建,这意味着我们可以轻易地通过更多子图来表现高维的关系:

sns.catplot(x="day", y="total_bill", hue="smoker",
            col="time", aspect=.6,
            kind="swarm", data=tips);
image

当我们想要定制更多细节时,我们就需要使用FacetGrid对象支持的方法了:

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");
image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容