高效使用 Python 可視化工具 Matplotlib
Matplotlib是Python中最常用的可視化工具之一,可以非常方便地創(chuàng)建海量類型的2D圖表和一些基本的3D圖表。本文主要介紹了在學(xué)習(xí)Matplotlib時(shí)面臨的一些挑戰(zhàn),為什么要使用Matplotlib,并推薦了一個(gè)學(xué)習(xí)使用Matplotlib的步驟。
簡介
對于新手來說,進(jìn)入Python可視化領(lǐng)域有時(shí)可能會(huì)令人感到沮喪。Python有很多不同的可視化工具,選擇一個(gè)正確的工具有時(shí)是一種挑戰(zhàn)。 例如,即使兩年過去了,這篇《Overview of Python Visualization Tools》是引導(dǎo)人們到這個(gè)網(wǎng)站的***帖子之一。 在那篇文章中,我對matplotlib留下了一些陰影,并在分析過程中不再使用。 然而,在使用諸如pandas,scikit-learn,seaborn和其他數(shù)據(jù)科學(xué)技術(shù)棧的python工具后,覺得丟棄matplotlib有點(diǎn)過早了。說實(shí)話,之前我不太了解matplotlib,也不知道如何在工作流程中有效地使用。
現(xiàn)在我花時(shí)間學(xué)習(xí)了其中的一些工具,以及如何使用matplotlib,已經(jīng)開始將matplotlib看作是不可或缺的工具了。這篇文章將展示我是如何使用matplotlib的,并為剛?cè)腴T的用戶或者沒時(shí)間學(xué)習(xí)matplotlib的用戶提供一些建議。我堅(jiān)信matplotlib是python數(shù)據(jù)科學(xué)技術(shù)棧的重要組成部分,希望本文能幫助大家了解如何將matplotlib用于自己的可視化。
為什么對matplotlib都是負(fù)面評價(jià)?
在我看來,新用戶學(xué)習(xí)matplotlib之所以會(huì)面臨一定的挑戰(zhàn),主要有以下幾個(gè)原因。
首先,matplotlib有兩種接口。***種是基于MATLAB并使用基于狀態(tài)的接口。第二種是面向?qū)ο蟮慕涌?。為什么是這兩種接口不在本文討論的范圍之內(nèi),但是知道有兩種方法在使用matplotlib進(jìn)行繪圖時(shí)非常重要。
兩種接口引起混淆的原因在于,在stack overflow社區(qū)和谷歌搜索可以獲得大量信息的情況下,新用戶對那些看起來有些相似但不一樣的問題,面對多個(gè)解決方案會(huì)感到困惑。從我自己的經(jīng)歷說起?;仡櫼幌挛业呐f代碼,一堆matplotlib代碼的混合——這對我來說非?;靵y(即使是我寫的)。
關(guān)鍵點(diǎn)
matplotlib的新用戶應(yīng)該學(xué)習(xí)使用面向?qū)ο蟮慕涌凇?/p>
matplotlib的另一個(gè)歷史性挑戰(zhàn)是,一些默認(rèn)風(fēng)格選項(xiàng)相當(dāng)沒有吸引力。 在R語言世界里,可以用ggplot生成一些相當(dāng)酷的繪圖,相比之下,matplotlib的選項(xiàng)看起來有點(diǎn)丑。令人欣慰的是matplotlib 2.0具有更美觀的樣式,以及非常便捷對可視化的內(nèi)容進(jìn)行主題化的能力。
使用matplotlib我認(rèn)為第三個(gè)挑戰(zhàn)是,當(dāng)繪制某些東西時(shí),應(yīng)該單純使用matplotlib還是使用建立在其之上的類似pandas或者seaborn這樣的工具,你會(huì)感到困惑。任何時(shí)候都可以有多種方式來做事,對于新手或不常用matplotlib的用戶來講,遵循正確的路徑是具有挑戰(zhàn)性的。將這種困惑與兩種不同的API聯(lián)系起來,是解決問題的秘訣。
為什么堅(jiān)持要用matplotlib?
盡管有這些問題,但是我慶幸有matplotlib,因?yàn)樗浅?qiáng)大。這個(gè)庫允許創(chuàng)建幾乎任何你可以想象的可視化。此外,圍繞著它還有一個(gè)豐富的python工具生態(tài)系統(tǒng),許多更先進(jìn)的可視化工具用matplotlib作為基礎(chǔ)庫。如果在python數(shù)據(jù)科學(xué)棧中進(jìn)行任何工作,都將需要對如何使用matplotlib有一個(gè)基本的了解。這是本文的其余部分的重點(diǎn)——介紹一種有效使用matplotlib的基本方法。
基本前提
如果你除了本文之外沒有任何基礎(chǔ),建議用以下幾個(gè)步驟學(xué)習(xí)如何使用matplotlib:
- 學(xué)習(xí)基本的matplotlib術(shù)語,尤其是什么是圖和坐標(biāo)軸
- 始終使用面向?qū)ο蟮慕涌?,從一開始就養(yǎng)成使用它的習(xí)慣
- 用基礎(chǔ)的pandas繪圖開始你的可視化學(xué)習(xí)
- 用seaborn進(jìn)行更復(fù)雜的統(tǒng)計(jì)可視化
- 用matplotlib來定制pandas或者seaborn可視化
這幅來自matplotlib faq的圖非常經(jīng)典,方便了解一幅圖的不同術(shù)語。
大多數(shù)術(shù)語都非常直接,但要記住的要點(diǎn)是,F(xiàn)igure是最終的圖像,可能包含一個(gè)或多個(gè)坐標(biāo)軸。坐標(biāo)軸代表一個(gè)單獨(dú)的劃分。一旦你了解這些內(nèi)容,以及如何通過面向?qū)ο蟮腁PI訪問它們,下面的步驟才能開始進(jìn)行。
這些術(shù)語知識(shí)有另一個(gè)好處,當(dāng)你在網(wǎng)上看某些東西時(shí),就有了一個(gè)起點(diǎn)。如果你花時(shí)間了解了這一點(diǎn),才會(huì)理解matplotlib API的其余部分。此外,許多python的高級軟件包,如seaborn和ggplot都依賴于matplotlib。因此,了解這些基礎(chǔ)知識(shí)后再學(xué)那些功能更強(qiáng)大的框架會(huì)容易一些。
***,我不是說你應(yīng)該避免選擇例如ggplot(aka ggpy),bokeh,plotly或者altair等其他更好的工具。我只是認(rèn)為你需要從對matplotlib + pandas + seaborn 有一個(gè)基本了解開始。一旦理解了基本的可視化技術(shù),就可以探索其他工具,并根據(jù)自己的需要做出明智的選擇。
入門
本文的其余部分將作為一個(gè)入門教程,介紹如何在pandas中進(jìn)行基本的可視化創(chuàng)建,并使用matplotlib自定義最常用的項(xiàng)目。一旦你了解了基本過程,進(jìn)一步的定制化創(chuàng)建就相對比較簡單。
重點(diǎn)講一下我遇到的最常見的繪圖任務(wù),如標(biāo)記軸,調(diào)整限制,更新繪圖標(biāo)題,保存圖片和調(diào)整圖例。
準(zhǔn)備開始,我先引入庫并讀入一些數(shù)據(jù):
- import pandas as pd
- import matplotlib.pyplot as plt
- from matplotlib.ticker import FuncFormatter
- df = pd.read_excel("https://github.com/chris1610/pbpython/blob/master/data/sample-salesv3.xlsx?raw=true")
- df.head()
這是2014年的銷售交易數(shù)據(jù)。為了使這些數(shù)據(jù)簡短一些,我將對數(shù)據(jù)進(jìn)行聚合,以便我們可以看到前十名客戶的總購買量和總銷售額。為了清楚我還會(huì)在繪圖中重新命名列。
- top_10 = (df.groupby('name')['ext price', 'quantity'].agg({'ext price': 'sum', 'quantity': 'count'})
- .sort_values(by='ext price', ascending=False))[:10].reset_index()
- top_10.rename(columns={'name': 'Name', 'ext price': 'Sales', 'quantity': 'Purchases'}, inplace=True)
下面是數(shù)據(jù)的處理結(jié)果。
現(xiàn)在,數(shù)據(jù)被格式化成一個(gè)簡單的表格,我們來看如何將這些結(jié)果繪制成條形圖。
如前所述,matplotlib有許多不同的樣式可用于渲染繪圖,可以用plt.style.available查看系統(tǒng)中有哪些可用的樣式。
- plt.style.available
- ['seaborn-dark',
- 'seaborn-dark-palette',
- 'fivethirtyeight',
- 'seaborn-whitegrid',
- 'seaborn-darkgrid',
- 'seaborn',
- 'bmh',
- 'classic',
- 'seaborn-colorblind',
- 'seaborn-muted',
- 'seaborn-white',
- 'seaborn-talk',
- 'grayscale',
- 'dark_background',
- 'seaborn-deep',
- 'seaborn-bright',
- 'ggplot',
- 'seaborn-paper',
- 'seaborn-notebook',
- 'seaborn-poster',
- 'seaborn-ticks',
- 'seaborn-pastel']
這樣簡單使用一個(gè)樣式:
- plt.style.use('ggplot')
我鼓勵(lì)大家嘗試不同的風(fēng)格,看看你喜歡哪些。
現(xiàn)在我們準(zhǔn)備好了一個(gè)更美觀的樣式,***步是使用標(biāo)準(zhǔn)的pandas繪圖功能繪制數(shù)據(jù):
- top_10.plot(kind='barh', y="Sales", x="Name")
我推薦先使用pandas繪圖,是因?yàn)樗且环N快速簡便構(gòu)建可視化的方法。 由于大多數(shù)人可能已經(jīng)在pandas中進(jìn)行過一些數(shù)據(jù)處理/分析,所以請先從基本的繪圖開始。
定制化繪圖
假設(shè)你對這個(gè)繪圖的要點(diǎn)很滿意,下一步就是定制它。使用pandas繪圖功能定制(如添加標(biāo)題和標(biāo)簽)非常簡單。但是,你可能會(huì)發(fā)現(xiàn)自己的需求在某種程度上超越該功能。這就是我建議養(yǎng)成這樣做的習(xí)慣的原因:
- fig, ax = plt.subplots()
- top_10.plot(kind='barh', y="Sales", x="Name", ax=ax)
得到的圖看起來與原始圖看起來相同,但是我們向plt.subplots() 添加了一個(gè)額外的調(diào)用,并將ax傳遞給繪圖函數(shù)。為什么要這樣做? 記得當(dāng)我說在matplotlib中要訪問坐標(biāo)軸和數(shù)字至關(guān)重要嗎?這就是我們在這里完成的工作。將來任何定制化都將通過ax或fig對象完成。
我們得益于pandas快速繪圖,獲得了訪問matplotlib的所有權(quán)限。我們現(xiàn)在可以做什么呢?用一個(gè)例子來展示。另外,通過命名約定,可以非常簡單地把別人的解決方案改成適合自己獨(dú)特需求的方案。
假設(shè)我們要調(diào)整x限制并更改一些坐標(biāo)軸的標(biāo)簽?現(xiàn)在坐標(biāo)軸保存在ax變量中,我們有很多的控制權(quán):
- fig, ax = plt.subplots()
- top_10.plot(kind='barh', y="Sales", x="Name", ax=ax)
- ax.set_xlim([-10000, 140000])
- ax.set_xlabel('Total Revenue')
- ax.set_ylabel('Customer');
下面是一個(gè)快捷方式,可以用來更改標(biāo)題和兩個(gè)標(biāo)簽:
- Python
- fig, ax = plt.subplots()
- top_10.plot(kind='barh', y="Sales", x="Name", ax=ax)
- ax.set_xlim([-10000, 140000])
- ax.set(title='2014 Revenue', xlabel='Total Revenue', ylabel='Customer')
為了進(jìn)一步驗(yàn)證這種方法,還可以調(diào)整圖像的大小。通過plt.subplots() 函數(shù),可以用英寸定義figsize。也可以用ax.legend().set_visible(False)來刪除圖例。
- fig, ax = plt.subplots(figsize=(5, 6))
- top_10.plot(kind='barh', y="Sales", x="Name", ax=ax)
- ax.set_xlim([-10000, 140000])
- ax.set(title='2014 Revenue', xlabel='Total Revenue')
- ax.legend().set_visible(False)
基于很多原因你可能想要調(diào)整一下這個(gè)圖。看著最別扭的地方是總收入數(shù)字的格式。 Matplotlib可以通過FuncFormatter來幫我們實(shí)現(xiàn)。這個(gè)功能可以將用戶定義的函數(shù)應(yīng)用于值,并返回一個(gè)格式整齊的字符串放置在坐標(biāo)軸上。
下面是一個(gè)貨幣格式化函數(shù),可以優(yōu)雅地處理幾十萬范圍內(nèi)的美元格式:
- def currency(x, pos):
- 'The two args are the value and tick position'
- if x >= 1000000:
- return '${:1.1f}M'.format(x*1e-6)
- return '${:1.0f}K'.format(x*1e-3)
現(xiàn)在我們有一個(gè)格式化函數(shù),需要定義它并將其應(yīng)用到x軸。以下是完整的代碼:
- fig, ax = plt.subplots()
- top_10.plot(kind='barh', y="Sales", x="Name", ax=ax)
- ax.set_xlim([-10000, 140000])
- ax.set(title='2014 Revenue', xlabel='Total Revenue', ylabel='Customer')
- formatter = FuncFormatter(currency)
- ax.xaxis.set_major_formatter(formatter)
- ax.legend().set_visible(False)
這樣更美觀,也是一個(gè)很好的例子,展示如何靈活地定義自己的問題解決方案。
我們***要去探索的一個(gè)自定義功能是通過添加注釋到繪圖。繪制一條垂直線,可以用ax.axvline()。添加自定義文本,可以用ax.text()。
在這個(gè)例子中,我們將繪制一條平均線,并顯示三個(gè)新客戶的標(biāo)簽。 下面是完整的代碼和注釋,把它們放在一起。
- # Create the figure and the axes
- fig, ax = plt.subplots()
- # Plot the data and get the averaged
- top_10.plot(kind='barh', y="Sales", x="Name", ax=ax)
- avg = top_10['Sales'].mean()
- # Set limits and labels
- ax.set_xlim([-10000, 140000])
- ax.set(title='2014 Revenue', xlabel='Total Revenue', ylabel='Customer')
- # Add a line for the average
- ax.axvline(x=avg, color='b', label='Average', linestyle='--', linewidth=1)
- # Annotate the new customers
- for cust in [3, 5, 8]:
- ax.text(115000, cust, "New Customer")
- # Format the currency
- formatter = FuncFormatter(currency)
- ax.xaxis.set_major_formatter(formatter)
- # Hide the legend
- ax.legend().set_visible(False)
雖然這可能不是讓人感到興奮(眼前一亮)的繪圖方式,但它展示了你在用這種方法時(shí)有多大權(quán)限。
圖形和圖像
到目前為止,我們所做的所有改變都是單個(gè)圖形。幸運(yùn)的是,我們也有能力在圖上添加多個(gè)圖形,并使用各種選項(xiàng)保存整個(gè)圖像。
如果決定要把兩幅圖放在同一個(gè)圖像上,我們應(yīng)對如何做到這一點(diǎn)有基本了解。 首先,創(chuàng)建圖形,然后創(chuàng)建坐標(biāo)軸,然后將其全部繪制在一起。我們可以用plt.subplots()來完成:
- fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=(7, 4))
在這個(gè)例子中,用nrows和ncols來指定大小,這樣對新用戶來說比較清晰。在示例代碼中,經(jīng)常看到像1,2這樣的變量。我覺得使用命名的參數(shù),之后在查看代碼時(shí)更容易理解。
用sharey = True這個(gè)參數(shù),以便yaxis共享相同的標(biāo)簽。
這個(gè)例子也很好,因?yàn)楦鱾€(gè)坐標(biāo)軸被解壓縮到ax0和ax1。有這些坐標(biāo)軸軸,你可以像上面的例子一樣繪制圖形,但是在ax0和ax1上各放一個(gè)圖。
Python
- # Get the figure and the axes
- fig, (ax0, ax1) = plt.subplots(nrows=1,ncols=2, sharey=True, figsize=(7, 4))
- top_10.plot(kind='barh', y="Sales", x="Name", ax=ax0)
- ax0.set_xlim([-10000, 140000])
- ax0.set(title='Revenue', xlabel='Total Revenue', ylabel='Customers')
- # Plot the average as a vertical line
- avg = top_10['Sales'].mean()
- ax0.axvline(x=avg, color='b', label='Average', linestyle='--', linewidth=1)
- # Repeat for the unit plot
- top_10.plot(kind='barh', y="Purchases", x="Name", ax=ax1)
- avg = top_10['Purchases'].mean()
- ax1.set(title='Units', xlabel='Total Units', ylabel='')
- ax1.axvline(x=avg, color='b', label='Average', linestyle='--', linewidth=1)
- # Title the figure
- fig.suptitle('2014 Sales Analysis', fontsize=14, fontweight='bold');
- # Hide the legends
- ax1.legend().set_visible(False)
- ax0.legend().set_visible(False)
到目前為止,我一直用jupyter notebook,借助%matplotlib內(nèi)聯(lián)指令來顯示圖形。但是很多時(shí)候,需要以特定格式保存數(shù)字,和其他內(nèi)容一起展示。
Matplotlib支持許多不同格式文件的保存。 你可以用fig.canvas.get_supported_filetypes()查看系統(tǒng)支持的格式:
- fig.canvas.get_supported_filetypes()
- {'eps': 'Encapsulated Postscript',
- 'jpeg': 'Joint Photographic Experts Group',
- 'jpg': 'Joint Photographic Experts Group',
- 'pdf': 'Portable Document Format',
- 'pgf': 'PGF code for LaTeX',
- 'png': 'Portable Network Graphics',
- 'ps': 'Postscript',
- 'raw': 'Raw RGBA bitmap',
- 'rgba': 'Raw RGBA bitmap',
- 'svg': 'Scalable Vector Graphics',
- 'svgz': 'Scalable Vector Graphics',
- 'tif': 'Tagged Image File Format',
- 'tiff': 'Tagged Image File Format'}
由于我們有fig對象,我們可以用多個(gè)選項(xiàng)來保存圖像:
- fig.savefig('sales.png', transparent=False, dpi=80, bbox_inches="tight")
上面的代碼把圖像保存為背景不透明的png。還指定了分辨率dpi和bbox_inches =“tight”來盡量減少多余的空格。
結(jié)論
希望這個(gè)過程有助于你了解如何在日常的數(shù)據(jù)分析中更有效地使用matplotlib。 如果在做分析時(shí)養(yǎng)成使用這種方法的習(xí)慣,你應(yīng)該可以快速定制出任何你需要的圖像。
作為***的福利,我引入一個(gè)快速指南來總結(jié)所有的概念。希望這有助于把這篇文章聯(lián)系起來,并為今后使用參考提供方便。