第1章 準(zhǔn)備工作
第2章 Python語法基礎(chǔ),IPython和Jupyter
第3章 Python的數(shù)據(jù)結(jié)構(gòu)、函數(shù)和文件
第4章 NumPy基礎(chǔ):數(shù)組和矢量計(jì)算
第5章 pandas入門
第6章 數(shù)據(jù)加載、存儲與文件格式
第7章 數(shù)據(jù)清洗和準(zhǔn)備
第8章 數(shù)據(jù)規(guī)整:聚合、合并和重塑
第9章 繪圖和可視化
第10章 數(shù)據(jù)聚合與分組運(yùn)算
第11章 時間序列
第12章 pandas高級應(yīng)用
第13章 Python建模庫介紹
第14章 數(shù)據(jù)分析案例
附錄A NumPy高級應(yīng)用
附錄B 更多關(guān)于IPython的內(nèi)容(完)
信息可視化(也叫繪圖)是數(shù)據(jù)分析中最重要的工作之一。它可能是探索過程的一部分,例如,幫助我們找出異常值、必要的數(shù)據(jù)轉(zhuǎn)換、得出有關(guān)模型的idea等。另外,做一個可交互的數(shù)據(jù)可視化也許是工作的最終目標(biāo)。Python有許多庫進(jìn)行靜態(tài)或動態(tài)的數(shù)據(jù)可視化,但我這里重要關(guān)注于matplotlib(http://matplotlib.org/)和基于它的庫。
matplotlib是一個用于創(chuàng)建出版質(zhì)量圖表的桌面繪圖包(主要是2D方面)。該項(xiàng)目是由John Hunter于2002年啟動的,其目的是為Python構(gòu)建一個MATLAB式的繪圖接口。matplotlib和IPython社區(qū)進(jìn)行合作,簡化了從IPython shell(包括現(xiàn)在的Jupyter notebook)進(jìn)行交互式繪圖。matplotlib支持各種操作系統(tǒng)上許多不同的GUI后端,而且還能將圖片導(dǎo)出為各種常見的矢量(vector)和光柵(raster)圖:PDF、SVG、JPG、PNG、BMP、GIF等。除了幾張,本書中的大部分圖都是用它生成的。
隨著時間的發(fā)展,matplotlib衍生出了多個數(shù)據(jù)可視化的工具集,它們使用matplotlib作為底層。其中之一是seaborn(http://seaborn.pydata.org/),本章后面會學(xué)習(xí)它。
學(xué)習(xí)本章代碼案例的最簡單方法是在Jupyter notebook進(jìn)行交互式繪圖。在Jupyter notebook中執(zhí)行下面的語句:
%matplotlib notebook
9.1 matplotlib API入門
matplotlib的通常引入約定是:
In [11]: import matplotlib.pyplot as plt
在Jupyter中運(yùn)行%matplotlib notebook(或在IPython中運(yùn)行%matplotlib),就可以創(chuàng)建一個簡單的圖形。如果一切設(shè)置正確,會看到圖9-1:
In [12]: import numpy as np
In [13]: data = np.arange(10)
In [14]: data
Out[14]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [15]: plt.plot(data)
雖然seaborn這樣的庫和pandas的內(nèi)置繪圖函數(shù)能夠處理許多普通的繪圖任務(wù),但如果需要自定義一些高級功能的話就必須學(xué)習(xí)matplotlib API。
筆記:雖然本書沒有詳細(xì)地討論matplotlib的各種功能,但足以將你引入門。matplotlib的示例庫和文檔是學(xué)習(xí)高級特性的最好資源。
Figure和Subplot
matplotlib的圖像都位于Figure對象中。你可以用plt.figure創(chuàng)建一個新的Figure:
In [16]: fig = plt.figure()
如果用的是IPython,這時會彈出一個空窗口,但在Jupyter中,必須再輸入更多命令才能看到。plt.figure有一些選項(xiàng),特別是figsize,它用于確保當(dāng)圖片保存到磁盤時具有一定的大小和縱橫比。
不能通過空Figure繪圖。必須用add_subplot創(chuàng)建一個或多個subplot才行:
In [17]: ax1 = fig.add_subplot(2, 2, 1)
這條代碼的意思是:圖像應(yīng)該是2×2的(即最多4張圖),且當(dāng)前選中的是4個subplot中的第一個(編號從1開始)。如果再把后面兩個subplot也創(chuàng)建出來,最終得到的圖像如圖9-2所示:
In [18]: ax2 = fig.add_subplot(2, 2, 2)
In [19]: ax3 = fig.add_subplot(2, 2, 3)
提示:使用Jupyter notebook有一點(diǎn)不同,即每個小窗重新執(zhí)行后,圖形會被重置。因此,對于復(fù)雜的圖形,,你必須將所有的繪圖命令存在一個小窗里。
這里,我們運(yùn)行同一個小窗里的所有命令:
fig = plt.figure()
ax1 = fig.add_subplot(2, 2, 1)
ax2 = fig.add_subplot(2, 2, 2)
ax3 = fig.add_subplot(2, 2, 3)
如果這時執(zhí)行一條繪圖命令(如plt.plot([1.5, 3.5, -2, 1.6])),matplotlib就會在最后一個用過的subplot(如果沒有則創(chuàng)建一個)上進(jìn)行繪制,隱藏創(chuàng)建figure和subplot的過程。因此,如果我們執(zhí)行下列命令,你就會得到如圖9-3所示的結(jié)果:
In [20]: plt.plot(np.random.randn(50).cumsum(), 'k--')
"k--"是一個線型選項(xiàng),用于告訴matplotlib繪制黑色虛線圖。上面那些由fig.add_subplot所返回的對象是AxesSubplot對象,直接調(diào)用它們的實(shí)例方法就可以在其它空著的格子里面畫圖了,如圖9-4所示:
In [21]: ax1.hist(np.random.randn(100), bins=20, color='k', alpha=0.3)
In [22]: ax2.scatter(np.arange(30), np.arange(30) + 3 * np.random.randn(30))
你可以在matplotlib的文檔中找到各種圖表類型。
創(chuàng)建包含subplot網(wǎng)格的figure是一個非常常見的任務(wù),matplotlib有一個更為方便的方法plt.subplots,它可以創(chuàng)建一個新的Figure,并返回一個含有已創(chuàng)建的subplot對象的NumPy數(shù)組:
In [24]: fig, axes = plt.subplots(2, 3)
In [25]: axes
Out[25]:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x7fb626374048>,
<matplotlib.axes._subplots.AxesSubplot object at 0x7fb62625db00>,
<matplotlib.axes._subplots.AxesSubplot object at 0x7fb6262f6c88>],
[<matplotlib.axes._subplots.AxesSubplot object at 0x7fb6261a36a0>,
<matplotlib.axes._subplots.AxesSubplot object at 0x7fb626181860>,
<matplotlib.axes._subplots.AxesSubplot object at 0x7fb6260fd4e0>]], dtype
=object)
這是非常實(shí)用的,因?yàn)榭梢暂p松地對axes數(shù)組進(jìn)行索引,就好像是一個二維數(shù)組一樣,例如axes[0,1]。你還可以通過sharex和sharey指定subplot應(yīng)該具有相同的X軸或Y軸。在比較相同范圍的數(shù)據(jù)時,這也是非常實(shí)用的,否則,matplotlib會自動縮放各圖表的界限。有關(guān)該方法的更多信息,請參見表9-1。
調(diào)整subplot周圍的間距
默認(rèn)情況下,matplotlib會在subplot外圍留下一定的邊距,并在subplot之間留下一定的間距。間距跟圖像的高度和寬度有關(guān),因此,如果你調(diào)整了圖像大小(不管是編程還是手工),間距也會自動調(diào)整。利用Figure的subplots_adjust方法可以輕而易舉地修改間距,此外,它也是個頂級函數(shù):
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)
不難看出,其中的軸標(biāo)簽重疊了。matplotlib不會檢查標(biāo)簽是否重疊,所以對于這種情況,你只能自己設(shè)定刻度位置和刻度標(biāo)簽。后面幾節(jié)將會詳細(xì)介紹該內(nèi)容。
顏色、標(biāo)記和線型
matplotlib的plot函數(shù)接受一組X和Y坐標(biāo),還可以接受一個表示顏色和線型的字符串縮寫。例如,要根據(jù)x和y繪制綠色虛線,你可以執(zhí)行如下代碼:
ax.plot(x, y, 'g--')
這種在一個字符串中指定顏色和線型的方式非常方便。在實(shí)際中,如果你是用代碼繪圖,你可能不想通過處理字符串來獲得想要的格式。通過下面這種更為明確的方式也能得到同樣的效果:
ax.plot(x, y, linestyle='--', color='g')
常用的顏色可以使用顏色縮寫,你也可以指定顏色碼(例如,'#CECECE')。你可以通過查看plot的文檔字符串查看所有線型的合集(在IPython和Jupyter中使用plot?)。
線圖可以使用標(biāo)記強(qiáng)調(diào)數(shù)據(jù)點(diǎn)。因?yàn)閙atplotlib可以創(chuàng)建連續(xù)線圖,在點(diǎn)之間進(jìn)行插值,因此有時可能不太容易看出真實(shí)數(shù)據(jù)點(diǎn)的位置。標(biāo)記也可以放到格式字符串中,但標(biāo)記類型和線型必須放在顏色后面(見圖9-6):
In [30]: from numpy.random import randn
In [31]: plt.plot(randn(30).cumsum(), 'ko--')
還可以將其寫成更為明確的形式:
plot(randn(30).cumsum(), color='k', linestyle='dashed', marker='o')
在線型圖中,非實(shí)際數(shù)據(jù)點(diǎn)默認(rèn)是按線性方式插值的。可以通過drawstyle選項(xiàng)修改(見圖9-7):
In [33]: data = np.random.randn(30).cumsum()
In [34]: plt.plot(data, 'k--', label='Default')
Out[34]: [<matplotlib.lines.Line2D at 0x7fb624d86160>]
In [35]: plt.plot(data, 'k-', drawstyle='steps-post', label='steps-post')
Out[35]: [<matplotlib.lines.Line2D at 0x7fb624d869e8>]
In [36]: plt.legend(loc='best')
你可能注意到運(yùn)行上面代碼時有輸出<matplotlib.lines.Line2D at ...>。matplotlib會返回引用了新添加的子組件的對象。大多數(shù)時候,你可以放心地忽略這些輸出。這里,因?yàn)槲覀儌鬟f了label參數(shù)到plot,我們可以創(chuàng)建一個plot圖例,指明每條使用plt.legend的線。
筆記:你必須調(diào)用plt.legend(或使用ax.legend,如果引用了軸的話)來創(chuàng)建圖例,無論你繪圖時是否傳遞label標(biāo)簽選項(xiàng)。
刻度、標(biāo)簽和圖例
對于大多數(shù)的圖表裝飾項(xiàng),其主要實(shí)現(xiàn)方式有二:使用過程型的pyplot接口(例如,matplotlib.pyplot)以及更為面向?qū)ο蟮脑鷐atplotlib API。
pyplot接口的設(shè)計(jì)目的就是交互式使用,含有諸如xlim、xticks和xticklabels之類的方法。它們分別控制圖表的范圍、刻度位置、刻度標(biāo)簽等。其使用方式有以下兩種:
- 調(diào)用時不帶參數(shù),則返回當(dāng)前的參數(shù)值(例如,plt.xlim()返回當(dāng)前的X軸繪圖范圍)。
- 調(diào)用時帶參數(shù),則設(shè)置參數(shù)值(例如,plt.xlim([0,10])會將X軸的范圍設(shè)置為0到10)。
所有這些方法都是對當(dāng)前或最近創(chuàng)建的AxesSubplot起作用的。它們各自對應(yīng)subplot對象上的兩個方法,以xlim為例,就是ax.get_xlim和ax.set_xlim。我更喜歡使用subplot的實(shí)例方法(因?yàn)槲蚁矚g明確的事情,而且在處理多個subplot時這樣也更清楚一些)。當(dāng)然你完全可以選擇自己覺得方便的那個。
設(shè)置標(biāo)題、軸標(biāo)簽、刻度以及刻度標(biāo)簽
為了說明自定義軸,我將創(chuàng)建一個簡單的圖像并繪制一段隨機(jī)漫步(如圖9-8所示):
In [37]: fig = plt.figure()
In [38]: ax = fig.add_subplot(1, 1, 1)
In [39]: ax.plot(np.random.randn(1000).cumsum())
要改變x軸刻度,最簡單的辦法是使用set_xticks和set_xticklabels。前者告訴matplotlib要將刻度放在數(shù)據(jù)范圍中的哪些位置,默認(rèn)情況下,這些位置也就是刻度標(biāo)簽。但我們可以通過set_xticklabels將任何其他的值用作標(biāo)簽:
In [40]: ticks = ax.set_xticks([0, 250, 500, 750, 1000])
In [41]: labels = ax.set_xticklabels(['one', 'two', 'three', 'four', 'five'],
....: rotation=30, fontsize='small')
rotation選項(xiàng)設(shè)定x刻度標(biāo)簽傾斜30度。最后,再用set_xlabel為X軸設(shè)置一個名稱,并用set_title設(shè)置一個標(biāo)題(見圖9-9的結(jié)果):
In [42]: ax.set_title('My first matplotlib plot')
Out[42]: <matplotlib.text.Text at 0x7fb624d055f8>
In [43]: ax.set_xlabel('Stages')
Y軸的修改方式與此類似,只需將上述代碼中的x替換為y即可。軸的類有集合方法,可以批量設(shè)定繪圖選項(xiàng)。前面的例子,也可以寫為:
props = {
'title': 'My first matplotlib plot',
'xlabel': 'Stages'
}
ax.set(**props)
添加圖例
圖例(legend)是另一種用于標(biāo)識圖表元素的重要工具。添加圖例的方式有多種。最簡單的是在添加subplot的時候傳入label參數(shù):
In [44]: from numpy.random import randn
In [45]: fig = plt.figure(); ax = fig.add_subplot(1, 1, 1)
In [46]: ax.plot(randn(1000).cumsum(), 'k', label='one')
Out[46]: [<matplotlib.lines.Line2D at 0x7fb624bdf860>]
In [47]: ax.plot(randn(1000).cumsum(), 'k--', label='two')
Out[47]: [<matplotlib.lines.Line2D at 0x7fb624be90f0>]
In [48]: ax.plot(randn(1000).cumsum(), 'k.', label='three')
Out[48]: [<matplotlib.lines.Line2D at 0x7fb624be9160>]
在此之后,你可以調(diào)用ax.legend()或plt.legend()來自動創(chuàng)建圖例(結(jié)果見圖9-10):
In [49]: ax.legend(loc='best')
legend方法有幾個其它的loc位置參數(shù)選項(xiàng)。請查看文檔字符串(使用ax.legend?)。
loc告訴matplotlib要將圖例放在哪。如果你不是吹毛求疵的話,"best"是不錯的選擇,因?yàn)樗鼤x擇最不礙事的位置。要從圖例中去除一個或多個元素,不傳入label或傳入label='nolegend'即可。(中文第一版這里把best錯寫成了beat)
注解以及在Subplot上繪圖
除標(biāo)準(zhǔn)的繪圖類型,你可能還希望繪制一些子集的注解,可能是文本、箭頭或其他圖形等。注解和文字可以通過text、arrow和annotate函數(shù)進(jìn)行添加。text可以將文本繪制在圖表的指定坐標(biāo)(x,y),還可以加上一些自定義格式:
ax.text(x, y, 'Hello world!',
family='monospace', fontsize=10)
注解中可以既含有文本也含有箭頭。例如,我們根據(jù)最近的標(biāo)準(zhǔn)普爾500指數(shù)價(jià)格(來自Yahoo!Finance)繪制一張曲線圖,并標(biāo)出2008年到2009年金融危機(jī)期間的一些重要日期。你可以在Jupyter notebook的一個小窗中試驗(yàn)這段代碼(圖9-11是結(jié)果):
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')
這張圖中有幾個重要的點(diǎn)要強(qiáng)調(diào):ax.annotate方法可以在指定的x和y坐標(biāo)軸繪制標(biāo)簽。我們使用set_xlim和set_ylim人工設(shè)定起始和結(jié)束邊界,而不使用matplotlib的默認(rèn)方法。最后,用ax.set_title添加圖標(biāo)標(biāo)題。
更多有關(guān)注解的示例,請?jiān)L問matplotlib的在線示例庫。
圖形的繪制要麻煩一些。matplotlib有一些表示常見圖形的對象。這些對象被稱為塊(patch)。其中有些(如Rectangle和Circle),可以在matplotlib.pyplot中找到,但完整集合位于matplotlib.patches。
要在圖表中添加一個圖形,你需要創(chuàng)建一個塊對象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)
如果查看許多常見圖表對象的具體實(shí)現(xiàn)代碼,你就會發(fā)現(xiàn)它們其實(shí)就是由塊patch組裝而成的。
將圖表保存到文件
利用plt.savefig可以將當(dāng)前圖表保存到文件。該方法相當(dāng)于Figure對象的實(shí)例方法savefig。例如,要將圖表保存為SVG文件,你只需輸入:
plt.savefig('figpath.svg')
文件類型是通過文件擴(kuò)展名推斷出來的。因此,如果你使用的是.pdf,就會得到一個PDF文件。我在發(fā)布圖片時最常用到兩個重要的選項(xiàng)是dpi(控制“每英寸點(diǎn)數(shù)”分辨率)和bbox_inches(可以剪除當(dāng)前圖表周圍的空白部分)。要得到一張帶有最小白邊且分辨率為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的其它選項(xiàng)。
matplotlib配置
matplotlib自帶一些配色方案,以及為生成出版質(zhì)量的圖片而設(shè)定的默認(rèn)配置信息。幸運(yùn)的是,幾乎所有默認(rèn)行為都能通過一組全局參數(shù)進(jìn)行自定義,它們可以管理圖像大小、subplot邊距、配色方案、字體大小、網(wǎng)格類型等。一種Python編程方式配置系統(tǒng)的方法是使用rc方法。例如,要將全局的圖像默認(rèn)大小設(shè)置為10×10,你可以執(zhí)行:
plt.rc('figure', figsize=(10, 10))
rc的第一個參數(shù)是希望自定義的對象,如'figure'、'axes'、'xtick'、'ytick'、'grid'、'legend'等。其后可以跟上一系列的關(guān)鍵字參數(shù)。一個簡單的辦法是將這些選項(xiàng)寫成一個字典:
font_options = {'family' : 'monospace',
'weight' : 'bold',
'size' : 'small'}
plt.rc('font', **font_options)
要了解全部的自定義選項(xiàng),請查閱matplotlib的配置文件matplotlibrc(位于matplotlib/mpl-data目錄中)。如果對該文件進(jìn)行了自定義,并將其放在你自己的.matplotlibrc目錄中,則每次使用matplotlib時就會加載該文件。
下一節(jié),我們會看到,seaborn包有若干內(nèi)置的繪圖主題或類型,它們使用了matplotlib的內(nèi)部配置。
9.2 使用pandas和seaborn繪圖
matplotlib實(shí)際上是一種比較低級的工具。要繪制一張圖表,你組裝一些基本組件就行:數(shù)據(jù)展示(即圖表類型:線型圖、柱狀圖、盒形圖、散布圖、等值線圖等)、圖例、標(biāo)題、刻度標(biāo)簽以及其他注解型信息。
在pandas中,我們有多列數(shù)據(jù),還有行和列標(biāo)簽。pandas自身就有內(nèi)置的方法,用于簡化從DataFrame和Series繪制圖形。另一個庫seaborn(https://seaborn.pydata.org/),由Michael Waskom創(chuàng)建的靜態(tài)圖形庫。Seaborn簡化了許多常見可視類型的創(chuàng)建。
提示:引入seaborn會修改matplotlib默認(rèn)的顏色方案和繪圖類型,以提高可讀性和美觀度。即使你不使用seaborn API,你可能也會引入seaborn,作為提高美觀度和繪制常見matplotlib圖形的簡化方法。
線型圖
Series和DataFrame都有一個用于生成各類圖表的plot方法。默認(rèn)情況下,它們所生成的是線型圖(如圖9-13所示):
In [60]: s = pd.Series(np.random.randn(10).cumsum(), index=np.arange(0, 100, 10))
In [61]: s.plot()
該Series對象的索引會被傳給matplotlib,并用以繪制X軸。可以通過use_index=False禁用該功能。X軸的刻度和界限可以通過xticks和xlim選項(xiàng)進(jìn)行調(diào)節(jié),Y軸就用yticks和ylim。plot參數(shù)的完整列表請參見表9-3。我只會講解其中幾個,剩下的就留給讀者自己去研究了。
pandas的大部分繪圖方法都有一個可選的ax參數(shù),它可以是一個matplotlib的subplot對象。這使你能夠在網(wǎng)格布局中更為靈活地處理subplot的位置。
DataFrame的plot方法會在一個subplot中為各列繪制一條線,并自動創(chuàng)建圖例(如圖9-14所示):
In [62]: df = pd.DataFrame(np.random.randn(10, 4).cumsum(0),
....: columns=['A', 'B', 'C', 'D'],
....: index=np.arange(0, 100, 10))
In [63]: df.plot()
plot屬性包含一批不同繪圖類型的方法。例如,df.plot()等價(jià)于df.plot.line()。后面會學(xué)習(xí)這些方法。
筆記:plot的其他關(guān)鍵字參數(shù)會被傳給相應(yīng)的matplotlib繪圖函數(shù),所以要更深入地自定義圖表,就必須學(xué)習(xí)更多有關(guān)matplotlib API的知識。
DataFrame還有一些用于對列進(jìn)行靈活處理的選項(xiàng),例如,是要將所有列都繪制到一個subplot中還是創(chuàng)建各自的subplot。詳細(xì)信息請參見表9-4。
注意: 有關(guān)時間序列的繪圖,請見第11章。
柱狀圖
plot.bar()和plot.barh()分別繪制水平和垂直的柱狀圖。這時,Series和DataFrame的索引將會被用作X(bar)或Y(barh)刻度(如圖9-15所示):
In [64]: fig, axes = plt.subplots(2, 1)
In [65]: data = pd.Series(np.random.rand(16), index=list('abcdefghijklmnop'))
In [66]: data.plot.bar(ax=axes[0], color='k', alpha=0.7)
Out[66]: <matplotlib.axes._subplots.AxesSubplot at 0x7fb62493d470>
In [67]: data.plot.barh(ax=axes[1], color='k', alpha=0.7)
color='k'和alpha=0.7設(shè)定了圖形的顏色為黑色,并使用部分的填充透明度。對于DataFrame,柱狀圖會將每一行的值分為一組,并排顯示,如圖9-16所示:
In [69]: df = pd.DataFrame(np.random.rand(6, 4),
....: index=['one', 'two', 'three', 'four', 'five', 'six'],
....: columns=pd.Index(['A', 'B', 'C', 'D'], name='Genus'))
In [70]: df
Out[70]:
Genus A B C D
one 0.370670 0.602792 0.229159 0.486744
two 0.420082 0.571653 0.049024 0.880592
three 0.814568 0.277160 0.880316 0.431326
four 0.374020 0.899420 0.460304 0.100843
five 0.433270 0.125107 0.494675 0.961825
six 0.601648 0.478576 0.205690 0.560547
In [71]: df.plot.bar()
注意,DataFrame各列的名稱"Genus"被用作了圖例的標(biāo)題。
設(shè)置stacked=True即可為DataFrame生成堆積柱狀圖,這樣每行的值就會被堆積在一起(如圖9-17所示):
In [73]: df.plot.barh(stacked=True, alpha=0.5)
筆記:柱狀圖有一個非常不錯的用法:利用value_counts圖形化顯示Series中各值的出現(xiàn)頻率,比如s.value_counts().plot.bar()。
再以本書前面用過的那個有關(guān)小費(fèi)的數(shù)據(jù)集為例,假設(shè)我們想要做一張堆積柱狀圖以展示每天各種聚會規(guī)模的數(shù)據(jù)點(diǎn)的百分比。我用read_csv將數(shù)據(jù)加載進(jìn)來,然后根據(jù)日期和聚會規(guī)模創(chuàng)建一張交叉表:
In [75]: tips = pd.read_csv('examples/tips.csv')
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]
然后進(jìn)行規(guī)格化,使得各行的和為1,并生成圖表(如圖9-18所示):
# Normalize to sum to 1
In [79]: party_pcts = party_counts.div(party_counts.sum(1), axis=0)
In [80]: party_pcts
Out[80]:
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
In [81]: party_pcts.plot.bar()
于是,通過該數(shù)據(jù)集就可以看出,聚會規(guī)模在周末會變大。
對于在繪制一個圖形之前,需要進(jìn)行合計(jì)的數(shù)據(jù),使用seaborn可以減少工作量。用seaborn來看每天的小費(fèi)比例(圖9-19是結(jié)果):
In [83]: import seaborn as sns
In [84]: tips['tip_pct'] = tips['tip'] / (tips['total_bill'] - tips['tip'])
In [85]: tips.head()
Out[85]:
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')
seaborn的繪制函數(shù)使用data參數(shù),它可能是pandas的DataFrame。其它的參數(shù)是關(guān)于列的名字。因?yàn)橐惶斓拿總€值有多次觀察,柱狀圖的值是tip_pct的平均值。繪制在柱狀圖上的黑線代表95%置信區(qū)間(可以通過可選參數(shù)配置)。
seaborn.barplot有顏色選項(xiàng),使我們能夠通過一個額外的值設(shè)置(見圖9-20):
In [88]: sns.barplot(x='tip_pct', y='day', hue='time', data=tips, orient='h')
注意,seaborn已經(jīng)自動修改了圖形的美觀度:默認(rèn)調(diào)色板,圖形背景和網(wǎng)格線的顏色。你可以用seaborn.set在不同的圖形外觀之間切換:
In [90]: sns.set(style="whitegrid")
直方圖和密度圖
直方圖(histogram)是一種可以對值頻率進(jìn)行離散化顯示的柱狀圖。數(shù)據(jù)點(diǎn)被拆分到離散的、間隔均勻的面元中,繪制的是各面元中數(shù)據(jù)點(diǎn)的數(shù)量。再以前面那個小費(fèi)數(shù)據(jù)為例,通過在Series使用plot.hist方法,我們可以生成一張“小費(fèi)占消費(fèi)總額百分比”的直方圖(如圖9-21所示):
In [92]: tips['tip_pct'].plot.hist(bins=50)
與此相關(guān)的一種圖表類型是密度圖,它是通過計(jì)算“可能會產(chǎn)生觀測數(shù)據(jù)的連續(xù)概率分布的估計(jì)”而產(chǎn)生的。一般的過程是將該分布近似為一組核(即諸如正態(tài)分布之類的較為簡單的分布)。因此,密度圖也被稱作KDE(Kernel Density Estimate,核密度估計(jì))圖。使用plot.kde和標(biāo)準(zhǔn)混合正態(tài)分布估計(jì)即可生成一張密度圖(見圖9-22):
In [94]: tips['tip_pct'].plot.density()
seaborn的distplot方法繪制直方圖和密度圖更加簡單,還可以同時畫出直方圖和連續(xù)密度估計(jì)圖。作為例子,考慮一個雙峰分布,由兩個不同的標(biāo)準(zhǔn)正態(tài)分布組成(見圖9-23):
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')
散布圖或點(diǎn)圖
點(diǎn)圖或散布圖是觀察兩個一維數(shù)據(jù)序列之間的關(guān)系的有效手段。在下面這個例子中,我加載了來自statsmodels項(xiàng)目的macrodata數(shù)據(jù)集,選擇了幾個變量,然后計(jì)算對數(shù)差:
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
然后可以使用seaborn的regplot方法,它可以做一個散布圖,并加上一條線性回歸的線(見圖9-24):
In [105]: sns.regplot('m1', 'unemp', data=trans_data)
Out[105]: <matplotlib.axes._subplots.AxesSubplot at 0x7fb613720be0>
In [106]: plt.title('Changes in log %s versus log %s' % ('m1', 'unemp'))
在探索式數(shù)據(jù)分析工作中,同時觀察一組變量的散布圖是很有意義的,這也被稱為散布圖矩陣(scatter plot matrix)。純手工創(chuàng)建這樣的圖表很費(fèi)工夫,所以seaborn提供了一個便捷的pairplot函數(shù),它支持在對角線上放置每個變量的直方圖或密度估計(jì)(見圖9-25):
In [107]: sns.pairplot(trans_data, diag_kind='kde', plot_kws={'alpha': 0.2})
你可能注意到了plot_kws參數(shù)。它可以讓我們傳遞配置選項(xiàng)到非對角線元素上的圖形使用。對于更詳細(xì)的配置選項(xiàng),可以查閱seaborn.pairplot文檔字符串。
分面網(wǎng)格(facet grid)和類型數(shù)據(jù)
要是數(shù)據(jù)集有額外的分組維度呢?有多個分類變量的數(shù)據(jù)可視化的一種方法是使用小面網(wǎng)格。seaborn有一個有用的內(nèi)置函數(shù)factorplot,可以簡化制作多種分面圖(見圖9-26):
In [108]: sns.factorplot(x='day', y='tip_pct', hue='time', col='smoker',
.....: kind='bar', data=tips[tips.tip_pct < 1])
除了在分面中用不同的顏色按時間分組,我們還可以通過給每個時間值添加一行來擴(kuò)展分面網(wǎng)格:
In [109]: sns.factorplot(x='day', y='tip_pct', row='time',
.....: col='smoker',
.....: kind='bar', data=tips[tips.tip_pct < 1])
factorplot支持其它的繪圖類型,你可能會用到。例如,盒圖(它可以顯示中位數(shù),四分位數(shù),和異常值)就是一個有用的可視化類型(見圖9-28):
In [110]: sns.factorplot(x='tip_pct', y='day', kind='box',
.....: data=tips[tips.tip_pct < 0.5])
使用更通用的seaborn.FacetGrid類,你可以創(chuàng)建自己的分面網(wǎng)格。請查閱seaborn的文檔(https://seaborn.pydata.org/)。
9.3 其它的Python可視化工具
與其它開源庫類似,Python創(chuàng)建圖形的方式非常多(根本羅列不完)。自從2010年,許多開發(fā)工作都集中在創(chuàng)建交互式圖形以便在Web上發(fā)布。利用工具如Boken(https://bokeh.pydata.org/en/latest/)和Plotly(https://github.com/plotly/plotly.py),現(xiàn)在可以創(chuàng)建動態(tài)交互圖形,用于網(wǎng)頁瀏覽器。
對于創(chuàng)建用于打印或網(wǎng)頁的靜態(tài)圖形,我建議默認(rèn)使用matplotlib和附加的庫,比如pandas和seaborn。對于其它數(shù)據(jù)可視化要求,學(xué)習(xí)其它的可用工具可能是有用的。我鼓勵你探索繪圖的生態(tài)系統(tǒng),因?yàn)樗鼘⒊掷m(xù)發(fā)展。
9.4 總結(jié)
本章的目的是熟悉一些基本的數(shù)據(jù)可視化操作,使用pandas,matplotlib,和seaborn。如果視覺顯示數(shù)據(jù)分析的結(jié)果對你的工作很重要,我鼓勵你尋求更多的資源來了解更高效的數(shù)據(jù)可視化。這是一個活躍的研究領(lǐng)域,你可以通過在線和紙質(zhì)的形式學(xué)習(xí)許多優(yōu)秀的資源。
下一章,我們將重點(diǎn)放在pandas的數(shù)據(jù)聚合和分組操作上。
第1章 準(zhǔn)備工作
第2章 Python語法基礎(chǔ),IPython和Jupyter
第3章 Python的數(shù)據(jù)結(jié)構(gòu)、函數(shù)和文件
第4章 NumPy基礎(chǔ):數(shù)組和矢量計(jì)算
第5章 pandas入門
第6章 數(shù)據(jù)加載、存儲與文件格式
第7章 數(shù)據(jù)清洗和準(zhǔn)備
第8章 數(shù)據(jù)規(guī)整:聚合、合并和重塑
第9章 繪圖和可視化
第10章 數(shù)據(jù)聚合與分組運(yùn)算
第11章 時間序列
第12章 pandas高級應(yīng)用
第13章 Python建模庫介紹
第14章 數(shù)據(jù)分析案例
附錄A NumPy高級應(yīng)用
附錄B 更多關(guān)于IPython的內(nèi)容(完)