
導讀:製作提供資訊的視覺化(有時稱為繪圖)是資料分析中的最重要任務之一。視覺化可能是探索過程的一部分,例如,幫助識別異常值或所需的資料轉換,或者為建模提供一些想法。對於其他人來說,構建網路互動式視覺化可能是最終標的。Python有很多附加庫可以用來製作靜態或動態的視覺化檔案,但是我將主要關註matplotlib和以它為基礎的庫。
作者:Wes McKinney
本文摘編自《利用Python進行資料分析》第2版,如需轉載請聯絡我們
matplotlib是一個用於生成出版級質量圖表(通常是二維的)的桌面繪圖包。該專案由John Hunter於2002年發起,目的在於在Python環境下進行MATLAB風格的繪圖。matplotlib和IPython社群合作簡化了IPython shell(目前是 Jupyter筆記本)的互動式繪圖。matplotlib支援所有作業系統上的各種GUI後端,還可以將視覺化匯出為所有常見的向量和光柵圖形格式(PDF,SVG,JPG,PNG,BMP,GIF等)。
隨著時間的推移,matplotlib已經產生了一些資料視覺化的附加工具包,使用matplotlib進行底層繪圖。
學習以下示例程式碼最簡單的方式就是在Jupyter notebook中使用互動式繪圖。在進行設定時,需要在Jupyter notebook中執行以下陳述句:
%matplotlib notebook
00 簡明matplotlib API入門
在使用matplotlib時,我們使用以下的匯入慣例:
In [11]: import matplotlib.pyplot as plt在Jupyter中執行%matplotlib notebook (或在IPython中執行%matplotlib ),我們就可以嘗試生成一個簡單的圖形。如果所有的設定都正確,則一個像圖1的圖形就會出現:
In [12]: import numpy as npIn [13]: data = np.arange(10)In [14]: dataOut[14]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])In [15]: plt.plot(data)
▲圖1 簡單的線性圖
儘管seaborn等庫和pandas內建的繪圖函式可以處理大部分繪圖的普通細節,但如果你想在提供的函式選項之外進行定製則需要學習一些matplotlib的AIP。
本文沒有足夠的篇幅來對matplotlib的功能寬度和深度進行全面介紹。但介紹的內容應該是足以使你入門的。matplotlib的視覺化作品庫和檔案是學習高階功能的最佳資源。
01 圖片與子圖
matplotlib所繪製的圖位於圖片(Figure)物件中。你可以使用plt.figure生成一個新的圖片:
In [16]: fig = plt.figure()在IPython中,一個空白的繪圖視窗就會出現,但在Jupyter中則沒有任何顯示,直到我們使用一些其他命令。plt.figure有一些選項,比如figsize是確保圖片有一個確定的大小以及儲存到硬碟時的長寬比。
你不能使用空白的圖片進行繪圖。你需要使用add_subplot建立一個或多個子圖(subplot):
In [16]: fig = plt.figure()In [17]: ax1 = fig.add_subplot(2, 2, 1)上面程式碼的意思是圖片應該是2*2的(最多四個圖形),並且我們選擇了四個圖形中的第一個(序號從1開始)。如果你接著建立了兩個子圖,你將會獲得看上去類似圖2的視覺化:
In [18]: ax2 = fig.add_subplot(2, 2, 2)In [19]: ax3 = fig.add_subplot(2, 2, 3)
▲圖2 一個帶有三個子圖的空白matplotlib圖片
使用Jupyter notebook時有個細節需要註意,在每個單元格執行後,圖表被重置,因此對於更複雜的圖表,你必須將所有的繪圖命令放在單個的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會在最後一個圖片和子圖(如果需要的話就建立一個)上進行繪製,從而隱藏圖片和子圖的建立。因此,如果我們添加了下麵的程式碼,你會得到形如圖3的視覺化:
In [20]: plt.plot(np.random.randn(50).cumsum(), 'k--')
▲圖3 單個子圖繪製的資料視覺化
‘k–‘是用於繪製黑色分段線的style選項。fig.add_subplot傳回的物件是AxesSubplot物件,使用這些物件你可以直接在其他空白的子圖上呼叫物件的實體方法進行繪圖(參考圖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))
▲圖4 增加子圖後的資料視覺化
你可以在matplotlib的官方檔案中找到完整的圖形型別。
使用子圖網路建立圖片是非常常見的任務,所以matplotlib包含了一個便捷方法plt.subplots,它建立一個新的圖片,然後傳回要給包含了已生成子圖物件的NumPy陣列:
In [24]: fig, axes = plt.subplots(2, 3)In [25]: axesOut[25]:array([[object at 0x7fb626374048>,object at 0x7fb62625db00>,object at 0x7fb6262f6c88>],[object at 0x7fb6261a36a0>,object at 0x7fb626181860>,object at 0x7fb6260fd4e0>]], dtype=object)|
引數 |
描述 |
|
nrows |
子圖的行數 |
|
ncols |
子圖的列數 |
|
sharex |
所有子圖使用相同的x軸刻度(調整xlim會影響所有子圖) |
|
sharey |
所有子圖使用相同的y軸刻度(調整ylim會影響所有子圖) |
|
subplot_kw |
傳入add_subplot的關鍵字引數字典,用於生成子圖 |
|
**fig_kw |
在生成圖片時使用的額外關鍵字引數,例如plt.subplots(2, 2, figsize=(8,6)) |
▲表1 pyplot.subplots選項
調整子圖周圍的間距
預設情況下,matplotlib會在子圖的外部和子圖之間留出一定的間距。這個間距都是相對於圖的高度和寬度來指定的,所以如果你透過程式設計或手動使用GUI視窗來調整圖的大小,那麼圖就會自動調整。 你可以使用圖物件上的subplots_adjust方法更改間距,也可以用作頂層函式:
subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=None)
wspace和hspace分別控制的是圖片的寬度和高度百分比,以用作子圖間的間距。下麵是一個小例子,我將這個間距一直縮小到零(見圖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)
▲圖5 沒有內部子圖間隔的資料視覺化
你可能會註意到軸標簽是存在重疊的。matplotlib並不檢查標簽是否重疊,因此在類似情況下你需要透過顯式指定刻度位置和刻度標簽的方法來修複軸標簽。
02 顏色、標記和線型別
matplotlib的主函式plot接收帶有x和y周的陣列以及一些可選的字串縮寫引數來指明顏色和線型別。例如,要用綠色破折號繪製x對y的線,你需要執行:
ax.plot(x, y, 'g--')這種在字串中指定顏色和線條樣式的方式是方便的; 在實踐中,如果你以程式設計方式建立繪圖,則可能不希望將字串混合在一起以建立具有所需樣式的圖表。 同樣的圖表可以使用更為顯式的方式來表達:
ax.plot(x, y, 'g--')ax.plot(x, y, linestyle='--', color='g')有很多顏色縮寫被用於常用顏色,但是你可以透過指定十六進位制顏色程式碼的方式來指定任何顏色(例如’#CECECE’ )。參考plot函式的檔案字串可以看到全部的線型別(在IPython或Jupyter中使用plot? )。
折線圖還可以有標記用來凸顯實際的的資料點。由於matplotlib建立了一個連續性折線圖,插入點之間有時並不清除點在哪。標記可以是樣式字串的一部分,樣式字串中線型別、標記型別必須跟在顏色後面(參考圖6):
In [30]: from numpy.random import randnIn [31]: plt.plot(randn(30).cumsum(), 'ko--')
▲圖6 帶有標記的折線圖
上面的程式碼可以寫的更為顯式:
plot(randn(30).cumsum(), color='k', linestyle='dashed', marker='o')對於折線圖,你會註意到後續的點預設是線性內插的。可以透過drawstyle選項進行更改(圖7):
In [33]: data = np.random.randn(30).cumsum()In [34]: plt.plot(data, 'k--', label='Default')Out[34]: [0x7fb624d86160>]In [35]: plt.plot(data, 'k-', drawstyle='steps-post', label='steps-post')Out[35]: [0x7fb624d869e8>]In [36]: plt.legend(loc='best')
▲圖7 不同drawstyle選項下的折線圖
你可能會註意到在執行程式碼後會有像 這樣的輸出。matplotlib傳回的物件取用了剛剛新增的圖表子元件。很多時候你可以安全地忽略這些輸出。這裡,由於我們向plot傳遞了label,我們可以使用plt.lengend為每條線生成一個用於區分的圖例。
無論你在用資料繪圖時是否傳遞了lebel選項,你都必須呼叫plt.lengend(如果你有軸的取用,也可以用ax.legend)來生成圖例來生成圖例。
03 刻度、標簽和圖例
對於大多數圖表修飾工作,有兩種主要的方式:使用程式性的pyplot介面(即matplotlib.pyplot)和更多面向物件的原生matplotlib API。
pyplot介面設計為互動式使用,包含了像xllim、xtick和xtcklabels等方法。這些方法分別控制了繪圖範圍、刻度位置以及刻度標簽。我們可以在兩種方式中使用:
-
在沒有函式引數的情況下呼叫,傳回當前的引數值(例如plt.xlim()傳回當前的x軸繪圖範圍 )。
-
傳入引數的情況下呼叫,並設定引數值(例如plt.xlim([0, 10])會將x軸的範圍設定為0到10)。
所有的這些方法都會在當前活動的或最近建立的AxeSubplot上生效。這些方法中的每一個對應於子圖自身的兩個方法;比如xlim對應於ax.get_lim和ax.set_lim。我更傾向於使用subplot的實體方法,因為這樣更為顯式(尤其是在處理多個子圖時),但你當然可以使用你覺得更為方便的方式。
1. 設定標題、軸標簽、刻度和刻度標簽
為了講解軸的自定義,我會生成一個簡單圖表,並繪製隨機漫步(參考圖8):
In [37]: fig = plt.figure()In [38]: ax = fig.add_subplot(1, 1, 1)In [39]: ax.plot(np.random.randn(1000).cumsum())
▲圖8 表述x軸(以及軸標簽)的簡單圖表
要改變x軸刻度,最簡單的方式是使用set_xticks和set_xticklebels。set_xticks表示在資料範圍內設定刻度的位置;預設情況下,這些刻度也有標簽。但是我們可以使用set_xticklabels為標簽賦值:
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選項會將x軸刻度標簽旋轉30度。最後,set_xlabel會給x軸一個名稱,set_titel會給子圖一個標題(參考圖9的結果圖):
In [42]: ax.set_title('My first matplotlib plot')Out[42]: 0x7fb624d055f8>In [43]: ax.set_xlabel('Stages')
▲圖9 x軸刻度的簡單示例
修改y軸坐標是相同過程,將上面示例中的x替換成y即可。軸的型別擁有一個set方法,允許批次設定繪圖屬性。根據之前的示例,我們可以寫如下程式碼:
props = {'title': 'My first matplotlib plot','xlabel': 'Stages'}ax.set(**props)2. 新增圖例
圖例是用來區分繪圖元素的重要內容。有多種方式可以新增圖例。最簡單的方式是在新增每個圖表時傳遞label引數:
In [44]: from numpy.random import randnIn [45]: fig = plt.figure(); ax = fig.add_subplot(1, 1, 1)In [46]: ax.plot(randn(1000).cumsum(), 'k', label='one')Out[46]: [0x7fb624bdf860>]In [47]: ax.plot(randn(1000).cumsum(), 'k--', label='two')Out[47]: [0x7fb624be90f0>]In [48]: ax.plot(randn(1000).cumsum(), 'k.', label='three')Out[48]: [0x7fb624be9160>]一旦你運行了上面的程式碼,你也可以呼叫ax.legend()或plt.legend自動生成圖例。結果圖表參見圖10:
In [49]: ax.legend(loc='best')
▲圖10 有三根折線和圖例的簡單圖表
legend方法有多個其他的位置引數loc。參考檔案字串(使用ax.legend?命令)獲取更多資訊。
loc引數告訴matplotlib在哪裡放置圖表。如果你不挑剔,’best’是一個好選項,它會自動選擇最合適的位置。如果取消圖例中的元素,不要傳入label引數或者傳入label=’_nolegend_’。
04 註釋與子圖加工
除了標準的繪圖型別,你可能還會想在圖表上繪製自己的註釋,而且註釋中可能會包含文字、箭頭以及其他圖形。你可以使用text、arrow和annote方法來新增註釋和文字。text在圖表上給定的坐標(x, y),根據可選的定製樣式繪製文字:
ax.text(x, y, 'Hello world!',family='monospace', fontsize=10)註釋可以同時繪製文字和箭頭。作為一個例子,讓我們繪製標普500指數從2007年來的收盤價(從雅虎財經獲得資料),併在圖表中標註從2008到2009年金融危機中的重要日期。你可以在Jupyter notebook的一個單元格中復現這些程式碼。參考圖11的程式碼執行結果:
from datetime import datetimefig = 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')# 放大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')
▲圖11 2008-2009金融危機中的重要日期
在圖表中有一些重要點需要凸顯:ax.annotate方法可以在指定的x和y坐標上繪製標簽。我們可以使用set_xlim和set_ylim方法手動設定圖表的邊界,而不是使用matplotlib的預設設定。最後,ax.set_title會圖表添加了一個主標題。
參考線上的matplotlib展覽館,可以學習更多註釋的範例。
繪製圖形時有更多需要註意的地方。matplotlib含有表示多種常見圖形的物件,這些物件的取用是patches。一些圖形,比如Rectangle(矩形)和Circle(圓形),可以在matplotlib.pyplot中找到,但圖形的全集位於matplotlib.patches。
想在圖表中新增圖形時,你需要生成patch(補丁)物件shp,並呼叫ax.add_patch(shp)將它加入到子圖中(參考圖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)
▲圖12 三種個不同patch圖形的視覺化
當你看到很多常見繪圖型別的實現時,你會發現他們都是從patches中組裝而來。
05 將圖片儲存到檔案
你可以使用plt.savefig將活動圖片儲存到檔案。這個方法等價於圖片物件的savefig實體方法。例如將圖片儲存為SVG,你只需要輸入以下程式碼:
plt.savefig('figpath.svg')檔案型別是從檔案副檔名中推斷出來的。 所以如果你使用.pdf,則會得到一個PDF。 我常常使用幾個重要的選項來釋出圖形:dpi,它控制每英寸點數的解析度; bbox_inches,可以修剪實際圖形的空白。 為了得到同樣一個PNG圖片,且使用最小的空白,擁有400 DPI,你需要執行以下程式碼:
plt.savefig('figpath.png', dpi=400, bbox_inches='tight')savefig並一定是寫到硬碟的,它可以將圖片寫入到所有的檔案型物件中,例如BytesIO:
from io import BytesIObuffer = BytesIO()plt.savefig(buffer)plot_data = buffer.getvalue()表2是savefig其他選項的串列:
|
引數 |
描述 |
|
fname |
包含檔案路徑或Python檔案型物件的字串。 圖片格式是從檔案副檔名中推斷出來的(例如PDF格式的.pdf或PNG的.png格式) |
|
dpi |
每英寸點數的解析度; 預設為100,但可以配置 |
|
facecolor,edgecolor |
子圖之外的圖形背景的顏色;預設情況下是’w'(白色) |
|
format |
檔案格式( (‘png’, ‘pdf’, ‘svg’, ‘ps’, ‘eps’, … ) |
|
bbox_inches |
要儲存的圖片範圍;如果傳遞’pass’,將會去除掉圖片周圍空白的部分 |
▲表2 Figure.savefig選項
06 matplotlib設定
matplotlib配置了配色方案和預設設定,主要用來準備用於釋出的圖片。 幸運的是,幾乎所有的預設行為都可以透過廣泛的全域性引數來定製,包括圖形大小、子圖間距、顏色、字型大小和網格樣式等等。使用rc方法是使用Python程式設計修改配置的一種方式; 例如,要將全域性預設數字大小設定為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路徑。如果你定製了這個檔案,並將他放置在home路徑下並且檔案名為.matplotlibrc,則每次你使用matplotlib時都會讀取該檔案。
關於作者:韋斯·麥金尼(Wes McKinney)是流行的Python開源資料分析庫pandas的創始人。他是一名活躍的演講者,也是Python資料社群和Apache軟體基金會的Python/C++開源開發者。目前他在紐約從事軟體架構師工作。
本文摘編自《利用Python進行資料分析》(原書第2版),經出版方授權釋出。
延伸閱讀《利用Python進行資料分析》
點選上圖瞭解及購買
轉載請聯絡微信:togo-maruko
推薦語:Python資料分析經典暢銷書全新升級,第1版中文版累計銷售100000冊。針對Python 3.6進行全面修訂和更新,涵蓋新版的pandas、NumPy、IPython和Jupyter。

更多精彩
在公眾號後臺對話方塊輸入以下關鍵詞
檢視更多優質內容!
PPT | 報告 | 讀書 | 書單
Python | 機器學習 | 深度學習 | 神經網路
區塊鏈 | 揭秘 | 乾貨 | 數學
猜你想看
Q: 你都有哪些製作圖表的小技巧?
歡迎留言與大家分享
覺得不錯,請把這篇文章分享給你的朋友
轉載 / 投稿請聯絡:baiyu@hzbook.com
更多精彩,請在後臺點選“歷史文章”檢視

知識星球
