本节,我们将重点学习 matplotlib 的基础绘图功能以及 pandas 的高级可视化功能,这也是现在最为常用、最为稳健,同时功能也非常丰富的数据可视化的解决方案。
# 导入一些需要用到的包
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline
# 配置 pandas
pd.set_option('display.notebook_repr_html', False)
pd.set_option('display.max_columns', 20)
pd.set_option('display.max_rows', 25)
plt.plot(np.random.normal(size=100), np.random.normal(size=100), 'ro')
with mpl.rc_context(rc={'font.family': 'serif', 'font.weight': 'bold', 'font.size': 8}):
fig = plt.figure(figsize=(6,3))
ax1 = fig.add_subplot(121)
ax1.set_xlabel('some random numbers')
ax1.set_ylabel('more random numbers')
ax1.set_title("Random scatterplot")
plt.plot(np.random.normal(size=100), np.random.normal(size=100), 'r.')
ax2 = fig.add_subplot(122)
plt.hist(np.random.normal(size=100), bins=15)
ax2.set_xlabel('sample')
ax2.set_ylabel('cumulative sum')
ax2.set_title("Normal distrubution")
plt.tight_layout()
plt.savefig("normalvars.png", dpi=150)
相对于 matplotlib 来讲,Pandas 支持 DataFrame 和 Series 两种比较高级的数据结构,可以想象得到用其绘制出图像的形式。
normals = pd.Series(np.random.normal(size=10))
normals.plot()
normals.cumsum().plot(grid=False)
对于 DataFrame 结构的数据,也可以进行类似的操作:
variables = pd.DataFrame({'normal': np.random.normal(size=100),
'gamma': np.random.gamma(1, size=100),
'poisson': np.random.poisson(size=100)})
variables.cumsum(0).plot()
Pandas 还支持这样的操作:利用参数“subplots”绘制 DataFrame 中每个序列对应的子图
variables.cumsum(0).plot(subplots=True)
或者,我们也可以绘制双坐标轴,将某些序列用次坐标轴展示,这样可以展示更多细节并且图像中没有过多空白。
variables.cumsum(0).plot(secondary_y='normal')
如果你有更多的绘图要求,也可以直接利用 matplotlib 的 “subplots” 方法,手动设置图像位置:
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(12, 4))
for i,var in enumerate(['normal','gamma','poisson']):
variables[var].cumsum(0).plot(ax=axes[i], title=var)
axes[0].set_ylabel('cumulative sum')
我们常用柱状图来展示和对比可测数据,比如计数或流量统计。在 Pandas 中,我们通过设置“plot”方法“kind='bar”参数,即可绘制柱状图。 下面我们以泰坦尼克数据集为例,进行相关绘图说明。
titanic = pd.read_excel("data/titanic.xls", "titanic")
titanic.head()
titanic.groupby('pclass').survived.sum().plot(kind='bar')
titanic.groupby(['sex','pclass']).survived.sum().plot(kind='barh')
death_counts = pd.crosstab([titanic.pclass, titanic.sex], titanic.survived.astype(bool))
death_counts.plot(kind='bar', stacked=True, color=['black','gold'], grid=False)
还可以通过除以该组的总人数来对比不同组之间的存活率
death_counts.div(death_counts.sum(1).astype(float), axis=0).plot(kind='barh', stacked=True, color=['black','gold'])
在数据分析之前,频数/率可以帮助我们了解数据的分布情况。直方图是一种展示数据频数/率的特殊的柱状图,也就是说,y 轴是频数/率的度量,既可以是频数(计数)也可以是频率(占比)。比如,我们想知道泰坦尼克船票价格的分布情况:
titanic.fare.hist(grid=False)
“hist”方法默认将连续票价划分为 10 个区间,我们可以通过设置参数“bins”来设定区间个数(或者说设定直方图的宽度):
titanic.fare.hist(bins=30)
有很多种计算最优区间个数的算法,且都会一定程度的随观测值个数变化。
sturges = lambda n: int(np.log2(n) + 1)
square_root = lambda n: int(np.sqrt(n))
from scipy.stats import kurtosis
doanes = lambda data: int(1 + np.log(len(data)) + np.log(1 + kurtosis(data) * (len(data) / 6.) ** 0.5))
n = len(titanic)
sturges(n), square_root(n), doanes(titanic.fare.dropna())
titanic.fare.hist(bins=doanes(titanic.fare.dropna()))
和直方图类似,密度图也描述了数据的分布情况,可以看成将直方图区间无限细分后形成的平滑曲线。设置“plot”方法的参数 kind='kde',即可绘制密度图,其中 'kde' 表示“kernel density estimate”。
titanic.fare.dropna().plot(kind='kde', xlim=(0,600))
titanic.fare.hist(bins=doanes(titanic.fare.dropna()), normed=True, color='lightseagreen')
titanic.fare.dropna().plot(kind='kde', xlim=(0,600), style='r--')
这里我们需要通过设置参数“normed=True”将直方图标准化处理,因为概率密度分布是标准化的,
另外一种展示数据分布的可视化图形是箱线图,主要展示分位数,具体包括上四分位数、下四分位数、中位数以及上下5%的极值。
titanic.boxplot(column='fare', by='pclass', grid=False)
可以将箱线图看成数据的分布图,图中“+”的点表示异常值。
可以将实际数据同时展示在箱线图中来丰富图像信息,这种做法比较适合观测值较少的数据集。
bp = titanic.boxplot(column='age', by='pclass', grid=False)
for i in [1,2,3]:
y = titanic.age[titanic.pclass==i].dropna()
# 通过设置随机值,使观测值在x轴方向不重叠
x = np.random.normal(i, 0.04, size=len(y))
plt.plot(x, y, 'r.', alpha=0.2)
当数据分布非常稠密时,有以下两种方式来增强图形的可读性: 1、降低透明度,使数据点半透明 2、增加数据点 x 轴方向的随机距离,减少数据点之间的重叠
一个和箱线图相近但远不及箱线图的图像叫做“dynamite”图,本质上就是给出标准偏差的柱状图
titanic.groupby('pclass')['fare'].mean().plot(kind='bar', yerr=titanic.groupby('pclass')['fare'].std())
为什么我们比较少用这种绘图方式呢?
相比之下,箱线图是更好的选择。
data1 = [150, 155, 175, 200, 245, 255, 395, 300, 305, 320, 375, 400, 420, 430, 440]
data2 = [225, 380]
fake_data = pd.DataFrame([data1, data2]).transpose()
p = fake_data.mean().plot(kind='bar', yerr=fake_data.std(), grid=False)
fake_data = pd.DataFrame([data1, data2]).transpose()
p = fake_data.mean().plot(kind='bar', yerr=fake_data.std(), grid=False)
x1, x2 = p.xaxis.get_majorticklocs()
plt.plot(np.random.normal(x1, 0.01, size=len(data1)), data1, 'ro')
plt.plot([x2]*len(data2), data2, 'ro')
用泰坦尼克数据集,绘制幸存者和死亡者年龄的密度图
下面以棒球数据集为例,讲解如何绘制散点图。
baseball = pd.read_csv("data/baseball.csv")
baseball.head()
在探索变量之间的关系=时,可以绘制散点图。Series 或者 DataFrame 结构的数据并没有相应的绘制散点图的方法,必须使用 matplotlib 的“scatter”函数。
plt.scatter(baseball.ab, baseball.h)
plt.xlim(0, 700); plt.ylim(0, 200)
可以通过赋予样本点不同的大小和颜色来展示更多的信息。
plt.scatter(baseball.ab, baseball.h, s=baseball.hr*10, alpha=0.5)
plt.xlim(0, 700); plt.ylim(0, 200)
plt.scatter(baseball.ab, baseball.h, c=baseball.hr, s=40, cmap='hot')
plt.xlim(0, 700); plt.ylim(0, 200);
可以使用“scatter_matrix”函数同时展示多个变量之间的散点图,最终会生成一个两两对应的散点图矩阵,可以选择在对角线展示直方图或者密度图。
_ = pd.scatter_matrix(baseball.loc[:,'r':'sb'], figsize=(12,8), diagonal='kde')
最近 Pandas 增加了利用 GofG 方法的绘图函数,使得栅栏图的绘制变得十分简单。栅栏图可以用于展示两个变量在不同条件下的关系,实现了利用平面图形展示高于两个维度的信息。
下面利用泰坦尼克数据集绘制一个栅栏图来同时展示 4 个变量之间的关系,有以下 4 个步骤: 1、创建一个仅和数据集中两个变量有关的“RPlot” 2、利用“passenger class ”和“sex”两个变量的不同取值最为条件区分,添加网格 3、增加用于可视化的实际数据 4、可视化展示
from pandas.tools.rplot import *
titanic = titanic[titanic.age.notnull() & titanic.fare.notnull()]
tp = RPlot(titanic, x='age')
tp.add(TrellisGrid(['pclass', 'sex']))
tp.add(GeomDensity())
_ = tp.render(plt.gcf())
利用张力障碍数据集,将“age”和“twstrs”两个变量之间的关系作为“week”和“treat”两个变量的函数,通过绘制散点图,我们可以同时探索不同情境下二者之间的关系,并进行拟合:
cdystonia = pd.read_csv("data/cdystonia.csv", index_col=None)
cdystonia.head()
plt.figure(figsize=(12,12))
bbp = RPlot(cdystonia, x='age', y='twstrs')
bbp.add(TrellisGrid(['week', 'treat']))
bbp.add(GeomScatter())
bbp.add(GeomPolyFit(degree=2))
_ = bbp.render(plt.gcf())
RPlot 不仅仅可以被用来绘制栅栏图,也可以通过使用不同的颜色、大小、形状等将多个变量绘制在一起。
cdystonia['site'] = cdystonia.site.astype(float)
plt.figure(figsize=(6,6))
cp = RPlot(cdystonia, x='age', y='twstrs')
cp.add(GeomPoint(colour=ScaleGradient('site', colour1=(1.0, 1.0, 0.5), colour2=(1.0, 0.0, 0.0)),
size=ScaleSize('week', min_size=10.0, max_size=200.0),
shape=ScaleShape('treat')))
_ = cp.render(plt.gcf())