如何優雅而高效地使用Matplotlib實現資料視覺化

路雪發表於2018-01-16

Matplotlib 能建立非常多的視覺化圖表,它也有一個豐富的 Python 工具生態環境,很多更高階的視覺化工具使用 Matplotlib 作為基礎庫。因此本文旨在提供一種高效的 Matplotlib 使用方法,並希望該方法可以幫助大家理解如何更有效地進行日常資料分析工作。

引言

對新手來說 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 視覺化。

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

如何優雅而高效地使用Matplotlib實現資料視覺化

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

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

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

開始

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

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

開始,我打算設定輸入,讀取一些資料:

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()

如何優雅而高效地使用Matplotlib實現資料視覺化

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

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)

下圖是資料。

如何優雅而高效地使用Matplotlib實現資料視覺化

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

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']

使用如下簡單風格:

plt.style.use('ggplot')

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

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

如何優雅而高效地使用Matplotlib實現資料視覺化

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

自定義圖表

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

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

生成的圖表和原始圖表基本一樣,不過我們向 plt.subplots() 新增了一個額外的呼叫,並將 ax 傳輸至繪圖函式。因此,透過 ax 或 fig 物件可以執行任何自定義。

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

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

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');

如何優雅而高效地使用Matplotlib實現資料視覺化

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

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')

如何優雅而高效地使用Matplotlib實現資料視覺化

為了進一步展示該方法,我們還可以使用 plt.subplots() 函式可以定義影像尺寸,一般以英寸為單位。我們還可以使用 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)

如何優雅而高效地使用Matplotlib實現資料視覺化

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

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

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)

現在我們有了格式化程式函式,就需要定義它,並將其應用到 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)

如何優雅而高效地使用Matplotlib實現資料視覺化

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

# 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)

如何優雅而高效地使用Matplotlib實現資料視覺化

圖表

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

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

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

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

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

# 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)

如何優雅而高效地使用Matplotlib實現資料視覺化

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

Matplotlib 支援多種不同檔案儲存格式。你可以使用 fig.canvas.get_supported_filetypes() 檢視系統支援的檔案格式:

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 物件,因此我們可以將影像儲存成多種格式:

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

結論

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

如何優雅而高效地使用Matplotlib實現資料視覺化

相關文章