純干貨:手把手教你用Python做數(shù)據(jù)可視化(附代碼)
導讀:制作提供信息的可視化(有時稱為繪圖)是數(shù)據(jù)分析中的最重要任務之一??梢暬赡苁翘剿鬟^程的一部分,例如,幫助識別異常值或所需的數(shù)據(jù)轉換,或者為建模提供一些想法。對于其他人來說,構建網(wǎng)絡交互式可視化可能是最終目標。Python有很多附加庫可以用來制作靜態(tài)或動態(tài)的可視化文件,但是我將主要關注matplotlib和以它為基礎的庫。
matplotlib是一個用于生成出版級質量圖表(通常是二維的)的桌面繪圖包。該項目由John Hunter于2002年發(fā)起,目的在于在Python環(huán)境下進行MATLAB風格的繪圖。matplotlib和IPython社區(qū)合作簡化了IPython shell(目前是 Jupyter筆記本)的交互式繪圖。matplotlib支持所有操作系統(tǒng)上的各種GUI后端,還可以將可視化導出為所有常見的矢量和光柵圖形格式(PDF,SVG,JPG,PNG,BMP,GIF等)。
隨著時間的推移,matplotlib已經產生了一些數(shù)據(jù)可視化的附加工具包,使用matplotlib進行底層繪圖。
學習以下示例代碼最簡單的方式就是在Jupyter notebook中使用交互式繪圖。在進行設置時,需要在Jupyter notebook中執(zhí)行以下語句:
%matplotlib notebook
簡明matplotlib API入門
在使用matplotlib時,我們使用以下的導入慣例:
In [11]: import matplotlib.pyplot as plt
在Jupyter中運行%matplotlib notebook (或在IPython中運行%matplotlib ),我們就可以嘗試生成一個簡單的圖形。如果所有的設置都正確,則一個像圖1的圖形就會出現(xiàn):
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)

▲圖1 簡單的線性圖
盡管seaborn等庫和pandas內建的繪圖函數(shù)可以處理大部分繪圖的普通細節(jié),但如果你想在提供的函數(shù)選項之外進行定制則需要學習一些matplotlib的AIP。
本文沒有足夠的篇幅來對matplotlib的功能寬度和深度進行全面介紹。但介紹的內容應該是足以使你入門的。matplotlib的可視化作品庫和文檔是學習高級功能的最佳資源。
圖片與子圖
matplotlib所繪制的圖位于圖片(Figure)對象中。你可以使用plt.figure生成一個新的圖片:
In [16]: fig = plt.figure()
在IPython中,一個空白的繪圖窗口就會出現(xiàn),但在Jupyter中則沒有任何顯示,直到我們使用一些其他命令。plt.figure有一些選項,比如figsize是確保圖片有一個確定的大小以及存儲到硬盤時的長寬比。
你不能使用空白的圖片進行繪圖。你需要使用add_subplot創(chuàng)建一個或多個子圖(subplot):
In [16]: fig = plt.figure()
In [17]: ax1 = fig.add_subplot(2, 2, 1)
上面代碼的意思是圖片應該是2*2的(最多四個圖形),并且我們選擇了四個圖形中的第一個(序號從1開始)。如果你接著創(chuàng)建了兩個子圖,你將會獲得看上去類似圖2的可視化:
In [18]: ax2 = fig.add_subplot(2, 2, 2)
In [19]: ax3 = fig.add_subplot(2, 2, 3)

▲圖2 一個帶有三個子圖的空白matplotlib圖片
使用Jupyter notebook時有個細節(jié)需要注意,在每個單元格運行后,圖表被重置,因此對于更復雜的圖表,你必須將所有的繪圖命令放在單個的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會在最后一個圖片和子圖(如果需要的話就創(chuàng)建一個)上進行繪制,從而隱藏圖片和子圖的創(chuàng)建。因此,如果我們添加了下面的代碼,你會得到形如圖3的可視化:
In [20]: plt.plot(np.random.randn(50).cumsum(), 'k--')

