信息可视化(也叫绘图)是数据分析中最重要的工作之一。Python有许多库进行静态或动态的数据可视化,但这里主要关注matplotlib和基于它的库。
matplotlib
是一个用于创建出版质量图表的桌面绘图包(主要是2D方面)。该项目是由John Hunter于2002年启动的,其目的是为Python
构建一个MATLAB
式的绘图接口。matplotlib
和IPython
社区进行合作,简化了从IPython shell
(包括现在的Jupyter notebook
)进行交互式绘图。matplotlib
支持各种操作系统上许多不同的GUI后端,而且还能将图片导出为各种常见的矢量(vector)和光栅(raster)图:PDF、SVG、JPG、PNG、BMP、GIF等。除了几张,本书中的大部分图都是用它生成的。
对于创建用于打印或网页的静态图形,我建议默认使用matplotlib
和附加的库,比如pandas
和seaborn
。对于交互式图形以便在Web上发布,可以使用Plotly和Boken
学习本章代码案例的最简单方法是在Jupyter notebook
进行交互式绘图。在Jupyter notebook中执行下面的语句:%matplotlib notebook
matplotlib
,并创建简单的图形import matplotlib.pyplot as plt
import numpy as np
data = np.arange(10)
plt.plot(data)
虽然seaborn
这样的库和pandas
的内置绘图函数能够处理许多普通的绘图任务,但如果需要自定义一些高级功能的话就必须学习matplotlib
API。matplotlib
的示例库和文档是学习高级特性的最好资源。
matplotlib
的图像都位于Figure对象中。你可以用plt.figure
创建一个新的Figure,但不能通过空Figure绘图。必须用add_subplot
创建一个或多个subplot
才行:fig = plt.figure()
ax1 = fig.add_subplot(2, 2, 1)
ax2 = fig.add_subplot(2, 2, 2)
ax3 = fig.add_subplot(2, 2, 3)
plt.plot(np.random.randn(50).cumsum(), 'k--') # 在最后一个用过的subplot上进行绘制,隐藏创建figure和subplot的过程
提示:使用Jupyter notebook有一点不同,即每个小窗重新执行后,图形会被重置。因此,对于复杂的图形,,你必须将所有的绘图命令存在一个小窗里。
由fig.add_subplot所返回的对象是AxesSubplot对象,直接调用它们的实例方法就可以在其它空着的格子里面画图了
ax1.hist(np.random.randn(100), bins=20, color='k', alpha=0.3)
ax2.scatter(np.arange(30), np.arange(30) + 3 * np.random.randn(30))
plt.subplots
,它可以创建一个新的Figure,并返回一个含有已创建的subplot
对象的NumPy
数组:fig, axes = plt.subplots(2, 3)
。可以轻松地对axes数组进行索引,就好像是一个二维数组一样,例如axes[0,1]。还可以通过sharex
和sharey
指定subplot
应该具有相同的X轴或Y轴。在比较相同范围的数据时,这也是非常实用的,否则,matplotlib
会自动缩放各图表的界限。subplots_adjust
(也是个顶级函数)方法可以轻而易举地修改间距:subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=None)
,其中wspace
和hspace
用于控制宽度和高度的百分比,可以用作subplot
之间的间距。plot
函数中可以通过字符串来指定颜色和线型:ax.plot(x, y,'g--')
ax.plot(x, y, linestyle='--', color='g')
#CECECE
)在
IPython
和Jupyter
中使用plot?
可以查看文档说明。
matplotlib
可以创建连续线图,在点之间进行插值,因此有时可能不太容易看出真实数据点的位置。标记也可以放到格式字符串中,但标记类型和线型必须放在颜色后面:from numpy.random import randn
plt.plot(randn(30).cumsum(), 'ko--')
plot(randn(30).cumsum(), color='k', linestyle='dashed', marker='o')
drawstyle
选项修改data = np.random.randn(30).cumsum()
plt.plot(data,'k--', label='Default')
plt.plot(data,'k-', drawstyle='steps-post', label='steps-post')
plt.legend(loc='best')
笔记:你必须调用plt.legend(或使用ax.legend,如果引用了轴的话)来创建图例,无论你绘图时是否传递label标签选项。
xlim
、xticks
和xticklabels
之类的方法。它们分别控制图表的范围、刻度位置、刻度标签等。其使用方式有以下两种:plt.xlim()
返回当前的X轴绘图范围)。plt.xlim([0,10])
会将X轴的范围设置为0到10)。AxesSubplot
起作用的。它们各自对应subplot
对象上的两个方法,以xlim
为例,就是ax.get_xlim
和ax.set_xlim
。fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(np.random.randn(1000).cumsum()) # 创建随机漫步数据
ticks = ax.set_xticks([0,250,500,750,1000]) # 改变x轴刻度
labels = ax.set_xticklabels(['one','two','three','four','five'], rotation=30, fontsize='small') # 设置轴标签
ax.set_title('My first matplotlib plot') # 设置题目
ax.set_xlabel('Stages') # 设置轴名称
props ={
'title':'My first matplotlib plot',
'xlabel':'Stages'
}
ax.set(**props) # 设置题目和轴名称
label
或传入label=’nolegend‘
即可。fig = plt.figure(); ax = fig.add_subplot(1,1,1)
ax.plot(randn(1000).cumsum(),'k', label='one')
ax.plot(randn(1000).cumsum(),'k--', label='two')
ax.plot(randn(1000).cumsum(),'k.', label='three')
ax.legend(loc='best') # 必须调用legend方法才能显示图例
ax.text(x, y,'Hello world!', family='monospace', fontsize=10)
ax.add_patch(shp)
将其添加到subplot中:fig = plt.figure()
ax = fig.add_subplot(1,1,1)
rect = plt.Rectangle((0.2,0.75),0.4,0.15, color='k', alpha=0.3)
circ = plt.Circle((0.7,0.2),0.15, color='b', alpha=0.3)
pgon = plt.Polygon([[0.15,0.15],[0.35,0.4],[0.2,0.6]],
color='g', alpha=0.5)
ax.add_patch(rect)
ax.add_patch(circ)
ax.add_patch(pgon)
plt.savefig
可以将当前图表保存到文件。该方法相当于Figure
对象的实例方法savefig
。plt.savefig('figpath.png', dpi=400, bbox_inches='tight')
savefig
并非一定要写入磁盘,也可以写入任何文件型的对象,比如BytesIO
:from io importBytesIO
buffer =BytesIO()
plt.savefig(buffer)
plot_data = buffer.getvalue()
matplotlib
自带一些配色方案,以及为生成出版质量的图片而设定的默认配置信息。几乎所有默认行为都能通过一组全局参数进行自定义,它们可以管理图像大小、subplot边距、配色方案、字体大小、网格类型等。一种Python
编程方式配置系统的方法是使用rc
方法。plt.rc('figure', figsize=(10,10))
rc
的第一个参数是希望自定义的对象,如’figure’、’axes’、’xtick’、’ytick’、’grid’、’legend’等。其后可以跟上一系列的关键字参数。一个简单的办法是将这些选项写成一个字典:font_options ={
'family':'monospace',
'weight':'bold',
'size':'small'
}
plt.rc('font',**font_options)
在pandas
中,我们有多列数据,还有行和列标签。pandas
自身就有内置的方法,用于简化从DataFrame
和Series
绘制图形。另一个库seaborn简化了许多常见可视类型的创建。
提示:引入seaborn会修改matplotlib默认的颜色方案和绘图类型,以提高可读性和美观度。即使你不使用seaborn API,你可能也会引入seaborn,作为提高美观度和绘制常见matplotlib图形的简化方法。
s = pd.Series(np.random.randn(10).cumsum(), index=np.arange(0, 100, 10))
s.plot()
该Series
对象的索引会被传给matplotlib
,并用以绘制X轴。可以通过use_index=False
禁用该功能。X轴的刻度和界限可以通过xticks
和xlim
选项进行调节,Y轴就用yticks
和ylim
。
pandas
的大部分绘图方法都有一个可选的ax参数,它可以是一个matplotlib
的subplot
对象。这使你能够在网格布局中更为灵活地处理subplot的位置。DataFrame
的plot
方法会在一个subplot
中为各列绘制一条线,并自动创建图例df = pd.DataFrame(np.random.randn(10, 4).cumsum(0), columns=['A', 'B', 'C', 'D'], index=np.arange(0, 100, 10))
df.plot()
plot属性包含一批不同绘图类型的方法。例如,df.plot()
等价于df.plot.line()
笔记:plot的其他关键字参数会被传给相应的matplotlib绘图函数,所以要更深入地自定义图表,就必须学习更多有关matplotlib API的知识。
plot.bar()
和plot.barh()
分别绘制水平和垂直的柱状图。这时,Series
和DataFrame
的索引将会被用作X(bar)或Y(barh)刻度fig, axes = plt.subplots(2, 1)
data = pd.Series(np.random.rand(16), index=list('abcdefghijklmnop'))
data.plot.bar(ax=axes[0], color='k', alpha=0.7) # alpha设置透明度
data.plot.barh(ax=axes[1], color='k', alpha=0.7)
df = pd.DataFrame(np.random.rand(6, 4), index=['one', 'two', 'three', 'four', 'five', 'six'], columns=pd.Index(['A', 'B', 'C', 'D'], name='Genus'))
df.plot.bar()
stacked=True
即可为DataFrame
生成堆积柱状图,这样每行的值就会被堆积在一起:df.plot.barh(stacked=True, alpha=0.5)
笔记:柱状图有一个非常不错的用法:利用
value_counts
图形化显示Series
中各值的出现频率,比如s.value_counts().plot.bar()
。
In [75]: tips = pd.read_csv('pydata-book-2nd-edition/')
In [76]: party_counts = pd.crosstab(tips['day'], tips['size'])
In [77]: party_counts
Out[77]:
size 1 2 3 4 5 6
day
Fri 1 16 1 1 0 0
Sat 2 53 18 13 1 0
Sun 0 39 15 18 3 1
Thur 1 48 4 5 1 3
# Not many 1- and 6-person parties
In [78]: party_counts = party_counts.loc[:, 2:5]
In [79]: party_pcts = party_counts.div(party_counts.sum(1), axis=0) # 进行规格化,使得各行的和为1
In [81]: party_pcts.plot.bar()
seaborn
可以减少工作量。seaborn
的绘制函数使用data参数,它可能是pandas
的DataFrame
。其它的参数是关于列的名字。因为一天的每个值有多次观察,柱状图的值是tip_pct
的平均值。绘制在柱状图上的黑线代表95%
置信区间(可以通过可选参数配置)。In [83]: import seaborn as sns
In [84]: %matplotlib inline # 在jupyter中输入,避免无法显示图的问题
In [85]: tips['tip_pct'] = tips['tip'] / (tips['total_bill'] - tips['tip'])
In [86]: tips.head()
Out[86]:
total_bill tip smoker day time size tip_pct
0 16.99 1.01 No Sun Dinner 2 0.063204
1 10.34 1.66 No Sun Dinner 3 0.191244
2 21.01 3.50 No Sun Dinner 3 0.199886
3 23.68 3.31 No Sun Dinner 2 0.162494
4 24.59 3.61 No Sun Dinner 4 0.172069
In [86]: sns.barplot(x='tip_pct', y='day', data=tips, orient='h')
In [87]: sns.barplot(x='tip_pct', y='day', hue='time', data=tips, orient='h') # 根据time列进行颜色区分
In [90]: sns.set(style="whitegrid") # 设置图形外观
tips['tip_pct'].plot.hist(bins=50) # bins表示柱的数量
tips['tip_pct'].plot.density()
seaborn
的distplot
方法绘制直方图和密度图更加简单,还可以同时画出直方图和连续密度估计图。作为例子,考虑一个双峰分布,由两个不同的标准正态分布组成:In [96]: comp1 = np.random.normal(0, 1, size=200)
In [97]: comp2 = np.random.normal(10, 2, size=200)
In [98]: values = pd.Series(np.concatenate([comp1, comp2]))
In [99]: sns.distplot(values, bins=100, color='k')
statsmodels
项目的macrodata
数据集,选择了几个变量,然后计算对数差:In [100]: macro = pd.read_csv('examples/macrodata.csv')
In [101]: data = macro[['cpi', 'm1', 'tbilrate', 'unemp']]
In [102]: trans_data = np.log(data).diff().dropna()
In [103]: trans_data[-5:]
Out[103]:
cpi m1 tbilrate unemp
198 -0.007904 0.045361 -0.396881 0.105361
199 -0.021979 0.066753 -2.277267 0.139762
200 0.002340 0.010286 0.606136 0.160343
201 0.008419 0.037461 -0.200671 0.127339
202 0.008894 0.012202 -0.405465 0.042560
In [104]: sns.regplot('m1', 'unemp', data=trans_data) # 做一个散布图,并加上一条线性回归的线
In [107]: sns.pairplot(trans_data, diag_kind='kde', plot_kws={'alpha': 0.2}) # 生成散布图矩阵,pairplot支持在对角线上放置每个变量的直方图或密度估计
plot_kws
参数可以传递配置选项到非对角线元素上的图形使用。
seaborn
有一个有用的内置函数factorplot
,可以简化制作多种分面图sns.factorplot(x='day', y='tip_pct', hue='time', col='smoker', kind='bar', data=tips[tips.tip_pct < 1])
sns.factorplot(x='day', y='tip_pct', row='time', col='smoker', kind='bar', data=tips[tips.tip_pct < 1]) # 通过给每个时间值添加一行来扩展分面网格
factorplot
支持其它的绘图类型,如盒图(它可以显示中位数,四分位数,和异常值):
sns.factorplot(x='tip_pct', y='day', kind='box', data=tips[tips.tip_pct < 0.5])