如何優雅而高效地使用Matplotlib實現數據可視化

本文經機器之心(微信公眾號:almosthuman2014)授權轉載,禁止二次轉載。

引言

對新手來說 Python 可視化實在有些令人挫敗。有很多不同的選項,如何選擇正確的選項是一個挑戰。例如,兩年前這篇文章《Overview of Python Visualization Tools》仍然吸引了大量讀者。在那篇文章中,我否定了 Matplotlib。但是,在使用過 pandas、scikit-learn、seaborn 和其他 Python 數據科學包之後,我覺得之前否認 Matplotlib 的行為有點不成熟。坦白講,當時我不是很了解 Matplotlib,也不懂如何在我的工作流中高效使用 Matplotlib。

現在我學習了一些工具,了解了如何基於 Matplotlib 使用這些工具,Matplotlib 逐漸變成了可視化工具的核心。本文將展示如何使用 Matplotlib。我堅定地認為 Matplotlib 是 Python 數據科學包必不可少的一部分,希望這篇文章可以幫助大家了解如何使用 Matplotlib 進行 Python 可視化。

為什麼大家都在否定 Matplotlib?

我認為,Matplotlib 對於新手來說比較難存在幾個原因。首先,Matplotlib 有兩個界面。第一個界面基於 MATLAB,使用基於狀態的介面。第二個界面是面向對象的介面。本文就不展開介紹 Matplotlib 有兩個界面的原因,但了解這兩種方法在使用 Matplotlib 繪圖時會很重要。兩個界面會引起混淆的原因可以通過 Stack Overflow 和谷歌搜索查找一些信息。此外,新用戶將發現混淆問題有多個解決方案,但是這些問題看起來類似卻不完全相同。從我的個人經驗來講,我們從以前的代碼中可以看出有一些 Matplotlib 代碼的混雜。

關鍵點

Matplotlib 新手應該學習和使用面向對象的介面。

使用 Matplotlib 的另一個歷史性挑戰是一些默認的樣式缺乏吸引力。在 R 使用 ggplot 就可以生成相當不錯的圖,而 Matplotlib 相對來說有點丑。好消息是 Matplotlib 2.0 中的樣式好看了很多,你可以用最小的努力生成可視化。

第三個挑戰是你不確定什麼時候該使用 Matplotlib,什麼時候該使用基於 Matplotlib 構建的工具,如 pandas 或 seaborn。大部分時候做一件事都有多種選擇,但是對於新手來說選擇正確的道路有些困難。

為什麼使用 Matplotlib?

儘管 Matplotlib 有這麼多問題,我還是喜歡用它。因為它很強大,這個庫允許你創建幾乎所有的可視化圖表。此外,圍繞 Matplotlib 有一個豐富的 Python 工具生態環境,很多更高級的可視化工具使用 Matplotlib 作為基礎庫。因此如果你想在 Python 數據科學工具包中進行任何操作,你需要對如何使用 Matplotlib 有一些基礎了解。這就是本文其餘部分的重點,提供一種高效使用 Matplotlib 的基礎方法。

前提

推薦以下步驟學習如何使用 Matplotlib:

  • 1. 學習 Matplotlib 的基本術語,具體來說就是什麼是 Figure 和 Axes。
  • 2. 一直使用面向對象的界面,養成習慣。
  • 3. 用基礎的 pandas 繪圖開始可視化。
  • 4. 使用 seaborn 進行稍微複雜的數據可視化。
  • 5. 使用 Matplotlib 自定義 pandas 或 seaborn 可視化。

下圖非常重要,有助於理解圖的不同術語。

大部分術語很直接易懂,需要牢記的是 Figure 是可能包含一或多個 axes 的最終圖像。Axes 代表單個圖。一旦你理解這些是什麼以及如何通過面向對象的 API 評估它們,其餘步驟就很簡單了。

了解這個知識還有一個好處,就是當你在網路上看東西的時候有一個出發點。如果你花時間了解了這個點,那麼其他的 Matplotlib API 才有意義。此外,很多高級 Python 包,如 seaborn 和 ggplot 依賴於 Matplotlib 構建,因此理解了基礎,學習更強大的框架才更加容易。

最後,我不是說你應該逃避其他優秀選項,如 ggplot(又名 ggpy)、bokeh、plotly 或 altair。我只是認為你需要對 matplotlib + pandas + seaborn 有一個基礎的了解。了解基礎可視化棧之後,你就可以探索其他優秀工具,根據需求做出合適的選擇。

開始

下面主要介紹如何在 pandas 中創建基礎的可視化以及使用 Matplotlib 定製最常用的項。了解基礎流程有助於更直觀地進行自定義。

我主要關注最常見的繪圖任務,如標註軸、調整圖形界限(limit)、更新圖標題、保存圖像和調整圖例。

