8.13 自定義刻度
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
本節(jié)是《Python 數(shù)據(jù)科學(xué)手冊》(Python Data Science Handbook)的摘錄。
Matplotlib 的默認(rèn)刻度定位器和格式化程序,在許多常見情況下通常都足夠了,但對于每個繪圖都不是最佳選擇。本節(jié)將提供幾個刻度位置和格式的示例,它們調(diào)整你感興趣的特定繪圖類型。
在我們進(jìn)入示例之前,我們最好進(jìn)一步了解 Matplotlib 繪圖的對象層次結(jié)構(gòu)。Matplotlib 旨在用 Python 對象表示繪圖中出現(xiàn)的所有內(nèi)容:例如,回想一下figure是繪圖元素所在的邊框。每個 Matplotlib 對象也可以充當(dāng)子對象的容器:例如,每個figure可以包含一個或多個axes對象,它們的每個又包含表示繪圖內(nèi)容的其他對象。
刻度線也不例外。 每個axes都有屬性xaxis和yaxis,它們又具有一些屬性,包括構(gòu)成軸域的直線,刻度和標(biāo)簽。
主要和次要刻度
在每個軸內(nèi),有主要刻度標(biāo)記和次要刻度標(biāo)記的概念。 正如名稱所暗示的那樣,主要刻度通常更大或更明顯,而次要刻度通常更小。 默認(rèn)情況下,Matplotlib 很少使用次要刻度,但是你可以在對數(shù)繪圖中看到它們:
import matplotlib.pyplot as plt
plt.style.use('classic')
%matplotlib inline
import numpy as np
ax = plt.axes(xscale='log', yscale='log')
ax.grid();

我們在這里看到每個主刻度線顯示為一個大刻度線和一個標(biāo)簽,而每個次刻度線顯示為一個沒有標(biāo)簽的較小刻度線。
這些刻度屬性 - 位置和標(biāo)簽 - 也就是說,可以通過設(shè)置每個軸的formatter和locator對象來定制。 讓我們檢查剛剛展示的繪圖的x軸:
print(ax.xaxis.get_major_locator())
print(ax.xaxis.get_minor_locator())
'''
<matplotlib.ticker.LogLocator object at 0x10dbaf630>
<matplotlib.ticker.LogLocator object at 0x10dba6e80>
'''
print(ax.xaxis.get_major_formatter())
print(ax.xaxis.get_minor_formatter())
'''
<matplotlib.ticker.LogFormatterMathtext object at 0x10db8dbe0>
<matplotlib.ticker.NullFormatter object at 0x10db9af60>
'''
我們看到主要和次要刻度標(biāo)簽的位置都由LogLocator指定(這對于對數(shù)圖是有意義的)。 但是,次要刻度的標(biāo)簽格式為NullFormatter:這表示不會顯示任何標(biāo)簽。我們現(xiàn)在將展示一些為各種圖設(shè)置這些定位器和格式化器的示例。
隱藏刻度或標(biāo)簽
也許最常見的刻度/標(biāo)簽格式化操作是隱藏刻度或標(biāo)簽。這可以使用plt.NullLocator()和plt.NullFormatter()來完成,如下所示:
ax = plt.axes()
ax.plot(np.random.rand(50))
ax.yaxis.set_major_locator(plt.NullLocator())
ax.xaxis.set_major_formatter(plt.NullFormatter())

請注意,我們已經(jīng)從x軸移除了標(biāo)簽(但保留了刻度線/網(wǎng)格線),并從y軸中刪除了刻度線(以及標(biāo)簽)。
在許多情況下,不顯示刻度可能很有用 - 例如,當(dāng)你想要顯示圖像網(wǎng)格的時候。
例如,考慮下圖,它包含不同的面部圖像,這是監(jiān)督機(jī)器學(xué)習(xí)問題中常用的一個例子(例如,參見“深入:支持向量機(jī)”):
fig, ax = plt.subplots(5, 5, figsize=(5, 5))
fig.subplots_adjust(hspace=0, wspace=0)
# 從 sklearn 獲取一些人臉數(shù)據(jù)
from sklearn.datasets import fetch_olivetti_faces
faces = fetch_olivetti_faces().images
for i in range(5):
for j in range(5):
ax[i, j].xaxis.set_major_locator(plt.NullLocator())
ax[i, j].yaxis.set_major_locator(plt.NullLocator())
ax[i, j].imshow(faces[10 * i + j], cmap="bone")

請注意,每個圖像都有自己的軸域,我們將定位器設(shè)置為null,因?yàn)榭潭戎担ㄔ谶@種情況下為像素數(shù))不會傳達(dá)這個特定可視化的相關(guān)信息。
減少或增加刻度數(shù)量
默認(rèn)設(shè)置的一個常見問題是,較小的子圖最終會擁有密集的標(biāo)簽。我們可以在這里顯示的繪圖網(wǎng)格中看到它:
fig, ax = plt.subplots(4, 4, sharex=True, sharey=True)

