探索性数据分析实践

Seaborn

Posted by Sein on October 8, 2018

        Seabron是我在Python上用的最多的绘图包。Seabron是为探索性数据分析(EDA)而生,非常易用。比如:无缝对接Pandas、绘图中的很多细节都有默认值,而且效果不错、类和函数的参数设计简单明了,通用性强。matplotlib虽然使用灵活,但是要考虑的细节太多,真要作EDA这种重复性很高的工作往往比较麻烦,所以一般都需要再封装一层,而Seaborn就是这样一个封装。而且大部分情况要比自己造的轮子好用的多。

        我会按照EDA的框架讲解Seaborn的具体实践。当然作为一个绘图包,Seaborn无法解决EDA的所有需求,还要Pandas、Scipy、Matplotlib等其它包的配合。

        值得一提的是,使用EDA图表的是数据分析师本人。对于图表,首先要严格执行统计规范,其次是表达准确。图表中的内容不能太复杂,也不需要漂亮的配色。Seaborn有能力绘制复杂图表,但是我们只需要让一张图表回答一个问题就足够了。

        当前Seaborn更新到0.9.0,所有的内容都基于此版本。


        对于Seaborn,有两个机制要先说一说,首先是主题的预设,主题(theme)能帮助我们解决制图的细节问题,我们只要将最关键的参数传入函数就能直接制表,比如变量的选取、点线面的样式颜色等。当然不同的图表对应的主题和样式也不同,Seaborn提供了很多选择。

        Seaborn主要通过axes_style()设置主题,主题共有5种:darkgrid、whitegrid、dark、white、和ticks。

In [1]:
import numpy as np
		import pandas as pd
		import matplotlib.pyplot as plt
		import seaborn as sns
		
In [2]:
def sinplot(flip=1):
			x = np.linspace(0, 14, 100)
			for i in range(1, 7):
				plt.plot(x, np.sin(x + i * .5) * (7 - i) * flip)
		
In [3]:
sns.set_style("darkgrid")
		sinplot()
		
In [4]:
sns.set_style("whitegrid")
		sinplot()
		
In [5]:
sns.set_style("dark")
		sinplot()
		
In [6]:
sns.set_style("white")
		sinplot()
		
In [7]:
sns.set_style("ticks")
		sinplot()
		

        可以看出,主题包含坐标轴、刻度、背景、网格线等内容。set_style()类似于全局变量。另外Seaborn通过despine()在主题的基础上做“减法”,这样主题的表现力进一步提高。

In [8]:
sinplot()
		sns.despine(offset=10, trim=True)
		

        set_style()除了传入主题名称,还能对rc参数传入字典对主题元素进行调整,适用一些特殊情况,比如对刻度的特别要求等等。

        另外Seaborn提供对图表的整体缩放,定义5种显示尺度paper、notebook、talk、和poster,通过set_context()实现。

In [9]:
sns.set_style("darkgrid")
		sns.set_context("poster")
		sinplot()
		

        Seaborn对主题的设置还算丰富,如果有过于复杂的需求,可以使用sns.set()来支持所有matplotlib设置,但是在EDA中几乎没有必要。Seaborn还提供了调色盘来设置默认配色,有兴趣可以试试,不过默认的足够使用。


        另一个需要说明的机制是网格图。一个数据集一般包含很多个变量,我们第一步要做的通常是绘制一个由多个图组成的网格,让我们对数据集的状况有个大致的了解。然后再仔细研究我们感兴趣的部分。Seaborn提供PairGrid()和pairplot()两个函数,PairGrid()更灵活,应对复杂需求。一般情况下,我们使用pairplot()来迅速绘图。

In [9]:
iris = sns.load_dataset("iris")
		sns.pairplot(iris, hue="species", height=2.5);
		

        进入正题,开始EDA实现:

        实际上Seaborn更偏向数据科学,有些实现不符合统计学的习惯,并且更关注数值型的数据。Seaborn也不直接提供各种分布图表,需要配合scipy使用。

        在开始之前先对数据集进行说明,实例中选取的是Seaborn自带的数据集tips。

In [1]:
import numpy as np
		import pandas as pd
		import matplotlib.pyplot as plt
		import seaborn as sns
		from scipy import stats

		sns.set_style("darkgrid")
		tips = sns.load_dataset("tips")
		
In [2]:
tips.info()
		
<class 'pandas.core.frame.DataFrame'>
		RangeIndex: 244 entries, 0 to 243
		Data columns (total 7 columns):
		total_bill    244 non-null float64
		tip           244 non-null float64
		sex           244 non-null category
		smoker        244 non-null category
		day           244 non-null category
		time          244 non-null category
		size          244 non-null int64
		dtypes: category(4), float64(2), int64(1)
		memory usage: 7.3 KB
		
In [3]:
tips.head()
		
Out[3]:
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4

        tips数据集很小,是某家餐厅的经营记录,总共7个变量,依次为账单金额、小费、用餐者性别、用餐者是否吸烟、星期、就餐时间(中餐、晚餐)、就餐人数。除total_bill,tip之外,全部为分类型变量。数据集的初衷是研究用餐的其他条件如何影响tip。


1、单样本分析

        对于分类型样本,Seaborn通过countplot()绘制计数图。

In [4]:
sns.countplot(x='day', data=tips);
		

        对于单个分类型样本,图表的唯一作用就是直观,意义不大。

        对数值型样本,离群值是首先需要处理的问题,Seaborn通过boxplot()绘制箱线图

