色彩映射正規化#

預設使用色彩映射的物件會將色彩映射中的色彩從資料值 *vmin* 線性映射到 *vmax*。例如:

pcm = ax.pcolormesh(x, y, Z, vmin=-1., vmax=1., cmap='RdBu_r')

會將 *Z* 中的資料從 -1 線性映射到 +1,因此 *Z=0* 會給出色彩映射 *RdBu_r* 中心的顏色(在本例中為白色)。

Matplotlib 會分兩個步驟進行此映射,首先將輸入資料正規化到 [0, 1],然後映射到色彩映射中的索引。正規化是在 matplotlib.colors() 模組中定義的類別。預設的線性正規化是 matplotlib.colors.Normalize()

將資料映射到色彩的藝術家會傳遞引數 *vmin* 和 *vmax* 來建構 matplotlib.colors.Normalize() 實例,然後呼叫它

>>> import matplotlib as mpl
>>> norm = mpl.colors.Normalize(vmin=-1, vmax=1)
>>> norm(0)
0.5

但是,有時在非線性方式下將資料映射到色彩映射會很有用。

對數#

最常見的轉換之一是透過取其對數(以 10 為底)來繪製資料。此轉換對於顯示跨不同尺度的變化很有用。使用 colors.LogNorm 會透過 \(log_{10}\) 正規化資料。在下面的範例中,有兩個凸起,一個比另一個小得多。使用 colors.LogNorm,可以清楚地看到每個凸起的形狀和位置

import matplotlib.pyplot as plt
import numpy as np

from matplotlib import cm
import matplotlib.cbook as cbook
import matplotlib.colors as colors

N = 100
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]

# A low hump with a spike coming out of the top right.  Needs to have
# z/colour axis on a log scale, so we see both hump and spike. A linear
# scale only shows the spike.
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X * 10)**2 - (Y * 10)**2)
Z = Z1 + 50 * Z2

fig, ax = plt.subplots(2, 1)

pcm = ax[0].pcolor(X, Y, Z,
                   norm=colors.LogNorm(vmin=Z.min(), vmax=Z.max()),
                   cmap='PuBu_r', shading='auto')
fig.colorbar(pcm, ax=ax[0], extend='max')

pcm = ax[1].pcolor(X, Y, Z, cmap='PuBu_r', shading='auto')
fig.colorbar(pcm, ax=ax[1], extend='max')
plt.show()
colormapnorms

置中#

在許多情況下,資料是對稱於中心點的,例如,中心 0 周圍的正負異常。在這種情況下,我們希望將中心映射到 0.5,並且將與中心偏差最大的資料點映射到 1.0,如果其值大於中心,否則映射到 0.0。標準 colors.CenteredNorm 會自動建立此類映射。它非常適合與發散色彩映射結合使用,該映射使用不同的顏色邊緣,在中心以不飽和的顏色相遇。

如果對稱中心與 0 不同,則可以使用 *vcenter* 引數進行設定。若要在中心的兩側進行對數縮放,請參閱下面的 colors.SymLogNorm;若要在中心上方和下方套用不同的映射,請使用下面的 colors.TwoSlopeNorm

delta = 0.1
x = np.arange(-3.0, 4.001, delta)
y = np.arange(-4.0, 3.001, delta)
X, Y = np.meshgrid(x, y)
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
Z = (0.9*Z1 - 0.5*Z2) * 2

# select a divergent colormap
cmap = cm.coolwarm

fig, (ax1, ax2) = plt.subplots(ncols=2)
pc = ax1.pcolormesh(Z, cmap=cmap)
fig.colorbar(pc, ax=ax1)
ax1.set_title('Normalize()')

pc = ax2.pcolormesh(Z, norm=colors.CenteredNorm(), cmap=cmap)
fig.colorbar(pc, ax=ax2)
ax2.set_title('CenteredNorm()')

plt.show()
Normalize(), CenteredNorm()

對稱對數#

同樣地,有時也會出現正負資料,但我們仍然希望將對數縮放套用到兩者。在這種情況下,負數也會以對數方式縮放,並映射到較小的數字;例如,如果 vmin=-vmax,則負數會從 0 映射到 0.5,而正數則從 0.5 映射到 1。