開始,我打算設置輸入,讀取一些數據:

import pandas as pdnimport matplotlib.pyplot as pltnfrom matplotlib.ticker import FuncFormatternndf = pd.read_excel("https://github.com/chris1610/pbpython/blob/master/data/sample-salesv3.xlsx?raw=true")ndf.head()n

數據包括 2014 年的銷售交易額。為簡短起見,我將總結這些數據,列出前十名客戶的採購次數和交易額。繪圖時我將對各列進行重命名。

top_10 = (df.groupby(name)[ext price, quantity].agg({ext price: sum, quantity: count})n .sort_values(by=ext price, ascending=False))[:10].reset_index()ntop_10.rename(columns={name: Name, ext price: Sales, quantity: Purchases}, inplace=True)n

下圖是數據。

現在數據以簡單的表格形式呈現,我們再來看一下如何將數據繪製成條形圖。如前所述,Matplotlib 具備多種不同風格,可用於渲染圖表。你可以使用 plt.style.available 查看你的系統可用的風格。

plt.style.availablen[seaborn-dark,n seaborn-dark-palette,n fivethirtyeight,n seaborn-whitegrid,n seaborn-darkgrid,n seaborn,n bmh,n classic,n seaborn-colorblind,n seaborn-muted,n seaborn-white,n seaborn-talk,n grayscale,n dark_background,n seaborn-deep,n seaborn-bright,n ggplot,n seaborn-paper,n seaborn-notebook,n seaborn-poster,n seaborn-ticks,n seaborn-pastel]n

使用如下簡單風格:

plt.style.use(ggplot)n

現在我們有了好看的風格,第一步就是使用標準 pandas 繪圖函數繪製數據:

top_10.plot(kind=barh, y="Sales", x="Name")n

推薦使用 pandas 繪圖的原因在於它是一種快速便捷地建立可視化原型的方式。

自定義圖表

如果你對該圖表的重要部分都很滿意,那麼下一步就是對它執行自定義。一些自定義(如添加標題和標籤)可以使用 pandas plot 函數輕鬆搞定。但是,你可能會發現自己需要在某個時刻跳出來。這就是我推薦你養成以下習慣的原因:

fig, ax = plt.subplots()ntop_10.plot(kind=barh, y="Sales", x="Name", ax=ax)n

生成的圖表和原始圖表基本一樣,不過我們向 plt.subplots() 添加了一個額外的調用,並將 ax 傳輸至繪圖函數。因此,通過 ax 或 fig 對象可以執行任何自定義。

我們利用 pandas 實現快速繪圖,現在利用 Matplotlib 獲取所有功能。通過使用命名慣例,調整別人的解決方案適應自己的需求變得更加直接簡單了。

假設我們想調整一些軸標籤,且 ax 變數中有多個軸,可以進行一些操作:

fig, ax = plt.subplots()ntop_10.plot(kind=barh, y="Sales", x="Name", ax=ax)nax.set_xlim([-10000, 140000])nax.set_xlabel(Total Revenue)nax.set_ylabel(Customer);n

這是另一種改變標題和標籤的簡單方式:

fig, ax = plt.subplots()ntop_10.plot(kind=barh, y="Sales", x="Name", ax=ax)nax.set_xlim([-10000, 140000])nax.set(title=2014 Revenue, xlabel=Total Revenue, ylabel=Customer)n

為了進一步展示該方法,我們還可以使用 plt.subplots() 函數可以定義圖像尺寸,一般以英寸為單位。我們還可以使用 ax.legend().set_visible(False) 移除圖例。

fig, ax = plt.subplots(figsize=(5, 6))ntop_10.plot(kind=barh, y="Sales", x="Name", ax=ax)nax.set_xlim([-10000, 140000])nax.set(title=2014 Revenue, xlabel=Total Revenue)nax.legend().set_visible(False)n

要想修改這個圖像,你可能需要執行很多操作。圖中最礙眼的可能是總收益額的格式。Matplotlib 可以使用 FuncFormatter 解決這一問題。該函數用途多樣,允許用戶定義的函數應用到值,並返回格式美觀的字元串。

以下是貨幣格式化函數,用於處理數十萬美元區間的數值:

def currency(x, pos):n The two args are the value and tick positionn if x >= 1000000:n return ${:1.1f}M.format(x*1e-6)n return ${:1.0f}K.format(x*1e-3)n

現在我們有了格式化程序函數,就需要定義它,並將其應用到 x 軸。完整代碼如下:

fig, ax = plt.subplots()ntop_10.plot(kind=barh, y="Sales", x="Name", ax=ax)nax.set_xlim([-10000, 140000])nax.set(title=2014 Revenue, xlabel=Total Revenue, ylabel=Customer)nformatter = FuncFormatter(currency)nax.xaxis.set_major_formatter(formatter)nax.legend().set_visible(False)n