In [5]:
sns.boxplot(x='total_bill', data=tips);
		

        箱线图的主要作用是观察离群值,但是离群值的识别是一个很主观的问题,除了数据的含义和产生过程,还取决于分析方法。统计学中针对离群值有大量的研究,对应很多定量的方法,可以多做尝试。箱线图也对数据分布做出一个简练的抽象,当然如果在使用中觉得这种抽象太过简练可以使用增强箱线图和小提琴图。

        Seaborn通过boxenplot()绘制增强箱线图。

In [6]:
sns.boxenplot(x='total_bill', data=tips);
		
'c' argument looks like a single numeric RGB or RGBA sequence, which should be avoided as value-mapping will have precedence in case its length matches with 'x' & 'y'.  Please use a 2-D array with a single row if you really want to specify the same RGB or RGBA value for all points.
		

        增强箱线图解决了箱线图太过抽象导致大数据集信息损失过多的问题,主要差异在于使用了比四分位更细致的划分,并且在处理缺失值的问题上更谨慎。

        另一种是小提琴图,通过violinplot()实现,但是不会用在单变量分析上。小提琴图用PDF来替换秩的分段,显示类似直方图。

        解决离群值问题之后,就需要观察分布情况,最有效的方法是直方图。Seaborn通过distplot()实现

In [7]:
sns.distplot(tips.total_bill, rug=True);
		

        默认情况下,distplot()还输出KDE计算的PDF(高斯核),传入rug参数也可以显示地毯图(Rug plot)。另外,Seaborn提供kdeplot()函数直接绘制PDF。

        对于直方图,有个很重要的参数是分箱数,默认情况下是自动计算的,也可以通过bins参数手动传入。但是需要谨慎对待显示结果,手动分组可能会在视觉上夸张局部,当然具体情况还需要看分析目的,如果要审视局部也可以多做尝试,观察总体最好使用默认分箱并参照PDF。

In [8]:
sns.distplot(tips.total_bill, bins=20, kde=False);
		

        参考上图可以发现,如果要显示频数而不是频率,必须禁用kde参数。也可以理解为使用kde,展示的是PMF而非直方图。

        CDF是展示分布的常用方法,绘制CDF使用前面提到的kdeplot(),Seaborn的实现方法可以理解为先通过KDE生成PDF然后积分获得CDF。

In [9]:
sns.kdeplot(tips.total_bill, cumulative=True);
		

        对于数值型样本,通过经验分布找出对应的分析分布是很重要的一环,常用可视化方法是绘制CCDF、正态概率图等等,Seaborn的解决方案很直接,通过拟合不同类别的分布的PDF,观察最契合的分布种类。scipy包含所有分布类型,有物理意义且常用的分布类型包括:正态分布norm、对数正态分布lognorm、指数分布expon、t分布t、F分布f、卡方分布chi2、贝塔分布beta、伽马分布gamma、泊松分布poisson、超几何分布hypergeom(poisson和hypergeom是离散变量分布)。

        实际操作通过观察PDF和拟合分布来粗选,但是如果要下结论还是用定量方法比较安全。

In [10]:
sns.distplot(tips.total_bill, kde=True, fit=stats.gamma);
		

2、多样本分析

        a. 对于多个分类型样本,统计分析的习惯是绘制列联表,对应卡方检验的输入。条形图可以做展示,不过实际效果反倒不如表格直观。

        列联表可以使用pandas的crosstab()函数;countplot()可以传入hue参数支持两个样本对比。

In [11]:
pd.crosstab(tips.time, tips.day, margins=True)
		
Out[11]:
day Thur Fri Sat Sun All
time
Lunch 61 7 0 0 68
Dinner 1 12 87 76 176
All 62 19 87 76 244
In [12]:
sns.countplot(x='day', hue='time', data=tips);
		

        Seaborn使用catplot()函数最多同时展示4个分类型样本。实际上这也是图表的极限。catplot()是高级函数,支持多种图表,之后还要经常用到。当然在一般情况下,我们会把同时分析的样本数控制在3以内。

In [13]:
sns.catplot(x='day', hue='time', col='sex', data=tips, kind='count');
		

        还有一种情况,就是分类的类别非常多的时候(尤其是连续型数据离散化),往往很难互相比较,更好的方法是热图。Seaborn通过heatmap()函数实现。注意传入的数据是列联表

In [14]:
sns.heatmap(tips.pivot_table(index='size', columns='day', values='total_bill', fill_value=0));
		

        b. 对于数值型的多个独立样本或者配对样本相比较,重点是观测样本间的差异,进而计算检验统计量的观测值。最常用的是直接绘制CDF比较。另外对于一般相关样本,对比一个数值型样本和若干个分类型样本时也是使用相同的方法。

In [15]:
first, second, third = np.random.randn(100), np.random.randn(1000), np.random.randn(50)
		for _ in [first, second, third]:
			sns.kdeplot(_, cumulative=True)
		

        因为CDF是基于秩的图表,所以对样本容量并不敏感,这也是使用CDF的原因之一。另外Seaborn很容易实现在同一张图中绘制多个内容,直接堆叠绘图函数即可。

        c. 对于两个数值型一般相关样本,对应的图表主要是散点图。Seaborn通过scatterplot()实现。

In [16]:
sns.scatterplot(x="total_bill", y="tip", data=tips);
		

        散点图的表现能力十分丰富,可以通过点的颜色、形状、面积来加入更多的内容,但也要适度。

In [17]:
sns.scatterplot(x="total_bill", y="tip", hue="time", style="time", data=tips);