由於接近零的值的對數趨於無窮大,因此需要在零周圍映射一小段線性範圍。參數 *linthresh* 允許使用者指定此範圍 (-*linthresh*,*linthresh*) 的大小。色彩映射中此範圍的大小由 *linscale* 設定。當 *linscale* == 1.0(預設值)時,線性範圍的正負兩半所使用的空間將等於對數範圍中的一個十年。

N = 100
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
Z = (Z1 - Z2) * 2

fig, ax = plt.subplots(2, 1)

pcm = ax[0].pcolormesh(X, Y, Z,
                       norm=colors.SymLogNorm(linthresh=0.03, linscale=0.03,
                                              vmin=-1.0, vmax=1.0, base=10),
                       cmap='RdBu_r', shading='auto')
fig.colorbar(pcm, ax=ax[0], extend='both')

pcm = ax[1].pcolormesh(X, Y, Z, cmap='RdBu_r', vmin=-np.max(Z), shading='auto')
fig.colorbar(pcm, ax=ax[1], extend='both')
plt.show()
colormapnorms

冪律#

有時,將色彩重新映射到冪律關係(即 \(y=x^{\gamma}\),其中 \(\gamma\) 是冪)會很有用。為此,我們使用 colors.PowerNorm。它將 *gamma* 作為引數(*gamma* == 1.0 只會產生預設的線性正規化)

注意

使用此類轉換繪製資料時,應該要有充分的理由。技術觀眾習慣於線性軸和對數軸以及資料轉換。冪律不太常見,應該明確告知觀眾它們已被使用。

N = 100
X, Y = np.mgrid[0:3:complex(0, N), 0:2:complex(0, N)]
Z1 = (1 + np.sin(Y * 10.)) * X**2

fig, ax = plt.subplots(2, 1, layout='constrained')

pcm = ax[0].pcolormesh(X, Y, Z1, norm=colors.PowerNorm(gamma=0.5),
                       cmap='PuBu_r', shading='auto')
fig.colorbar(pcm, ax=ax[0], extend='max')
ax[0].set_title('PowerNorm()')

pcm = ax[1].pcolormesh(X, Y, Z1, cmap='PuBu_r', shading='auto')
fig.colorbar(pcm, ax=ax[1], extend='max')
ax[1].set_title('Normalize()')
plt.show()
PowerNorm(), Normalize()

離散邊界#

Matplotlib 隨附的另一個正規化是 colors.BoundaryNorm。除了 *vmin* 和 *vmax* 之外,它還將資料要映射到的邊界作為引數。然後,顏色會在此「邊界」之間線性分佈。它也可以採用 *extend* 引數,以將超出範圍的上限和/或下限值新增至分佈顏色的範圍。例如:

>>> import matplotlib.colors as colors
>>> bounds = np.array([-0.25, -0.125, 0, 0.5, 1])
>>> norm = colors.BoundaryNorm(boundaries=bounds, ncolors=4)
>>> print(norm([-0.2, -0.15, -0.02, 0.3, 0.8, 0.99]))
[0 0 1 2 3 3]

注意:與其他標準不同,此標準會傳回從 0 到 *ncolors*-1 的值。

N = 100
X, Y = np.meshgrid(np.linspace(-3, 3, N), np.linspace(-2, 2, N))
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
Z = ((Z1 - Z2) * 2)[:-1, :-1]

fig, ax = plt.subplots(2, 2, figsize=(8, 6), layout='constrained')
ax = ax.flatten()

# Default norm:
pcm = ax[0].pcolormesh(X, Y, Z, cmap='RdBu_r')
fig.colorbar(pcm, ax=ax[0], orientation='vertical')
ax[0].set_title('Default norm')

# Even bounds give a contour-like effect:
bounds = np.linspace(-1.5, 1.5, 7)
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
pcm = ax[1].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r')
fig.colorbar(pcm, ax=ax[1], extend='both', orientation='vertical')
ax[1].set_title('BoundaryNorm: 7 boundaries')

# Bounds may be unevenly spaced:
bounds = np.array([-0.2, -0.1, 0, 0.5, 1])
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
pcm = ax[2].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r')
fig.colorbar(pcm, ax=ax[2], extend='both', orientation='vertical')
ax[2].set_title('BoundaryNorm: nonuniform')

# With out-of-bounds colors:
bounds = np.linspace(-1.5, 1.5, 7)
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256, extend='both')
pcm = ax[3].pcolormesh(X, Y, Z, norm=norm, cmap='RdBu_r')
# The colorbar inherits the "extend" argument from BoundaryNorm.
fig.colorbar(pcm, ax=ax[3], orientation='vertical')
ax[3].set_title('BoundaryNorm: extend="both"')
plt.show()
Default norm, BoundaryNorm: 7 boundaries, BoundaryNorm: nonuniform, BoundaryNorm: extend=