特別是對于x刻度,數(shù)字幾乎重疊并使它們很難看清。我們可以用plt.MaxNLocator()解決這個問題,它允許我們指定要顯示的最大刻度數(shù)。給定此最大數(shù)量,Matplotlib 將使用內(nèi)部邏輯來選擇特定的刻度位置:
# 對于每個軸,設(shè)置 x 和 y 主要定位器
for axi in ax.flat:
axi.xaxis.set_major_locator(plt.MaxNLocator(3))
axi.yaxis.set_major_locator(plt.MaxNLocator(3))
fig

這使事情變得更加干凈。 如果你想要更多地控制等間隔的刻度位置,你也可以使用plt.MultipleLocator,我們將在下一節(jié)討論。
花式刻度格式
Matplotlib 的默認(rèn)刻度格式可能會有很多不足之處:它可以作為一個泛用的默認(rèn)值,但有時你還想做更多的事情??紤]一下正弦和余弦的繪圖:
# 繪制正弦和余弦曲線
fig, ax = plt.subplots()
x = np.linspace(0, 3 * np.pi, 1000)
ax.plot(x, np.sin(x), lw=3, label='Sine')
ax.plot(x, np.cos(x), lw=3, label='Cosine')
# 配置網(wǎng)格,圖例和限制
ax.grid(True)
ax.legend(frameon=False)
ax.axis('equal')
ax.set_xlim(0, 3 * np.pi);

我們可能想做一些改變。 首先,以 π 的倍數(shù)的刻度線和網(wǎng)格線來區(qū)分這些數(shù)據(jù)更加自然。 我們可以通過設(shè)置MultipleLocator來實(shí)現(xiàn),它可以在你提供的數(shù)字的倍數(shù)處,設(shè)置刻度線。 為了更好地衡量,我們將以π/4的倍數(shù)添加主要和次要刻度:
ax.xaxis.set_major_locator(plt.MultipleLocator(np.pi / 2))
ax.xaxis.set_minor_locator(plt.MultipleLocator(np.pi / 4))
fig

但現(xiàn)在這些刻度標(biāo)簽看起來有點(diǎn)傻:我們可以看到它們是 π 的倍數(shù),但十進(jìn)制表示并沒有立即傳達(dá)這一點(diǎn)。要解決這個問題,我們可以更改刻度格式化器。對于我們想要做的事情,沒有內(nèi)置格式化器,所以我們改為使用plt.FuncFormatter,它接受用戶定義的函數(shù),對刻度輸出進(jìn)行細(xì)粒度控制:
def format_func(value, tick_number):
# 尋找 pi/2 倍數(shù)的數(shù)字
N = int(np.round(2 * value / np.pi))
if N == 0:
return "0"
elif N == 1:
return r"$\pi/2$"
elif N == 2:
return r"$\pi$"
elif N % 2 > 0:
return r"${0}\pi/2$".format(N)
else:
return r"${0}\pi$".format(N // 2)
ax.xaxis.set_major_formatter(plt.FuncFormatter(format_func))
fig

這要好得多! 請注意,我們已經(jīng)使用了 Matplotlib 的 LaTeX 支持,通過將字符串括在美元符號中來指定。 這對于顯示數(shù)學(xué)符號和公式非常方便:在這種情況下,$\pi$顯示為希臘字符π。
plt.FuncFormatter()提供繪圖刻度外觀的極細(xì)粒度控制,并且在準(zhǔn)備繪圖用于演示或發(fā)布時非常方便。
格式化器和定位器的總結(jié)
我們已經(jīng)提到了一些可用的格式化器和定位器。我們將簡要列出所有內(nèi)置定位器和格式化器的選項(xiàng)來結(jié)束本節(jié)。 對于其中任何內(nèi)容的更多信息,請參閱文檔字符串或 Matplotlib 在線文檔。plt命名空間中提供以下東西:
| 定位器類 | 描述 |
|---|---|
NullLocator |
沒有刻度 |
FixedLocator |
刻度定位器是固定的 |
IndexLocator |
索引繪圖的定位器(也就是,其中x = range(len(y))) |
LinearLocator |
等間隔的刻度,從最小值到最大值 |
LogLocator |
對數(shù)刻度,從最小值到最大值 |
MultipleLocator |
刻度和范圍是基數(shù)的倍數(shù) |
MaxNLocator |
在不錯的位置尋找小于等于最大值的刻度數(shù) |
AutoLocator |
(默認(rèn))帶有簡單默認(rèn)值的MaxNLocator
|
AutoMinorLocator |
用于次要刻度的定位器 |
| 格式化器類 | 描述 |
|---|---|
NullFormatter |
刻度上沒有標(biāo)簽 |
IndexFormatter |
從一列標(biāo)簽中設(shè)置字符串 |
FixedFormatter |
手動為標(biāo)簽設(shè)置字符串 |
FuncFormatter |
使用用戶定義的函數(shù)設(shè)置標(biāo)簽 |
FormatStrFormatter |
對每個值使用格式化字符串 |
ScalarFormatter |
(默認(rèn))用于標(biāo)量值的格式化器 |
LogFormatter |
對數(shù)軸域的默認(rèn)格式化器 |
我們將在本書的其余部分看到更多這些例子。