Seaborn中文教程(一):可视化变量间的关系

众所周知,Seaborn“可能”是Python下最友好、易用的可视化工具了,可视化效果也非常好。但是截止目前,并没有一份中文教程供广大国内Python使用者查阅学习。怎么能因为语言的问题,让大家错过这么好用的一个可视化工具呢?

思考再三,我决定花一些时间将官方的英文文档整理出来,为大家提供一份最权威的中文教程。考虑到我的时间比较碎片化,这项工作可能会在未来的几周内完成,感兴趣的朋友可以先关注和收藏下,后续的更新会在简书和我的个人博客中(www.data-insights.cn/www.data-insight.cn)发布。

今天,我整理了第一部分:如何用Seaborn可视化变量之间的关系,这一部分非常实用,基本上学习后马上就能给我们带来很多帮助。那么接下来,就开始我们的学习吧!

文章较长,建议收藏后在PC站或者我的个人博客阅读。


统计分析是这样的一个过程:尝试去理解一个数据集中变量之间的关系,以及这些关系如何受到其他变量的影响。可视化是这个过程的核心元素,因为当数据以非常恰当的方式展示出来时,我们可以非常直观地观察到某些趋势或者模式,而这些,就揭示了变量之间的关系。

在这篇教程中,我们会讨论三个seaborn函数。我们用的最多的是relplot()。这是一个图形级别的函数,它用散点图和线图两种常用的手段来表现统计关系。relplot()使用两个坐标轴级别的函数来结合了FacetGrid

  • scatterplot():(使用kind="scatter",这是默认参数)
  • lineplot():(使用`kind="line")

正如我们所见,这些函数非常直白,因为它们用了简单易懂的展示方式,却可以呈现复杂的数据集结构。这是因为通过huesizestyle参数,我们可以在二维图形中表现出更多的变量(除了x轴和y轴的两个,还可以通过不同的方式额外展示3个变量)。

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

一、用散点图展示相关变量

散点图是统计图形中的中流砥柱。它用一系列的散点将两个变量的联合分布描绘出来,其中每个点就是一个观测样本。这种描述方式可以让我们从视觉上推断出大量信息,来判断两个变量之间是否存在某种有意义的关系。

seaborn中,我们有数种方法可以实现散点图的绘制。最基本的一种适用于两个变量都是数值型变量的情况,它就是scatterplot()。在分类可视化教程中,我们会看到如何绘制分类数据的散点图。relplot()的默认类型(kind)就是scatterplot()(当然,我们也可以强制指定参数kind="scatter",这和不指定这一参数时效果是一样的)。

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

当我们已经将散点绘制在二维的平面上时,我们还可以根据第三个变量来对这些点施以不同的颜色,从而引入一个新的维度。在seaborn中,我们用hue参数实现了这种想法,因为点的颜色是有意义的。

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

如果我们想要强调不同分类之间的差异,同时增加易读性,我们可以对不同的分类使用不同的标记样式:

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

我们也可以同时展示四个变量,只需要将huestyle参数单独调整到不同的分类变量即可。但是我们要谨慎使用这种方法,因为我们的眼睛对于形状的敏感性远远不如对颜色的敏感性。

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

在上边这个例子中,hue参数对应的变量是分类型数据,因此seaborn自动为它应用了默认的定性(分类)调色板。如果hue参数对应的变量是数值型的(可转化为浮点数的),那么默认的颜色也会随之变为连续的定量调色板。

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

上述两种情况下(分类或连续数据),我们都可以自定义我们的调色板。有很多选项可以实现这一目的。下面,我们使用cubehelix_palette()的字符串接口来定制我们的连续调色板:

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

我们还可以使用点的大小来引入第三个额外的变量:

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

matplotlib.pyplot.scatter()不同的是,这里并不是使用原始数据中的数值来为每个点选择面积大小,seaborn将原始数据归一化(正则化)到了某个范围,这个范围可以由我们来指定:

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

更多关于如何定制不同参数来展示统计关系的示例可以在scatterplot()的API中找到。

二、使用线图表现连续性

散点图很高效,但是没有哪种可视化类型可以完美应对所有情况。事实上,我们的可视化呈现方式要适应数据集的种类以及我们想要通过图形回答的问题。

在某些数据集中,我们可能想要理解某个变量随着时间的变化规律,或者想要理解某个连续型的变量。这种情况下,线图会是一个不错的选择。在seaborn中,我们可以通过lineplot()函数或者使用带有kind="line"参数的relplot()来实现线图的绘制。

df = pd.DataFrame(dict(time=np.arange(500), value=np.random.randn(500).cumsum()))
g = sns.relplot(x="time", y="value", kind="line", data=df)
g.fig.autofmt_xdate()
png

由于lineplot()假设用户在大多数情况下是在尝试描绘y相对于x的函数(变化规律),因此它在绘制之前会默认先对x做一个排序。不过我们可以禁止它。

df = pd.DataFrame(np.random.randn(500, 2).cumsum(axis=0), columns=["x", "y"])
sns.relplot(x="x", y="y", sort=False, kind="line", data=df);
png

聚合并展示不确定性

在更多复杂的数据集中,会出现一个x轴变量对应了多个观测值(y)的情况。seaborn会默认将多个观测值聚合起来,并且将它们的均值以及95%的置信区间展示出来:

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

置信区间是通过自助采样法(bootstrapping)计算的,这在遇到大型数据集时可以帮助我们节省时间。当然,我们也可以禁止它。

sns.relplot(x="timepoint", y="signal", ci=None, kind="line", data=fmri);
png

另一个不错的选择是,我们可以用标准差替代置信区间来展示每个时间点下观测值的分布,当数据集比较大时这一选择尤其明智。

sns.relplot(x="timepoint", y="signal", kind="line", ci="sd", data=fmri);
png

如果想要关闭所有的聚合操作,我们可以设置estimator=None。不过当同一时间点存在多个观测值时,我们的图会看起来有些奇怪。

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

通过参数映射可视化数据子集

lineplot()scatterplot()一样具有很强的灵活性:它也可以通过huesizestyle参数来展示额外的三个变量。它和scatterplot()使用了相同的API,因此我们不需要停下来绞尽脑汁地思考哪些参数是用来控制线条、哪些参数是用来控制散点。

使用不同的参数会决定我们的数据如何聚合。比如,增加一个具有两个水平的分类变量作为hue参数,会将我们的图形分为两条线以及两个误差带,并分别施以不同的颜色来区分数据的分类归属。

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

我们可以增加一个style参数,以不同的线条样式来展示不同的分类:

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

我们还可以设置不同分类的标记样式,标记样式既可以和线条样式同时设置,也可以各自单独设置。

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

跟散点图一样,我们要慎重使用这些参数来展示太多变量。有些时候它们会展示丰富的信息,但是有些时候它们会使图形太过复杂导致我们难以解析和解释它。然而当你仅打算考虑额外的一个变量时,同时修改它们的颜色和样式会很有帮助。当需要考虑到色盲人群时,将图形颜色设置为黑白色调则是一个不错的选择。

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

当我们需要应对重复测量的数据时,我们可以将不同的抽样单元(单次实验观测到的数据系列)分离开来展示,这并不需要我们使用一个语义参数(hue/style/size)。后者会导致图例看起来像一个灾难(想象一下几十个分类的情况):

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

scatterplot()类似,lineplot()默认的调色板以及图例处理方式也取决于hue对应的数据是分类型还是连续数值型。

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

hue参数对应的变量的数据是均匀分布在对数刻度上的(即数据分布范围非常大,比如从1到1亿),即使是连续的调色板也无法很好地应对这种情况。但是我们可以使用列表或者字典对每条线指定一个颜色。

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

或者我们可以直接修改调色板数值的正则方式:

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

不要忘了我们还有个size参数,它用来修改线条的宽度:

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

size一般接受连续数值型变量,但是我们也可以传入分类型变量。但是要慎重考虑这种做法,因为这样比“粗线 vs. 细线”的区分难多了。然而,当数据具有非常高频的变异性时,我们使用style表现的不同线条样式会很难区分,这时使用不同的线条宽度就是一个更高效的选择了:

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

绘制时间序列数据

线图常用来描绘日期、时间相关的诗句。这些方法以原始格式传入更底层的matplotlib函数中,这样它们就可以利用matplotlib的能力来格式化日期数据。但是所有的时间格式化过程都是在matplotlib层实现的,想要知道更多实现的细节就需要去看一下matplotlib中关于这部分的文档:

df = pd.DataFrame(dict(time=pd.date_range("2017-1-1", periods=500),
                       value=np.random.randn(500).cumsum()))
g = sns.relplot(x="time", y="value", kind="line", data=df)
g.fig.autofmt_xdate()
png

三、用更多子图展示多重关系

前边我们已经强调过,虽然我们可以在一张图中展示数个不同的语义变量(通过hue/style/size参数),但是这么做并不是总会高效。那么当我们真的很想理解在某些额外变量的影响下两个变量之间的关系有什么不同时怎么办呢?

最好的办法就是画更多的图。由于relplot()是基于FacetGrid的,因此这很容易做到。当我们想要表现出一个额外变量的影响时,我们可以不用将它赋给前边提到的语义参数(hue/style/size),而是用它来将图形“面板”化。这意味着我们会创建多个坐标轴,分别用来绘制不同的子数据集:

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

我们还可以同时使用col(列)和row(行)参数来展示两个变量的影响。当我们在图中增加了更多的变量时(会有更多的子图),我们可能会想要调整图形的大小。要记住在FacetGrid中,我们用height(子图高度)和aspect(高宽比)来定制每个子图的大小:

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

当我们想要检验某个具有大量水平的变量的影响时,我们可以将这个变量赋给col参数,同时我们通过col_wrap参数设置每行达到多少列就换行:

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

这种常被叫做“格子图”或“small-multiples”的可视化方式,非常高效,因为它们呈现数据的方式使得我们很容易同时发现整体的模式以及不同模式之间的偏差。当你需要利用scatterplot()relplot()的灵活性来表现更多信息时,一定要记住,多幅简单的图通常比一幅复杂的图更加高效。

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