1. 程式人生 > 實用技巧 >Matplotlib基礎--個性化顏色條

Matplotlib基礎--個性化顏色條

圖例可以將離散的點標示為離散的標籤。對於建立在不同顏色之上的連續的值(點線面)來說,標註了的顏色條是非常方便的工具。Matplotlib 的顏色條是獨立於圖表之外的一個類似於比色卡的圖形,用來展示圖表中不同顏色的數值含義。本節內容中的所有帶色彩的圖都可以在(https://github.com/wangyingsm/Python-Data-Science-Handbook)中找到。我們還是首先匯入本節需要的包和模組:

import matplotlib.pyplot as plt
plt.style.use('classic')
import numpy as np

通過plt.colorbar函式可以建立最簡單的顏色條,在本節中我們會多次看到:

x = np.linspace(0, 10, 1000)
I = np.sin(x) * np.cos(x[:, np.newaxis])

plt.imshow(I)
plt.colorbar();
plt.show()

我們下面來討論如何個性化顏色條以及在不同的場合高效的使用它們。

自定義顏色條

顏色條可以通過cmap引數指定使用的色譜系統(或叫色圖):

plt.imshow(I, cmap='gray');

所有可用的色圖都可以在plt.cm模組中找到;在 IPython 中使用 Tab 自動補全功能能列出所有的色圖列表:

plt.cm.<TAB>

但是知道在哪裡選擇色圖只是第一步:更重要的是在各種選項中選出合適的色圖。這個選擇比你預料的要微妙的多。

選擇色圖

在視覺化方案中選擇顏色完整的介紹說明超出了本書的範圍,如果你對這個課題和相關內容有興趣,可以參考文章["繪製更漂亮圖表的 10 個簡單規則"](http://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1003833)。Matplotlib 的線上文件也有一章關於色圖選擇的有趣討論。

通常來說,你應該注意以下三種不同型別的色圖:

  • 序列色圖:這型別的色譜只包括一個連續序列的色系(例如binaryviridis)。
  • 分化色圖:這型別的色譜包括兩種獨立的色系,這兩種顏色有著非常大的對比度(例如RdBuPuOr)。
  • 定性色圖
    :這型別的色圖混合了非特定連續序列的顏色(例如rainbowjet)。

jet色圖,在 Matplotlib 2.0 版本之前都是預設的色圖,是定性色圖的一個例子。jet作為預設色圖的位置其實有點尷尬,因為定性圖通常都不是對定量資料進行展示的好選擇。原因是定性圖通常都不能在範圍增加時提供亮度的均勻增長。

我們可以通過將jet顏色條轉換為黑白來看到這點:

from matplotlib.colors import LinearSegmentedColormap

def grayscale_cmap(cmap):
    """返回給定色圖的灰度版本"""
    cmap = plt.cm.get_cmap(cmap) # 使用名稱獲取色圖物件
    colors = cmap(np.arange(cmap.N)) # 將色圖物件轉為RGBA矩陣,形狀為N×4

    # 將RGBA顏色轉換為灰度
    # 參考 http://alienryderflex.com/hsp.html
    RGB_weight = [0.299, 0.587, 0.114] # RGB三色的權重值
    luminance = np.sqrt(np.dot(colors[:, :3] ** 2, RGB_weight)) # RGB平方值和權重的點積開平方根
    colors[:, :3] = luminance[:, np.newaxis] # 得到灰度值矩陣
    # 返回相應的灰度值色圖
    return LinearSegmentedColormap.from_list(cmap.name + "_gray", colors, cmap.N)


def view_colormap(cmap):
    """將色圖對應的灰度版本繪製出來"""
    cmap = plt.cm.get_cmap(cmap)
    colors = cmap(np.arange(cmap.N))

    cmap = grayscale_cmap(cmap)
    grayscale = cmap(np.arange(cmap.N))

    fig, ax = plt.subplots(2, figsize=(6, 2),
                           subplot_kw=dict(xticks=[], yticks=[]))
    ax[0].imshow([colors], extent=[0, 10, 0, 1])
    ax[1].imshow([grayscale], extent=[0, 10, 0, 1])
view_colormap('jet')

注意一下上面的灰度圖中亮條紋的位置。即使在上述彩色圖中,也出現了這種不規則的亮條紋,這會導致眼睛被區域中亮條紋所吸引,這很可能造成閱讀者被不重要的資料集部分干擾了。更好的選擇是使用類似viridis這樣的色圖(Matplotlib 2.0 後預設色圖),它們被設計為有著均勻的亮度變化。因此它們無論是在彩色圖中還是在灰度圖中都有著同樣的亮度變化:

view_colormap('viridis')

如果你更喜歡彩虹方案,另一個好的選擇是使用cubehelix色圖:

view_colormap('cubehelix')

對於其他的情況,例如某種正負分佈的資料集,雙色顏色條如RdBu(Red-Blue)會很常用。然而正如你從下面例子看到的,如果將雙色顏色條轉化為灰度的話,正負或兩級的資訊就會丟失:

view_colormap('RdBu')

後面我們會看到更多使用這些色圖的例子。

Matplotlib 中有大量可用的色圖;要看到它們的列表,你可以使用 IPython 來探索plt.cm模組。要在 Python 中更加正規的使用顏色,你可以檢視 Seaborn 庫的工具和文件

顏色限制和擴充套件

Matplotlib 允許你對顏色條進行大量的自定義。顏色條本身就是一個plt.Axes物件,因此所有軸和刻度定製的技巧都可以應用在上面。顏色條也有著一些有趣的自定義行為:例如,我們可以縮小顏色的範圍並且通過設定extend引數將超出範圍之外的數值展示為頂部和底部的三角箭頭形狀。這對於展示一些受到噪聲干擾的資料時非常方便:

x = np.linspace(0, 10, 1000)
I = np.sin(x) * np.cos(x[:, np.newaxis])
# 在I陣列中人為生成不超過1%的噪聲
speckles = (np.random.random(I.shape) < 0.01)
I[speckles] = np.random.normal(0, 3, np.count_nonzero(speckles))

plt.figure(figsize=(10, 3.5))
# 不考慮去除噪聲時的顏色分佈
plt.subplot(1, 2, 1)
plt.imshow(I, cmap='RdBu')
plt.colorbar()
# 設定去除噪聲時的顏色分佈
plt.subplot(1, 2, 2)
plt.imshow(I, cmap='RdBu')
plt.colorbar(extend='both')
plt.clim(-1, 1);
plt.show()

注意到在左邊的圖表中,預設的顏色閾值是包括了噪聲的,因此整體的條紋形狀都被噪聲資料沖刷淡化了。而右邊的圖表,我們手動設定了顏色的閾值,並在繪製顏色條是加上了extend引數來表示超出閾值的資料。對於我們的資料來說,右圖比左圖要好的多。

離散顏色條

色圖預設是連續的,但是在某些情況下你可能需要展示離散值。最簡單的方法是使plt.cm.get_cmap()函式,在傳遞某個色圖名稱的同時,還額外傳遞一個顏色分桶的數量值引數給該函式:

plt.imshow(I, cmap=plt.cm.get_cmap('Blues', 6))
plt.colorbar()
plt.clim(-1, 1);

離散色圖的使用方式和其他色圖沒有任何區別。

例子:手寫數字

最後我們來看一個很有實用價值的例子,讓我們實現對一些手寫數字影象資料的視覺化分析。這個資料包含在 Sciki-Learn 中,以供包含有將近 2,000 張8*8大小的不同筆跡的手寫數字縮圖。

首先,我們下載這個資料集,然後使用plt.imshow()將其中部分資料展示出來:

# 讀取數字0-5的手寫影象,然後使用Matplotlib展示頭64張縮圖
from sklearn.datasets import load_digits
digits = load_digits(n_class=6)

fig, ax = plt.subplots(8, 8, figsize=(6, 6))
for i, axi in enumerate(ax.flat):
    axi.imshow(digits.images[i], cmap='binary')
    axi.set(xticks=[], yticks=[])
plt.show()

因為每個數字都是使用 64 個畫素點渲染出來的,我們可以認為每個數字是一個 64 維空間中的點:每個維度代表這其中一個畫素的灰度值。但是要在圖表中將這麼高維度空間的聯絡可視化出來是非常困難的。有一種做法是使用降維技術,比方說使用流形學習來減少資料的維度然而不會丟失資料中有效的資訊。

我們來看一下將這些手寫數字影象資料對映到二維流形學習當中:

# 使用Isomap將手寫數字影象對映到二維流形學習中
from sklearn.manifold import Isomap
iso = Isomap(n_components=2)
projection = iso.fit_transform(digits.data)

我們使用離散顏色條來展示結果,設定ticksclim來進一步美化結果的顏色條:

# 繪製圖表結果
plt.scatter(projection[:, 0], projection[:, 1], lw=0.1,
            c=digits.target, cmap=plt.cm.get_cmap('cubehelix', 6))
plt.colorbar(ticks=range(6), label='digit value')
plt.clim(-0.5, 5.5)
plt.show()

我們從流形學習中的對映中可以觀察到一些有趣現象:例如,圖表中 5 和 3 有一些重疊的部分,這表示一些手寫體中 5 和 3 是比較難以辨別的,因此對於自動識別演算法來說這是比較容易混淆的部分。而 0 和 1,它們在圖表中距離很遠,這表示兩者比較容易辨別,不太可能造成混淆。這個圖表分析與我們的直覺一致,因為 5 和 3 顯然比 0 和 1 看起來更加接近。