信息可视化(也叫绘图)是数据分析中最重要的⼯作之⼀。它可能是探索过程的⼀部分,例如,帮助我们找出异常值、必要的数据转换、得出有关模型的idea等。另外,做⼀个可交互的数据可视化也许是⼯作的最终⽬标。Python有许多库进⾏静态或动态的数据可视化,但这⾥主要关注于matplotlib(http://matplotlib.org/)和基于它的库。
matplotlib是⼀个⽤于创建出版质量图表的桌⾯绘图包(主要是2D⽅⾯)。该项⽬是由John Hunter于2002年启动的,其⽬的是为Python构建⼀个MATLAB式的绘图接⼝。matplotlib和IPython社区进⾏合作,简化了从IPython shell(包括现在的Jupyternotebook)进⾏交互式绘图。matplotlib⽀持各种操作系统上许多不同的GUI后端,⽽且还能将图⽚导出为各种常⻅的⽮量(vector)和光栅(raster)图:PDF、SVG、JPG、PNG、BMP、GIF等。
随着时间的发展,matplotlib衍⽣出了多个数据可视化的⼯具集,它们使⽤matplotlib作为底层。其中之⼀是seaborn(http://seaborn.pydata.org/)。
学习本节代码案例的最简单⽅法是在Jupyter notebook进⾏交互式绘图。在Jupyter notebook
中执⾏下⾯的语句:
%matplotlib notebook
一、matplotlib API⼊⻔
matplotlib的通常引⼊约定是:
import matplotlib.pyplot as plt
在Jupyter中运⾏%matplotlib notebook(或在IPython中运⾏%matplotlib),就可以创建⼀个简单的图形。如果⼀切设置正确,执行下面代码会看到图9-1:
import numpy as np
data = np.arange(10)
data # 输出:array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
plt.plot(data) # 如下图所示
图9-1 简单的线图
虽然seaborn这样的库和pandas的内置绘图函数能够处理许多普通的绘图任务,但如果需要⾃定义⼀些⾼级功能的话就必须学习matplotlib API。matplotlib的示例库和⽂档是学习⾼级特性的最好资源。
1、Figure和Subplot
matplotlib的图像都位于Figure对象中。你可以⽤plt.figure创建⼀个新的Figure:
fig = plt.figure()
如果⽤的是IPython,这时会弹出⼀个空窗⼝,但在Jupyter中,必须再输⼊更多命令才能看到。plt.figure有⼀些选项,特别是figsize,它⽤于确保当图⽚保存到磁盘时具有⼀定的⼤⼩和纵横⽐。
不能通过空Figure绘图。必须⽤add_subplot创建⼀个或多个subplot才⾏:
ax1 = fig.add_subplot(2, 2, 1)
这条代码的意思是:图像应该是2×2的(即最多4张图),且当前选中的是4个subplot中的第⼀个(编号从1开始)。如果再把后⾯两个subplot也创建出来,最终得到的图像如图9-2所示:
ax2 = fig.add_subplot(2, 2, 2)
ax3 = fig.add_subplot(2, 2, 3)
注意:使⽤Jupyter notebook有⼀点不同,即每个⼩窗重新执⾏后,图形会被重置。因此,对于复杂的图形,你必须将所有的绘图命令存在⼀个⼩窗⾥。
这⾥,我们运⾏同⼀个⼩窗⾥的所有命令:
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([1.5, 3.5, -2, 1.6])),matplotlib就会在最后⼀个⽤过的subplot(如果没有则创建⼀个)上进⾏绘制,隐藏创建figure和subplot的过程。因此,如果我们执⾏下列命令,你就会得到如图9-3所示的结果:
plt.plot(np.random.randn(50).cumsum(), 'k--') # 输出图片:
"k--"是⼀个线型选项,⽤于告诉matplotlib绘制⿊⾊虚线图。上⾯那些由fig.add_subplot所返回的对象是AxesSubplot对象,直接调⽤它们的实例⽅法就可以在其它空着的格⼦⾥⾯画图了,如图9-4所示:
_ = ax1.hist(np.random.randn(100), bins=20, color='k', alpha=0.3) # 在ax1中绘图
ax2.scatter(np.arange(30), np.arange(30) + 3 * np.random.randn(30)) # 在ax2中绘图
图9-4 继续绘制两次之后的图像
可以在matplotlib的⽂档中找到各种图表类型。
创建包含subplot⽹格的figure是⼀个⾮常常⻅的任务,matplotlib有⼀个更为⽅便的⽅法plt.subplots,它可以创建⼀个新的Figure,并返回⼀个含有已创建的subplot对象的NumPy数组:
fig, axes = plt.subplots(2, 3) # 在1个空白图形中产生6个小窗图
axes # 输出如下:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x000001A705330908>,
<matplotlib.axes._subplots.AxesSubplot object at 0x000001A70540D208>,
<matplotlib.axes._subplots.AxesSubplot object at 0x000001A7054364E0>],
[<matplotlib.axes._subplots.AxesSubplot object at 0x000001A70545F7B8>,
<matplotlib.axes._subplots.AxesSubplot object at 0x000001A705979AC8>,
<matplotlib.axes._subplots.AxesSubplot object at 0x000001A7059A3D68>]],
dtype=object)
这是⾮常实⽤的,因为可以轻松地对axes数组进⾏索引,就好像是⼀个⼆维数组⼀样,例如axes[0,1]。你还可以通过sharex和sharey指定subplot应该具有相同的X轴或Y轴。在⽐较相同范围的数据时,这也是⾮常实⽤的,否则,matplotlib会⾃动缩放各图表的界限。有关该⽅法的更多信息,请参⻅表9-1。
表9-1 pyplot.subplots的选项
参数 说明
nrows subplot的行数
ncols subplot的列数
sharex 所有subplot应该使用相同的X轴刻度(调节xlim将会影响所有subplot)
sharey 所有subplot应该使用相同的Y轴刻度(调节ylim将会影响所有subplot)
subplot_kw 用于创建各subplot的关键字字典
**fig_kw 创建figure时的其他关键字,如plt.subplots(2,2,figsize=(8,6))
2、调整subplot周围的间距
默认情况下,matplotlib会在subplot外围留下⼀定的边距,并在subplot之间留下⼀定的间距。间距跟图像的⾼度和宽度有关,因此,如果你调整了图像⼤⼩(不管是编程还是⼿⼯),间距也会⾃动调整。利⽤Figure的subplots_adjust⽅法可以轻⽽易举地修改间距,此外,它也是个顶级函数:
subplots_adjust(left=None, bottom=None, right=None, top=None,
wspace=None, hspace=None)
wspace和hspace⽤于控制宽度和⾼度的百分⽐,可以⽤作subplot之间的间距。
下⾯是⼀个简单的例⼦,其中我将间距收缩到了0(如图9-5所示):
fig, axes = plt.subplots(2, 2, sharex=True, sharey=True)
for i in range(2):
for j in range(2):
axes[i, j].hist(np.random.randn(500), bins=50, color='k', alpha=0.5)
plt.subplots_adjust(wspace=0, hspace=0)
图9-5 各subplot之间没有间距
不难看出,其中的轴标签重叠了。matplotlib不会检查标签是否重叠,所以对于这种情况,你只能⾃⼰设定刻度位置和刻度标签。后⾯⼏节将会详细介绍该内容。
3、颜⾊、标记和线型
matplotlib的plot函数接受⼀组X和Y坐标,还可以接受⼀个表示颜⾊和线型的字符串缩写。例如,要根据x和y绘制绿⾊虚线,你可以执⾏如下代码:
ax.plot(x, y, 'g--')
这种在⼀个字符串中指定颜⾊和线型的⽅式⾮常⽅便。在实际中,如果你是⽤代码绘图,你可能不想通过处理字符串来获得想要的格式。通过下⾯这种更为明确的⽅式也能得到同样的效果:
ax.plot(x, y, linestyle='--', color='g')
常⽤的颜⾊可以使⽤颜⾊缩写,你也可以指定颜⾊码(例如,'#CECECE')。你可以通过查看plot的⽂档字符串查看所有线型的合集(在IPython和Jupyter中使⽤plot?)。
线图可以使⽤标记强调数据点。因为matplotlib可以创建连续线图,在点之间进⾏插值,因此有时可能不太容易看出真实数据点的位置。标记也可以放到格式字符串中,但标记类型和线型必须放在颜⾊后⾯(⻅图9-6):
from numpy.random import randn
plt.plot(randn(30).cumsum(), 'ko--')
图9-6 带有标记的线型图示例
还可以将其写成更为明确的形式:
plt.plot(randn(30).cumsum(), color='k', linestyle='dashed', marker='o')
在线型图中,⾮实际数据点默认是按线性⽅式插值的。可以通过drawstyle选项修改(⻅图9-7):
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')
图9-7 不同drawstyle选项的线型图
在运⾏上⾯代码时有输出<matplotlib.lines.Line2D at...>。matplotlib会返回引⽤了新添加的⼦组件的对象。⼤多数时候,你可以放⼼地忽略这些输出。这⾥,因为传递了label参数到plot,我们可以创建⼀个plot图例,指明每条使⽤plt.legend的线。
注意:你必须调⽤plt.legend(或使⽤ax.legend,如果引⽤了轴的话)来创建图例,⽆论你绘图时是否传递label标签选项。
4、刻度、标签和图例
对于⼤多数的图表装饰项,其主要实现⽅式有⼆:使⽤过程型的pyplot接⼝(例如,matplotlib.pyplot)以及更为⾯向对象的原⽣matplotlib API。
pyplot接⼝的设计⽬的就是交互式使⽤,含有诸如xlim、xticks和xticklabels之类的⽅法。它们分别控制图表的范围、刻度位置、刻度标签等。其使⽤⽅式有以下两种:
调⽤时不带参数,则返回当前的参数值(例如,plt.xlim()返回当前的X轴绘图范围)。
调⽤时带参数,则设置参数值(例如,plt.xlim([0,10])会将X轴的范围设置为0到10)。
所有这些⽅法都是对当前或最近创建的AxesSubplot起作⽤的。它们各⾃对应subplot对象上的两个⽅法,以xlim为例,就是ax.get_xlim和ax.set_xlim。更常使⽤subplot的实例⽅法(因为处理明确的事情,⽽且在处理多个subplot时这样也更清楚⼀些)。当然也可以选择⾃⼰觉得⽅便的那个。
5、设置标题、轴标签、刻度以及刻度标签
为了说明⾃定义轴,我将创建⼀个简单的图像并绘制⼀段随机漫步(如图9-8所示):
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(np.random.randn(1000).cumsum()) # 输出图片:
图9-8 ⽤于演示xticks的简单线型图(带有标签)
要改变x轴刻度,最简单的办法是使⽤set_xticks和set_xticklabels。前者告诉matplotlib要将刻度放在数据范围中的哪些位置,默认情况下,这些位置也就是刻度标签。但我们可以通过set_xticklabels将任何其他的值⽤作标签:
ticks = ax.set_xticks([0, 250, 500, 750, 1000])
labels = ax.set_xticklabels(['one', 'two', 'three', 'four', 'five'],
rotation=30, fontsize='small')
rotation选项设定x刻度标签倾斜30度。最后,再⽤set_xlabel为X轴设置⼀个名称,并⽤set_title设置⼀个标题(⻅图9-9的结果):
ax.set_title('My first matplotlib plot') # 输出:Text(0.5,1,'My first matplotlib plot')
ax.set_xlabel('Stages') # 输出:Text(0.5,10.8739,'Stages')
Y轴的修改⽅式与此类似,只需将上述代码中的x替换为y即可。
轴的类有集合⽅法,可以批量设定绘图选项。前⾯的例⼦,也可以写为:
props = {
'title': 'My first matplotlib plot',
'xlabel': 'Stages',
'ylabel': 'Stages_Y',}
ax.set(**props) # 输出文本内容如下:此时图9-9的显示效果如图9-9-2所示
[Text(71.3472,0.5,'Stages_Y'),
Text(0.5,15.2739,'Stages'),
Text(0.5,1,'My first matplotlib plot')]
图9-9-2 ⽤于演示xticks的简单线型图
6、添加图例
图例(legend)是另⼀种⽤于标识图表元素的重要⼯具。添加图例的⽅式有多种。最简单的是在添加subplot的时候传⼊label参数:
from numpy.random import randn
fig = plt.figure(); ax = fig.add_subplot(1, 1, 1)
ax.plot(randn(1000).cumsum(), 'k', label='one') # 输出:[<matplotlib.lines.Line2D at 0x1ea4d249518>]
ax.plot(randn(1000).cumsum(), 'k--', label='two') # 输出:[<matplotlib.lines.Line2D at 0x1ea4d255b38>]
ax.plot(randn(1000).cumsum(), 'k.', label='three') # 输出:[<matplotlib.lines.Line2D at 0x1ea4d38c160>]
在此之后,你可以调⽤ax.legend()或plt.legend()来⾃动创建图例(结果⻅图9-10):
ax.legend(loc='best') # 输出:<matplotlib.legend.Legend at 0x1ea4d38c0b8>
legend⽅法有⼏个其它的loc位置参数选项。请查看⽂档字符串(使⽤ax.legend?)。loc告诉matplotlib要将图例放在哪。如果没有特别要求的话,"best"是不错的选择,因为它会选择最不碍事的位置。要从图例中去除⼀个或多个元素,不传⼊label或传⼊label='nolegend'即可。
7、注解以及在Subplot上绘图
除标准的绘图类型,你可能还希望绘制⼀些⼦集的注解,可能是⽂本、箭头或其他图形等。
注解和⽂字可以通过text、arrow和annotate函数进⾏添加。text可以将⽂本绘制在图表的指定坐标(x,y),还可以加上⼀些⾃定义格式:
ax.text(x, y, 'Hello world!', family='monospace', fontsize=10)
注解中可以既含有⽂本也含有箭头。例如,我们根据最近的标准普尔500指数价格(来⾃Yahoo!Finance)绘制⼀张曲线图,并标出2008年到2009年⾦融危机期间的⼀些重要⽇期。你可以在Jupyter notebook的⼀个⼩窗中试验这段代码(图9-11是结果):
from datetime import datetime
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
data = pd.read_csv('examples/spx.csv', index_col=0, parse_dates=True)
spx = data['SPX']
spx.plot(ax=ax, style='k-')
crisis_data = [
(datetime(2007, 10, 11), 'Peak of bull market'),
(datetime(2008, 3, 12), 'Bear Stearns Fails'),
(datetime(2008, 9, 15), 'Lehman Bankruptcy')
]
for date, label in crisis_data:
ax.annotate(label, xy=(date, spx.asof(date) + 75),
xytext=(date, spx.asof(date) + 225),
arrowprops=dict(facecolor='black', headwidth=4, width=2, headlength=4),
horizontalalignment='left', verticalalignment='top')
# Zoom in on 2007-2010
ax.set_xlim(['1/1/2007', '1/1/2011'])
ax.set_ylim([600, 1800])
ax.set_title('Important dates in the 2008-2009 financial crisis')
这张图中有⼏个重要的点要强调:ax.annotate⽅法可以在指定的x和y坐标轴绘制标签。
我们使⽤set_xlim和set_ylim⼈⼯设定起始和结束边界,⽽不使⽤matplotlib的默认⽅法。最后,⽤ax.set_title添加图标标题。
更多有关注解的示例,可访问matplotlib的在线示例库。
图形的绘制要麻烦⼀些。matplotlib有⼀些表示常⻅图形的对象。这些对象被称为块(patch)。其中有些(如Rectangle和Circle),可以在matplotlib.pyplot中找到,但完整集合位于matplotlib.patches。
要在图表中添加⼀个图形,你需要创建⼀个块对象shp,然后通过ax.add_patch(shp)将其添加到subplot中(如图9-12所示):
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)
如果查看许多常⻅图表对象的具体实现代码,你就会发现它们其实就是由块patch组装⽽成的。
8、将图表保存到⽂件
利⽤plt.savefig可以将当前图表保存到⽂件。该⽅法相当于Figure对象的实例⽅法savefig。例如,要将图表保存为SVG⽂件,你只需输⼊:
plt.savefig('figpath.svg')
⽂件类型是通过⽂件扩展名推断出来的。因此,如果你使⽤的是.pdf,就会得到⼀个PDF⽂件。在发布图⽚时最常⽤到两个重要的选项是dpi(控制“每英⼨点数”分辨率)和bbox_inches(可以剪除当前图表周围的空⽩部分)。要得到⼀张带有最⼩⽩边且分辨率为400DPI的PNG图⽚,你可以:
plt.savefig('figpath.png', dpi=400, bbox_inches='tight')
savefig并⾮⼀定要写⼊磁盘,也可以写⼊任何⽂件型的对象,⽐如BytesIO:
from io import BytesIO
buffer = BytesIO()
plt.savefig(buffer)
plot_data = buffer.getvalue()
表9-2列出了savefig的其它选项。
表9-2 Figure.savefig的选项
参数 说明
fname 含有文件路径的字符串或Python的文件型对象。图像格式由文件扩展名
推断得出,例如:.pdf推断出PDF,.png推断出PNG
dpi 图像分辨率(每英寸点数),默认为100
facecolor,edgecolor 图像的背景色,默认为“w”(白色)
format 显式设置文件格式("png","pdf","svg","ps","eps", ...)
bbox_inches 图表需要保存的部分。如果设置为"tight",则将尝试剪除图表周围的空白部分
9、matplotlib配置
matplotlib⾃带⼀些配⾊⽅案,以及为⽣成出版质量的图⽚⽽设定的默认配置信息。幸运的是,⼏乎所有默认⾏为都能通过⼀组全局参数进⾏⾃定义,它们可以管理图像⼤⼩、subplot边距、配⾊⽅案、字体⼤⼩、⽹格类型等。⼀种Python编程⽅式配置系统的⽅法是使⽤rc⽅法。例如,要将全局的图像默认⼤⼩设置为10×10,你可以执⾏:
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)
要了解全部的⾃定义选项,请查阅matplotlib的配置⽂件matplotlibrc(位于matplotlib/mpl-data⽬录中)。如果对该⽂件进⾏了⾃定义,并将其放在你⾃⼰的.matplotlibrc⽬录中,则每次使⽤matplotlib时就会加载该⽂件。
接下来会看到,seaborn包有若⼲内置的绘图主题或类型,它们使⽤了matplotlib的内部配置。
二、使⽤pandas和seaborn绘图
matplotlib实际上是⼀种⽐较低级的⼯具。要绘制⼀张图表,你组装⼀些基本组件就⾏:数据展示(即图表类型:线型图、柱状图、盒形图、散布图、等值线图等)、图例、标题、刻度标签以及其他注解型信息。
在pandas中,我们有多列数据,还有⾏和列标签。pandas⾃身就有内置的⽅法,⽤于简化从DataFrame和Series绘制图形。另⼀个库seaborn(https://seaborn.pydata.org/),由Michael Waskom创建的静态图形库。Seaborn简化了许多常⻅可视类型的创建。
提示:引⼊seaborn会修改matplotlib默认的颜⾊⽅案和绘图类型,以提⾼可读性和美观度。即使你不使⽤seaborn API,你可能也会引⼊seaborn,作为提⾼美观度和绘制常⻅matplotlib图形的简化⽅法。
1、线型图
Series和DataFrame都有⼀个⽤于⽣成各类图表的plot⽅法。默认情况下,它们所⽣成的是线型图(如图9-13所示):
s = pd.Series(np.random.randn(10).cumsum(), index=np.arange(0, 100, 10))
s.plot() # 输出图形9-13
该Series对象的索引会被传给matplotlib,并⽤以绘制X轴。可以通过use_index=False禁⽤该功能。X轴的刻度和界限可以通过xticks和xlim选项进⾏调节,Y轴就⽤yticks和ylim。plot参数的完整列表请参⻅表9-3。这里只讲解其中⼏个,剩下的需要⾃⼰去研究了。
表9-3 Series.plot⽅法的参数
参数 说明
label 用于图例的标签
ax 要在其上进行绘制的matplotlib subplot对象。如果没有设置,则使用当前matplot subplot
style 将要传给matplotlib的风格字符串,如 'ko--'
alpha 图表的填充不透明(0到1之间)
kind 可以是 'line', 'bar', 'barh', 'kde'
logy 在Y轴上使用对数标尺
use_index 将对象的索引用作刻度标签
rot 旋转刻度标签(0到360)
xticks 用作X轴的刻度值
yticks 用作Y轴的刻度值
xlim X轴的界限(例如[0, 10])
ylim Y轴的界限
grid 显示轴网格线(默认打开)
pandas的⼤部分绘图⽅法都有⼀个可选的ax参数,它可以是⼀个matplotlib的subplot对象。这使你能够在⽹格布局中更为灵活地处理subplot的位置。
DataFrame的plot⽅法会在⼀个subplot中为各列绘制⼀条线,并⾃动创建图例(如图9-14所示):
df = pd.DataFrame(np.random.randn(10, 4).cumsum(0),
columns=['A', 'B', 'C', 'D'],
index=np.arange(0, 100, 10))
df.plot() # 输出图形9-14
plot属性包含⼀批不同绘图类型的⽅法。例如,df.plot()等价于df.plot.line()。后⾯会介绍这些⽅法。
注意:plot的其他关键字参数会被传给相应的matplotlib绘图函数,所以要更深⼊地⾃定义图表,就必须学习更多有关matplotlib API的知识。
DataFrame还有⼀些⽤于对列进⾏灵活处理的选项,例如,是要将所有列都绘制到⼀个subplot中还是创建各⾃的subplot。详细信息请参⻅表9-4。
表9-4 专⽤于DataFrame的plot参数
参数 说明
subplots 将各个DataFrame列绘制到单独的subplot中(subplots=True)
sharex 如果subplots=True,则共用同一个X轴,包括刻度和界限
sharey 如果subplots=True,则共用同一个Y轴
figsize 表示图像大小的元组
title 表示图像标题的字符串
legend 添加一个subplot图例(默认为True)
sort_columns 以字母表示顺序绘制各列,默认使用当前顺序
2、柱状图
plot.bar()和plot.barh()分别绘制⽔平和垂直的柱状图。这时,Series和DataFrame的索引将会被⽤作X(bar)或Y(barh)刻度(如图9-15所示):
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) # <matplotlib.axes._subplots.AxesSubplot at 0x1ea534fa518>
data.plot.barh(ax=axes[1], color='k', alpha=0.7) # <matplotlib.axes._subplots.AxesSubplot at 0x1ea533d3ac8>
color='k'和alpha=0.7设定了图形的颜⾊为⿊⾊,并使⽤部分的填充透明度。对于DataFrame,柱状图会将每⼀⾏的值分为⼀组,并排显示,如图9-16所示:
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 # 输出如下:
Genus A B C D
one 0.861894 0.619917 0.182926 0.394619
two 0.436444 0.237038 0.938138 0.055486
three 0.803059 0.052139 0.002201 0.479030
four 0.882260 0.951629 0.339795 0.347106
five 0.393330 0.926185 0.958367 0.292699
six 0.700171 0.999124 0.048058 0.399770
df.plot.bar() # 输出图形9-16
注意,DataFrame各列的名称"Genus"被⽤作了图例的标题。
设置stacked=True即可为DataFrame⽣成堆积柱状图,这样每⾏的值就会被堆积在⼀起(如图9-17所示):
df.plot.barh(stacked=True, alpha=0.5) # 输出图形9-17
笔记:柱状图有⼀个⾮常不错的⽤法:利⽤value_counts图形化显示Series中各值的出现频率,⽐如 s.value_counts().plot.bar()。
再以前⾯⽤过的那个有关⼩费的数据集为例,假设我们想要做⼀张堆积柱状图以展示每天各种聚会规模的数据点的百分⽐。我⽤read_csv将数据加载进来,然后根据⽇期和聚会规模创建⼀张交叉表:
tips = pd.read_csv('examples/tips.csv')
tips[:10] # tips的内容如下:
total_bill tip smoker day time size
0 16.99 1.01 No Sun Dinner 2
1 10.34 1.66 No Sun Dinner 3
2 21.01 3.50 No Sun Dinner 3
3 23.68 3.31 No Sun Dinner 2
4 24.59 3.61 No Sun Dinner 4
5 25.29 4.71 No Sun Dinner 4
6 8.77 2.00 No Sun Dinner 2
7 26.88 3.12 No Sun Dinner 4
8 15.04 1.96 No Sun Dinner 2
9 14.78 3.23 No Sun Dinner 2
party_counts = pd.crosstab(tips['day'], tips['size'])
party_counts # 输出如下:
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
不包含1和6人的聚会
party_counts = party_counts.loc[:, 2:5]
然后进⾏规格化,使得各⾏的和为1,并⽣成图表(如图9-18所示):
# 先按行求和,再用每行的单个元素除以该行的和
party_pcts = party_counts.div(party_counts.sum(1), axis=0)
party_pcts # 输出如下:
size 2 3 4 5
day
Fri 0.888889 0.055556 0.055556 0.000000
Sat 0.623529 0.211765 0.152941 0.011765
Sun 0.520000 0.200000 0.240000 0.040000
Thur 0.827586 0.068966 0.086207 0.017241
party_pcts.plot.bar() # 输出图形9-18
于是,通过该数据集就可以看出,聚会规模在周末会变⼤。
对于在绘制⼀个图形之前,需要进⾏合计的数据,使⽤seaborn可以减少⼯作量。⽤seaborn来看每天的⼩费⽐例(图9-19是结果):
import seaborn as sns
tips['tip_pct'] = tips['tip'] / (tips['total_bill'] - tips['tip'])
tips.head() # 输出如下:
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
sns.barplot(x='tip_pct', y='day', data=tips, orient='h') # 输出图形9-19
seaborn的绘制函数使⽤data参数,它可能是pandas的DataFrame。其它的参数是关于列的名字。因为⼀天的每个值有多次观察,柱状图的值是tip_pct的平均值。绘制在柱状图上的⿊线代表95%置信区间(可以通过可选参数配置)。
seaborn.barplot有颜⾊选项,使我们能够通过⼀个额外的值设置(⻅图9-20):
sns.barplot(x='tip_pct', y='day', hue='time', data=tips, orient='h') # 输出图形9-20
注意,seaborn已经⾃动修改了图形的美观度:默认调⾊板,图形背景和⽹格线的颜⾊。你可以⽤seaborn.set在不同的图形外观之间切换:
sns.set(style='whitegrid')
3、直⽅图和密度图
直⽅图(histogram)是⼀种可以对值频率进⾏离散化显示的柱状图。数据点被拆分到离散的、间隔均匀的⾯元中,绘制的是各⾯元中数据点的数量。再以前⾯那个⼩费数据为例,通过在Series使⽤plot.hist⽅法,我们可以⽣成⼀张“⼩费占消费总额百分⽐”的直⽅图(如图9-21所示):
tips['tip_pct'].plot.hist(bins=50) # 输出图形9-21
与此相关的⼀种图表类型是密度图,它是通过计算“可能会产⽣观测数据的连续概率分布的估计”⽽产⽣的。⼀般的过程是将该分布近似为⼀组核(即诸如正态分布之类的较为简单的分布)。因此,密度图也被称作KDE(Kernel Density Estimate,核密度估计)图。使⽤plot.kde和标准混合正态分布估计即可⽣成⼀张密度图(⻅图9-22):
tips['tip_pct'].plot.density() # 输出图片9-22
seaborn的distplot⽅法绘制直⽅图和密度图更加简单,还可以同时画出直⽅图和连续密度估计图。
作为例⼦,考虑⼀个双峰分布,由两个不同的标准正态分布组成(⻅图9-23):
comp1 = np.random.normal(0, 1, size=100)
comp2 = np.random.normal(10, 2, size=100)
values = pd.Series(np.concatenate([comp1, comp2]))
sns.distplot(values, bins=100, color='k') # 输出图形9-23
4、散布图或点图
点图或散布图是观察两个⼀维数据序列之间的关系的有效⼿段。在下⾯这个例⼦中,加载了来⾃statsmodels项⽬的macrodata数据集,选择了⼏个变量,然后计算对数差:
macro = pd.read_csv('examples/macrodata.csv')
data = macro[['cpi', 'm1', 'tbilrate','unemp']]
trans_data = np.log(data).diff().dropna()
trans_data[-5:] # 输出如下:
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
然后可以使⽤seaborn的regplot⽅法,它可以做⼀个散布图,并加上⼀条线性回归的线(⻅图9-24):
sns.regplot('m1', 'unemp', data=trans_data)
<matplotlib.axes._subplots.AxesSubplot at 0x1c11fc48eb8>
plt.title('Changes in log %s versus log %s' % ('m1', 'unemp')) # 输出图形9-24
在探索式数据分析⼯作中,同时观察⼀组变量的散布图是很有意义的,这也被称为散布图矩阵(scatter plot matrix)。纯⼿⼯创建这样的图表很费⼯夫,所以seaborn提供了⼀个便捷的pairplot函数,它⽀持在对⻆线上放置每个变量的直⽅图或密度估计(⻅图9-25):
sns.pairplot(trans_data, diag_kind='kde', plot_kws={'alpha': 0.2}) # 输出图形9-25
图9-25 statsmodels macro data的散布图矩阵
你可能注意到了plot_kws参数。它可以让我们传递配置选项到⾮对⻆线元素上的图形使⽤。
对于更详细的配置选项,可以查阅seaborn.pairplot⽂档字符串。
5、分⾯⽹格(facet grid)和类型数据
要是数据集有额外的分组维度呢?有多个分类变量的数据可视化的⼀种⽅法是使⽤⼩⾯⽹格。
seaborn有⼀个有⽤的内置函数factorplot,可以简化制作多种分⾯图(⻅图9-26):
sns.factorplot(x='day', y='tip_pct', hue='time', col='smoker', kind='bar', data=tips[tips.tip_pct < 1])
图9-26 按照天/时间/吸烟者的⼩费百分⽐
除了在分⾯中⽤不同的颜⾊按时间分组,我们还可以通过给每个时间值添加⼀⾏来扩展分⾯⽹格:
sns.factorplot(x='day', y='tip_pct', row='time', col='smoker', kind='bar', data=tips[tips.tip_pct < 1])
图9-27 按天的tip_pct,通过time/smoker分⾯
factorplot⽀持其它的绘图类型,你可能会⽤到。例如,盒图(它可以显示中位数,四分位数,和异常值)就是⼀个有⽤的可视化类型(⻅图9-28):
sns.factorplot(x='tip_pct', y='day', kind='box',data=tips[tips.tip_pct < 0.5]) # 输出图形9-28
使⽤更通⽤的seaborn.FacetGrid类,可以创建⾃⼰的分⾯⽹格。请查阅seaborn的⽂档(https://seaborn.pydata.org/)。
三、其它的Python可视化⼯具
许多开发⼯作都集中在创建交互式图形以便在Web上发布。利⽤⼯具如
Boken(https://bokeh.pydata.org/en/latest/)和
Plotly(https://github.com/plotly/plotly.py),现在可以创建动态交互图形,⽤于⽹⻚浏览器。
对于创建⽤于打印或⽹⻚的静态图形,建议默认使⽤matplotlib和附加的库,⽐如pandas和seaborn。
对于其它数据可视化要求,学习其它的可⽤⼯具可能是有⽤的。去探索绘图的⽣态系统,因为它将持续发展。