▲圖3 單個子圖繪制的數(shù)據(jù)可視化
'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 增加子圖后的數(shù)據(jù)可視化
你可以在matplotlib的官方文檔中找到完整的圖形類型。
使用子圖網(wǎng)絡創(chuàng)建圖片是非常常見的任務,所以matplotlib包含了一個便捷方法plt.subplots,它創(chuàng)建一個新的圖片,然后返回要給包含了已生成子圖對象的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ù) | 描述 |
|---|---|
| nrows | 子圖的行數(shù) |
| ncols | 子圖的列數(shù) |
| sharex | 所有子圖使用相同的x軸刻度(調整xlim會影響所有子圖) |
| sharey | 所有子圖使用相同的y軸刻度(調整ylim會影響所有子圖) |
| subplot_kw | 傳入add_subplot的關鍵字參數(shù)字典,用于生成子圖 |
| **fig_kw | 在生成圖片時使用的額外關鍵字參數(shù),例如plt.subplots(2, 2, figsize=(8,6)) |
▲表1 pyplot.subplots選項
調整子圖周圍的間距
默認情況下,matplotlib會在子圖的外部和子圖之間留出一定的間距。這個間距都是相對于圖的高度和寬度來指定的,所以如果你通過編程或手動使用GUI窗口來調整圖的大小,那么圖就會自動調整。你可以使用圖對象上的subplots_adjust方法更改間距,也可以用作頂層函數(shù):
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 沒有內部子圖間隔的數(shù)據(jù)可視化
你可能會注意到軸標簽是存在重疊的。matplotlib并不檢查標簽是否重疊,因此在類似情況下你需要通過顯式指定刻度位置和刻度標簽的方法來修復軸標簽。
顏色、標記和線類型
matplotlib的主函數(shù)plot接收帶有x和y周的數(shù)組以及一些可選的字符串縮寫參數(shù)來指明顏色和線類型。例如,要用綠色破折號繪制x對y的線,你需要執(zhí)行:
ax.plot(x, y, 'g--')
這種在字符串中指定顏色和線條樣式的方式是方便的; 在實踐中,如果你以編程方式創(chuàng)建繪圖,則可能不希望將字符串混合在一起以創(chuàng)建具有所需樣式的圖表。同樣的圖表可以使用更為顯式的方式來表達:
ax.plot(x, y, 'g--')
ax.plot(x, y, linestyle='--', color='g')
有很多顏色縮寫被用于常用顏色,但是你可以通過指定十六進制顏色代碼的方式來指定任何顏色(例如'#CECECE' )。參考plot函數(shù)的文檔字符串可以看到全部的線類型(在IPython或Jupyter中使用plot? )。
折線圖還可以有標記用來凸顯實際的的數(shù)據(jù)點。由于matplotlib創(chuàng)建了一個連續(xù)性折線圖,插入點之間有時并不清除點在哪。標記可以是樣式字符串的一部分,樣式字符串中線類型、標記類型必須跟在顏色后面(參考圖6):
In [30]: from numpy.random import randn
In [31]: plt.plot(randn(30).cumsum(), 'ko--')

▲圖6 帶有標記的折線圖
上面的代碼可以寫的更為顯式:
plot(randn(30).cumsum(), color='k', linestyle='dashed', marker='o')
對于折線圖,你會注意到后續(xù)的點默認是線性內插的??梢酝ㄟ^drawstyle選項進行更改(圖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')

▲圖7 不同drawstyle選項下的折線圖
你可能會注意到在運行代碼后會有像 這樣的輸出。matplotlib返回的對象引用了剛剛添加的圖表子組件。很多時候你可以安全地忽略這些輸出。這里,由于我們向plot傳遞了label,我們可以使用plt.lengend為每條線生成一個用于區(qū)分的圖例。
無論你在用數(shù)據(jù)繪圖時是否傳遞了lebel選項,你都必須調用plt.lengend(如果你有軸的引用,也可以用ax.legend)來生成圖例來生成圖例。
刻度、標簽和圖例
對于大多數(shù)圖表修飾工作,有兩種主要的方式:使用程序性的pyplot接口(即matplotlib.pyplot)和更多面向對象的原生matplotlib API。
pyplot接口設計為交互式使用,包含了像xllim、xtick和xtcklabels等方法。這些方法分別控制了繪圖范圍、刻度位置以及刻度標簽。我們可以在兩種方式中使用:
在沒有函數(shù)參數(shù)的情況下調用,返回當前的參數(shù)值(例如plt.xlim()返回當前的x軸繪圖范圍 )。
傳入?yún)?shù)的情況下調用,并設置參數(shù)值(例如plt.xlim([0, 10])會將x軸的范圍設置為0到10)。
所有的這些方法都會在當前活動的或最近創(chuàng)建的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表示在數(shù)據(jù)范圍內設定刻度的位置;默認情況下,這些刻度也有標簽。但是我們可以使用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]: <matplotlib.text.Text at 0x7fb624d055f8>
In [43]: ax.set_xlabel('Stages')

▲圖9 x軸刻度的簡單示例
修改y軸坐標是相同過程,將上面示例中的x替換成y即可。軸的類型擁有一個set方法,允許批量設置繪圖屬性。根據(jù)之前的示例,我們可以寫如下代碼:
props = {
'title': 'My first matplotlib plot',
'xlabel': 'Stages'
}
ax.set(**props)
2. 添加圖例
圖例是用來區(qū)分繪圖元素的重要內容。有多種方式可以添加圖例。最簡單的方式是在添加每個圖表時傳遞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>]
一旦你運行了上面的代碼,你也可以調用ax.legend()或plt.legend自動生成圖例。結果圖表參見圖10:
In [49]: ax.legend(loc='best')