TwoSlopeNorm:在中心點任一側進行不同的映射#

有時,我們希望在概念中心點的任一側具有不同的色彩映射,並且我們希望這兩個色彩映射具有不同的線性刻度。一個範例是地形圖,其中陸地和海洋的中心為零,但陸地通常比水深範圍具有更大的海拔範圍,並且它們通常由不同的色彩映射表示。

dem = cbook.get_sample_data('topobathy.npz')
topo = dem['topo']
longitude = dem['longitude']
latitude = dem['latitude']

fig, ax = plt.subplots()
# make a colormap that has land and ocean clearly delineated and of the
# same length (256 + 256)
colors_undersea = plt.cm.terrain(np.linspace(0, 0.17, 256))
colors_land = plt.cm.terrain(np.linspace(0.25, 1, 256))
all_colors = np.vstack((colors_undersea, colors_land))
terrain_map = colors.LinearSegmentedColormap.from_list(
    'terrain_map', all_colors)

# make the norm:  Note the center is offset so that the land has more
# dynamic range:
divnorm = colors.TwoSlopeNorm(vmin=-500., vcenter=0, vmax=4000)

pcm = ax.pcolormesh(longitude, latitude, topo, rasterized=True, norm=divnorm,
                    cmap=terrain_map, shading='auto')
# Simple geographic plot, set aspect ratio because distance between lines of
# longitude depends on latitude.
ax.set_aspect(1 / np.cos(np.deg2rad(49)))
ax.set_title('TwoSlopeNorm(x)')
cb = fig.colorbar(pcm, shrink=0.6)
cb.set_ticks([-500, 0, 1000, 2000, 3000, 4000])
plt.show()
TwoSlopeNorm(x)

FuncNorm:任意函數正規化#

如果上述標準無法提供您想要的正規化,您可以使用 FuncNorm 來定義您自己的標準。請注意,此範例與 PowerNorm(冪為 0.5)相同

def _forward(x):
    return np.sqrt(x)


def _inverse(x):
    return x**2

N = 100
X, Y = np.mgrid[0:3:complex(0, N), 0:2:complex(0, N)]
Z1 = (1 + np.sin(Y * 10.)) * X**2
fig, ax = plt.subplots()

norm = colors.FuncNorm((_forward, _inverse), vmin=0, vmax=20)
pcm = ax.pcolormesh(X, Y, Z1, norm=norm, cmap='PuBu_r', shading='auto')
ax.set_title('FuncNorm(x)')
fig.colorbar(pcm, shrink=0.6)
plt.show()
FuncNorm(x)

自訂正規化:手動實作兩個線性範圍#

上面描述的 TwoSlopeNorm 為定義您自己的標準提供了一個有用的範例。請注意,為了使色彩條正常運作,您必須為您的標準定義一個反函式

class MidpointNormalize(colors.Normalize):
    def __init__(self, vmin=None, vmax=None, vcenter=None, clip=False):
        self.vcenter = vcenter
        super().__init__(vmin, vmax, clip)

    def __call__(self, value, clip=None):
        # I'm ignoring masked values and all kinds of edge cases to make a
        # simple example...
        # Note also that we must extrapolate beyond vmin/vmax
        x, y = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1.]
        return np.ma.masked_array(np.interp(value, x, y,
                                            left=-np.inf, right=np.inf))

    def inverse(self, value):
        y, x = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1]
        return np.interp(value, x, y, left=-np.inf, right=np.inf)


fig, ax = plt.subplots()
midnorm = MidpointNormalize(vmin=-500., vcenter=0, vmax=4000)

pcm = ax.pcolormesh(longitude, latitude, topo, rasterized=True, norm=midnorm,
                    cmap=terrain_map, shading='auto')
ax.set_aspect(1 / np.cos(np.deg2rad(49)))
ax.set_title('Custom norm')
cb = fig.colorbar(pcm, shrink=0.6, extend='both')
cb.set_ticks([-500, 0, 1000, 2000, 3000, 4000])

plt.show()
Custom norm

腳本的總執行時間:(0 分鐘 6.383 秒)

由 Sphinx-Gallery 產生的圖庫