這張圖美觀多了,非常好地展示了自定義問題解決方案的靈活性。最後要說的自定義特徵是向圖表添加註釋。你可以使用 ax.axvline() 畫垂直線,使用 ax.text() 添加自定義文本。就以上示例,我們可以畫一條表示平均值的線,包括代表 3 個新客戶的標籤。以下是完整代碼:

# Create the figure and the axesnfig, ax = plt.subplots()nn# Plot the data and get the averagedntop_10.plot(kind=barh, y="Sales", x="Name", ax=ax)navg = top_10[Sales].mean()nn# Set limits and labelsnax.set_xlim([-10000, 140000])nax.set(title=2014 Revenue, xlabel=Total Revenue, ylabel=Customer)nn# Add a line for the averagenax.axvline(x=avg, color=b, label=Average, linestylex=--, linewidth_=1)nn# Annotate the new customersnfor cust in [3, 5, 8]:n ax.text(115000, cust, "New Customer")nn# Format the currencynformatter = FuncFormatter(currency)nax.xaxis.set_major_formatter(formatter)nn# Hide the legendnax.legend().set_visible(False)n

圖表

目前,我們所做的所有改變都是針對單個圖表。我們還能夠在圖像上添加多個表,使用不同的選項保存整個圖像。

如果我們確定要在同一個圖像上放置兩個表,那麼我們應該對如何做有一個基礎了解。首先,創建圖像,然後創建軸,再將它們繪製成圖表。使用 plt.subplots() 可以完成該操作:

fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=(7, 4))n

在這個例子中,我使用 nrows 和 ncols 指定大小,這對新用戶來說比較清晰易懂。我還使用 sharey=True 以使 y 軸共享相同的標籤。

該示例很靈活,因為不同的軸可以解壓成 ax0 和 ax1。現在我們有了這些軸,就可以像上述示例中那樣繪圖,然後把一個圖放在 ax0 上,另一個圖放在 ax1。

# Get the figure and the axesnfig, (ax0, ax1) = plt.subplots(nrows=1,ncols=2, sharey=True, figsize=(7, 4))ntop_10.plot(kind=barh, y="Sales", x="Name", ax=ax0)nax0.set_xlim([-10000, 140000])nax0.set(title=Revenue, xlabel=Total Revenue, ylabel=Customers)nn# Plot the average as a vertical linenavg = top_10[Sales].mean()nax0.axvline(x=avg, color=b, label=Average, linestylex=--, linewidth_=1)nn# Repeat for the unit plotntop_10.plot(kind=barh, y="Purchases", x="Name", ax=ax1)navg = top_10[Purchases].mean()nax1.set(title=Units, xlabel=Total Units, ylabel=)nax1.axvline(x=avg, color=b, label=Average, linestylex=--, linewidth_=1)nn# Title the figurenfig.suptitle(2014 Sales Analysis, fontsize=14, fontweight=bold);nn# Hide the legendsnax1.legend().set_visible(False)nax0.legend().set_visible(False)n

現在,我已經在 jupyter notebook 中用 %matplotlib inline 展示了很多圖像。但是,在很多情況下你需要以特定格式保存圖像,將其和其他呈現方式整合在一起。

Matplotlib 支持多種不同文件保存格式。你可以使用 fig.canvas.get_supported_filetypes() 查看系統支持的文件格式:

fig.canvas.get_supported_filetypes()n{eps: Encapsulated Postscript,n jpeg: Joint Photographic Experts Group,n jpg: Joint Photographic Experts Group,n pdf: Portable Document Format,n pgf: PGF code for LaTeX,n png: Portable Network Graphics,n ps: Postscript,n raw: Raw RGBA bitmap,n rgba: Raw RGBA bitmap,n svg: Scalable Vector Graphics,n svgz: Scalable Vector Graphics,n tif: Tagged Image File Format,n tiff: Tagged Image File Format}n

我們有 fig 對象,因此我們可以將圖像保存成多種格式:

fig.savefig(sales.png, transparent=False, dpi=80, bbox_inches="tight")n

結論

該版本將圖表保存為不透明背景的 png 文件。我還指定 dpi 和 bbox_inches="tight" 以最小化多餘空白。最後,希望該方法可以幫助大家理解如何更有效地使用 Matplotlib 進行日常數據分析。

原 文:機器之心

作 者:器之心編譯

更多文章:SDK.CN - 中國領先的開發者服務平台


推薦閱讀:

Matplotlib 可視化系列一
python matplotlib中axes與axis的區別?
matplotlib圖例中文亂碼?

TAG:Python | Matplotlib | 数据可视化 |