▲圖10 有三根折線和圖例的簡單圖表
legend方法有多個其他的位置參數(shù)loc。參考文檔字符串(使用ax.legend?命令)獲取更多信息。
loc參數(shù)告訴matplotlib在哪里放置圖表。如果你不挑剔,'best'是一個好選項,它會自動選擇最合適的位置。如果取消圖例中的元素,不要傳入label參數(shù)或者傳入label='nolegend'。
注釋與子圖加工
除了標準的繪圖類型,你可能還會想在圖表上繪制自己的注釋,而且注釋中可能會包含文本、箭頭以及其他圖形。你可以使用text、arrow和annote方法來添加注釋和文本。text在圖表上給定的坐標(x, y),根據(jù)可選的定制樣式繪制文本:
ax.text(x, y, 'Hello world!',
family='monospace', fontsize=10)
注釋可以同時繪制文本和箭頭。作為一個例子,讓我們繪制標普500指數(shù)從2007年來的收盤價(從雅虎財經獲得數(shù)據(jù)),并在圖表中標注從2008到2009年金融危機中的重要日期。你可以在Jupyter notebook的一個單元格中復現(xiàn)這些代碼。參考圖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')
# 放大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圖形的可視化
當你看到很多常見繪圖類型的實現(xiàn)時,你會發(fā)現(xiàn)他們都是從patches中組裝而來。
將圖片保存到文件
你可以使用plt.savefig將活動圖片保存到文件。這個方法等價于圖片對象的savefig實例方法。例如將圖片保存為SVG,你只需要輸入以下代碼:
plt.savefig('figpath.svg')
文件類型是從文件擴展名中推斷出來的。所以如果你使用.pdf,則會得到一個PDF。我常常使用幾個重要的選項來發(fā)布圖形:dpi,它控制每英寸點數(shù)的分辨率; bbox_inches,可以修剪實際圖形的空白。為了得到同樣一個PNG圖片,且使用最小的空白,擁有400 DPI,你需要運行以下代碼:
plt.savefig('figpath.png', dpi=400, bbox_inches='tight')
savefig并一定是寫到硬盤的,它可以將圖片寫入到所有的文件型對象中,例如BytesIO:
from io import BytesIO
buffer = BytesIO()
plt.savefig(buffer)
plot_data = buffer.getvalue()
表2是savefig其他選項的列表:
| 參數(shù) | 描述 |
|---|---|
| fname | 包含文件路徑或Python文件型對象的字符串。圖片格式是從文件擴展名中推斷出來的(例如PDF格式的.pdf或PNG的.png格式) |
| dpi | 每英寸點數(shù)的分辨率; 默認為100,但可以配置 |
| facecolor,edgecolor | 子圖之外的圖形背景的顏色;默認情況下是'w'(白色) |
| format | 文件格式( ('png', 'pdf', 'svg', 'ps', 'eps', … ) |
| bbox_inches | 要保存的圖片范圍;如果傳遞'pass',將會去除掉圖片周圍空白的部分 |
▲表2 Figure.savefig選項
matplotlib設置
matplotlib配置了配色方案和默認設置,主要用來準備用于發(fā)布的圖片。幸運的是,幾乎所有的默認行為都可以通過廣泛的全局參數(shù)來定制,包括圖形大小、子圖間距、顏色、字體大小和網(wǎng)格樣式等等。使用rc方法是使用Python編程修改配置的一種方式; 例如,要將全局默認數(shù)字大小設置為10×10,你可以輸入:
plt.rc('figure', figsize=(10, 10))
rc的第一個參數(shù)是你想要自定義的組件,比如'figure'、'axes'、'xtick'、'ytick'、'grid'、'legend'等等。之后,可以按照關鍵字參數(shù)的序列指定新參數(shù)。字典是一種在程序中設置選項的簡單方式:
font_options = {'family' : 'monospace',
'weight' : 'bold',
'size' : 'small'}
plt.rc('font', **font_options)
如果需要更深入的定制和參看全量選項,可以參考matplotlib的設置文件matplotlibrc,該文件位于matplotlib/mpl-data路徑。如果你定制了這個文件,并將他放置在home路徑下并且文件名為.matplotlibrc,則每次你使用matplotlib時都會讀取該文件。
關于作者:韋斯·麥金尼(Wes McKinney)是流行的Python開源數(shù)據(jù)分析庫pandas的創(chuàng)始人。他是一名活躍的演講者,也是Python數(shù)據(jù)社區(qū)和Apache軟件基金會的Python/C++開源開發(fā)者。目前他在紐約從事軟件架構師工作。
本文摘編自《利用Python進行數(shù)據(jù)分析》(原書第2版),經出版方授權發(fā)布。
贈書福利
書籍贊助方:機械工業(yè)出版社華章公司
贈書書籍:《利用Python進行數(shù)據(jù)分析》
贈書規(guī)則:為本文「點贊」+ 「在看」 +「轉發(fā)本文至朋友圈」+「留言」,與文章內容相關的優(yōu)質留言即可上墻并從所有留言中隨機選出2位走心讀者留言將各獲得一本。
截止時間:2021年6月16日,晚 20:00
領書須知:提供轉發(fā)截圖 + 點贊和在看截圖
注意事項:最終獲贈者請在24小時以內添加我的微信,備注:贈書??
推薦閱讀
杰哥的另一個公眾號,主要分享關于個人成長經歷的那點事,歡迎您的關注。

