第九篇 繪圖和視覺化
資訊視覺化(也叫繪圖)是資料分析中最重要的⼯作之⼀。它可能是探索過程的⼀部分,例如,幫助我們找出異常值、必要的資料轉換、得出有關模型的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])