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 看起來更加接近。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM