ฉันต้องการกำหนดจุดกึ่งกลางของ colormap คือข้อมูลของฉันเปลี่ยนจาก -5 ถึง 10 ฉันต้องการให้ศูนย์อยู่ตรงกลาง ฉันคิดว่าวิธีที่จะทำได้คือการทำให้คลาสย่อยเป็นปกติและใช้บรรทัดฐาน แต่ฉันไม่พบตัวอย่างใด ๆ และมันก็ไม่ชัดเจนสำหรับฉันว่าฉันต้องใช้อะไรบ้าง
ฉันต้องการกำหนดจุดกึ่งกลางของ colormap คือข้อมูลของฉันเปลี่ยนจาก -5 ถึง 10 ฉันต้องการให้ศูนย์อยู่ตรงกลาง ฉันคิดว่าวิธีที่จะทำได้คือการทำให้คลาสย่อยเป็นปกติและใช้บรรทัดฐาน แต่ฉันไม่พบตัวอย่างใด ๆ และมันก็ไม่ชัดเจนสำหรับฉันว่าฉันต้องใช้อะไรบ้าง
คำตอบ:
โปรดทราบว่าใน matplotlib เวอร์ชัน 3.1 ไฟล์ ได้เพิ่มคลาสDivergingNorm ฉันคิดว่ามันครอบคลุมกรณีการใช้งานของคุณ สามารถใช้งานได้ดังนี้:
from matplotlib import colors
colors.DivergingNorm(vmin=-4000., vcenter=0., vmax=10000)
ใน matplotlib 3.2 คลาสถูกเปลี่ยนชื่อเป็นTwoSlopesNorm
norm
ทำให้เป็นมาตรฐานสำหรับภาพของคุณ norms
จับมือกับ colormaps
TwoSlopeNorm
: matplotlib.org/3.2.0/api/_as_gen/…
ฉันรู้ว่านี่เป็นเกมที่ล่าช้า แต่ฉันเพิ่งทำตามขั้นตอนนี้และหาวิธีแก้ปัญหาที่อาจจะมีประสิทธิภาพน้อยกว่าคลาสย่อยปกติ แต่ง่ายกว่ามาก ฉันคิดว่ามันเป็นการดีที่จะแบ่งปันที่นี่เพื่อลูกหลาน
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import AxesGrid
def shiftedColorMap(cmap, start=0, midpoint=0.5, stop=1.0, name='shiftedcmap'):
'''
Function to offset the "center" of a colormap. Useful for
data with a negative min and positive max and you want the
middle of the colormap's dynamic range to be at zero.
Input
-----
cmap : The matplotlib colormap to be altered
start : Offset from lowest point in the colormap's range.
Defaults to 0.0 (no lower offset). Should be between
0.0 and `midpoint`.
midpoint : The new center of the colormap. Defaults to
0.5 (no shift). Should be between 0.0 and 1.0. In
general, this should be 1 - vmax / (vmax + abs(vmin))
For example if your data range from -15.0 to +5.0 and
you want the center of the colormap at 0.0, `midpoint`
should be set to 1 - 5/(5 + 15)) or 0.75
stop : Offset from highest point in the colormap's range.
Defaults to 1.0 (no upper offset). Should be between
`midpoint` and 1.0.
'''
cdict = {
'red': [],
'green': [],
'blue': [],
'alpha': []
}
# regular index to compute the colors
reg_index = np.linspace(start, stop, 257)
# shifted index to match the data
shift_index = np.hstack([
np.linspace(0.0, midpoint, 128, endpoint=False),
np.linspace(midpoint, 1.0, 129, endpoint=True)
])
for ri, si in zip(reg_index, shift_index):
r, g, b, a = cmap(ri)
cdict['red'].append((si, r, r))
cdict['green'].append((si, g, g))
cdict['blue'].append((si, b, b))
cdict['alpha'].append((si, a, a))
newcmap = matplotlib.colors.LinearSegmentedColormap(name, cdict)
plt.register_cmap(cmap=newcmap)
return newcmap
biased_data = np.random.random_integers(low=-15, high=5, size=(37,37))
orig_cmap = matplotlib.cm.coolwarm
shifted_cmap = shiftedColorMap(orig_cmap, midpoint=0.75, name='shifted')
shrunk_cmap = shiftedColorMap(orig_cmap, start=0.15, midpoint=0.75, stop=0.85, name='shrunk')
fig = plt.figure(figsize=(6,6))
grid = AxesGrid(fig, 111, nrows_ncols=(2, 2), axes_pad=0.5,
label_mode="1", share_all=True,
cbar_location="right", cbar_mode="each",
cbar_size="7%", cbar_pad="2%")
# normal cmap
im0 = grid[0].imshow(biased_data, interpolation="none", cmap=orig_cmap)
grid.cbar_axes[0].colorbar(im0)
grid[0].set_title('Default behavior (hard to see bias)', fontsize=8)
im1 = grid[1].imshow(biased_data, interpolation="none", cmap=orig_cmap, vmax=15, vmin=-15)
grid.cbar_axes[1].colorbar(im1)
grid[1].set_title('Centered zero manually,\nbut lost upper end of dynamic range', fontsize=8)
im2 = grid[2].imshow(biased_data, interpolation="none", cmap=shifted_cmap)
grid.cbar_axes[2].colorbar(im2)
grid[2].set_title('Recentered cmap with function', fontsize=8)
im3 = grid[3].imshow(biased_data, interpolation="none", cmap=shrunk_cmap)
grid.cbar_axes[3].colorbar(im3)
grid[3].set_title('Recentered cmap with function\nand shrunk range', fontsize=8)
for ax in grid:
ax.set_yticks([])
ax.set_xticks([])
start
และstop
ไม่ใช่ 0 และ 1 ตามลำดับหลังจากที่คุณทำreg_index = np.linspace(start, stop, 257)
คุณจะไม่สามารถสมมติว่าค่า 129 เป็นจุดกึ่งกลางของ cmap ดั้งเดิมได้อีกต่อไปดังนั้นการปรับขนาดทั้งหมดจึงไม่สมเหตุสมผลเมื่อใดก็ตามที่คุณครอบตัด นอกจากนี้start
ควรอยู่ระหว่าง 0 ถึง 0.5 และstop
0.5 ถึง 1 ไม่ใช่ตั้งแต่ 0 ถึง 1 ตามที่คุณสั่ง
midpoint
ข้อมูลมีค่าเท่ากับ 0 หรือ 1 ดูคำตอบของฉันด้านล่างสำหรับการแก้ไขปัญหาง่ายๆ
นี่คือโซลูชันย่อยคลาส Normalize เพื่อใช้งาน
norm = MidPointNorm(midpoint=3)
imshow(X, norm=norm)
นี่คือคลาส:
import numpy as np
from numpy import ma
from matplotlib import cbook
from matplotlib.colors import Normalize
class MidPointNorm(Normalize):
def __init__(self, midpoint=0, vmin=None, vmax=None, clip=False):
Normalize.__init__(self,vmin, vmax, clip)
self.midpoint = midpoint
def __call__(self, value, clip=None):
if clip is None:
clip = self.clip
result, is_scalar = self.process_value(value)
self.autoscale_None(result)
vmin, vmax, midpoint = self.vmin, self.vmax, self.midpoint
if not (vmin < midpoint < vmax):
raise ValueError("midpoint must be between maxvalue and minvalue.")
elif vmin == vmax:
result.fill(0) # Or should it be all masked? Or 0.5?
elif vmin > vmax:
raise ValueError("maxvalue must be bigger than minvalue")
else:
vmin = float(vmin)
vmax = float(vmax)
if clip:
mask = ma.getmask(result)
result = ma.array(np.clip(result.filled(vmax), vmin, vmax),
mask=mask)
# ma division is very slow; we can take a shortcut
resdat = result.data
#First scale to -1 to 1 range, than to from 0 to 1.
resdat -= midpoint
resdat[resdat>0] /= abs(vmax - midpoint)
resdat[resdat<0] /= abs(vmin - midpoint)
resdat /= 2.
resdat += 0.5
result = ma.array(resdat, mask=result.mask, copy=False)
if is_scalar:
result = result[0]
return result
def inverse(self, value):
if not self.scaled():
raise ValueError("Not invertible until scaled")
vmin, vmax, midpoint = self.vmin, self.vmax, self.midpoint
if cbook.iterable(value):
val = ma.asarray(value)
val = 2 * (val-0.5)
val[val>0] *= abs(vmax - midpoint)
val[val<0] *= abs(vmin - midpoint)
val += midpoint
return val
else:
val = 2 * (value - 0.5)
if val < 0:
return val*abs(vmin-midpoint) + midpoint
else:
return val*abs(vmax-midpoint) + midpoint
มันง่ายที่สุดที่จะเพียงแค่ใช้vmin
และvmax
ข้อโต้แย้งimshow
(สมมติว่าคุณกำลังทำงานกับข้อมูลภาพ) มากกว่า matplotlib.colors.Normalize
subclassing
เช่น
import numpy as np
import matplotlib.pyplot as plt
data = np.random.random((10,10))
# Make the data range from about -5 to 10
data = 10 / 0.75 * (data - 0.25)
plt.imshow(data, vmin=-10, vmax=10)
plt.colorbar()
plt.show()
Normalize
ในกรณีที่ว่าใช่คุณจะต้องซับคลาส ฉันจะเพิ่มตัวอย่างเพียงเล็กน้อย (สมมติว่ามีคนอื่นไม่ได้เอาชนะฉัน ... )
vmax=abs(Z).max(), vmin=-abs(Z).max()
ที่นี่ฉันสร้างคลาสย่อยNormalize
ตามด้วยตัวอย่างขั้นต่ำ
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
class MidpointNormalize(mpl.colors.Normalize):
def __init__(self, vmin, vmax, midpoint=0, clip=False):
self.midpoint = midpoint
mpl.colors.Normalize.__init__(self, vmin, vmax, clip)
def __call__(self, value, clip=None):
normalized_min = max(0, 1 / 2 * (1 - abs((self.midpoint - self.vmin) / (self.midpoint - self.vmax))))
normalized_max = min(1, 1 / 2 * (1 + abs((self.vmax - self.midpoint) / (self.midpoint - self.vmin))))
normalized_mid = 0.5
x, y = [self.vmin, self.midpoint, self.vmax], [normalized_min, normalized_mid, normalized_max]
return np.ma.masked_array(np.interp(value, x, y))
vals = np.array([[-5., 0], [5, 10]])
vmin = vals.min()
vmax = vals.max()
norm = MidpointNormalize(vmin=vmin, vmax=vmax, midpoint=0)
cmap = 'RdBu_r'
plt.imshow(vals, cmap=cmap, norm=norm)
plt.colorbar()
plt.show()
ตัวอย่างเดียวกันกับข้อมูลเชิงบวกเท่านั้น vals = np.array([[1., 3], [6, 10]])
คุณสมบัติ:
vmin
จะใหญ่กว่าmidpoint
ก็ตาม (ไม่ได้ทดสอบขอบทั้งหมด)โซลูชันนี้ได้รับแรงบันดาลใจจากคลาสที่มีชื่อเดียวกันจากหน้านี้
def __call__
)
normalized_min
และnormalized_max
ถูกนำมาเป็นจำนวนเต็ม ใส่เป็น 0.0 vals = sp.array([[-5.0, 0.0], [5.0, 10.0]])
นอกจากนี้เพื่อให้ได้ผลผลิตที่ถูกต้องของรูปของคุณผมต้องใช้ ขอบคุณสำหรับคำตอบ!
ไม่แน่ใจว่าคุณยังคงหาคำตอบอยู่ สำหรับฉันการพยายามคลาสย่อยNormalize
ไม่สำเร็จ ดังนั้นฉันจึงมุ่งเน้นไปที่การสร้างชุดข้อมูลเครื่องหมายขีดและป้ายกำกับใหม่ด้วยตนเองเพื่อให้ได้เอฟเฟกต์ที่ฉันคิดว่าคุณต้องการ
ฉันพบscale
โมดูลใน matplotlib ที่มีคลาสที่ใช้ในการแปลงพล็อตเส้นตามกฎ 'syslog' ดังนั้นฉันจึงใช้มันเพื่อแปลงข้อมูล จากนั้นฉันจะปรับขนาดข้อมูลเพื่อให้จาก 0 เป็น 1 (อะไรNormalize
โดยปกติจะทำอะไร) แต่ฉันจะปรับขนาดตัวเลขบวกให้แตกต่างจากจำนวนลบ เนื่องจาก vmax และ vmin ของคุณอาจไม่เหมือนกันดังนั้น. 5 -> 1 อาจครอบคลุมช่วงบวกที่ใหญ่กว่า. 5 -> 0 ซึ่งช่วงค่าลบจะเป็นเช่นนั้น มันง่ายกว่าสำหรับฉันที่จะสร้างกิจวัตรเพื่อคำนวณค่าขีดและป้ายกำกับ
ด้านล่างนี้คือโค้ดและรูปตัวอย่าง
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.mpl as mpl
import matplotlib.scale as scale
NDATA = 50
VMAX=10
VMIN=-5
LINTHRESH=1e-4
def makeTickLables(vmin,vmax,linthresh):
"""
make two lists, one for the tick positions, and one for the labels
at those positions. The number and placement of positive labels is
different from the negative labels.
"""
nvpos = int(np.log10(vmax))-int(np.log10(linthresh))
nvneg = int(np.log10(np.abs(vmin)))-int(np.log10(linthresh))+1
ticks = []
labels = []
lavmin = (np.log10(np.abs(vmin)))
lvmax = (np.log10(np.abs(vmax)))
llinthres = int(np.log10(linthresh))
# f(x) = mx+b
# f(llinthres) = .5
# f(lavmin) = 0
m = .5/float(llinthres-lavmin)
b = (.5-llinthres*m-lavmin*m)/2
for itick in range(nvneg):
labels.append(-1*float(pow(10,itick+llinthres)))
ticks.append((b+(itick+llinthres)*m))
# add vmin tick
labels.append(vmin)
ticks.append(b+(lavmin)*m)
# f(x) = mx+b
# f(llinthres) = .5
# f(lvmax) = 1
m = .5/float(lvmax-llinthres)
b = m*(lvmax-2*llinthres)
for itick in range(1,nvpos):
labels.append(float(pow(10,itick+llinthres)))
ticks.append((b+(itick+llinthres)*m))
# add vmax tick
labels.append(vmax)
ticks.append(b+(lvmax)*m)
return ticks,labels
data = (VMAX-VMIN)*np.random.random((NDATA,NDATA))+VMIN
# define a scaler object that can transform to 'symlog'
scaler = scale.SymmetricalLogScale.SymmetricalLogTransform(10,LINTHRESH)
datas = scaler.transform(data)
# scale datas so that 0 is at .5
# so two seperate scales, one for positive and one for negative
data2 = np.where(np.greater(data,0),
.75+.25*datas/np.log10(VMAX),
.25+.25*(datas)/np.log10(np.abs(VMIN))
)
ticks,labels=makeTickLables(VMIN,VMAX,LINTHRESH)
cmap = mpl.cm.jet
fig = plt.figure()
ax = fig.add_subplot(111)
im = ax.imshow(data2,cmap=cmap,vmin=0,vmax=1)
cbar = plt.colorbar(im,ticks=ticks)
cbar.ax.set_yticklabels(labels)
fig.savefig('twoscales.png')
อย่าลังเลที่จะปรับ "ค่าคงที่" (เช่นVMAX
) ที่ด้านบนของสคริปต์เพื่อยืนยันว่าทำงานได้ดี
ฉันใช้คำตอบที่ยอดเยี่ยมจาก Paul H แต่พบปัญหาเนื่องจากข้อมูลบางส่วนของฉันอยู่ในช่วงค่าลบถึงบวกในขณะที่ข้อมูลอื่น ๆ อยู่ในช่วงตั้งแต่ 0 ถึงบวกหรือจากลบถึง 0 ไม่ว่าในกรณีใดฉันต้องการให้ 0 เป็นสีขาว (จุดกึ่งกลางของ colormap ที่ฉันใช้) ด้วยการนำไปใช้งานที่มีอยู่หากmidpoint
ค่าของคุณเท่ากับ 1 หรือ 0 การแมปดั้งเดิมจะไม่ถูกเขียนทับ คุณจะเห็นว่าในภาพต่อไปนี้:
คอลัมน์ที่ 3 ดูถูกต้อง แต่พื้นที่สีน้ำเงินเข้มในคอลัมน์ที่ 2 และพื้นที่สีแดงเข้มในคอลัมน์ที่เหลือทั้งหมดควรเป็นสีขาว (ค่าข้อมูลเป็น 0) การใช้การแก้ไขของฉันทำให้ฉัน:
หน้าที่ของฉันโดยพื้นฐานแล้วเหมือนกับของ Paul H โดยมีการแก้ไขที่จุดเริ่มต้นของ
for
ลูป:
def shiftedColorMap(cmap, min_val, max_val, name):
'''Function to offset the "center" of a colormap. Useful for data with a negative min and positive max and you want the middle of the colormap's dynamic range to be at zero. Adapted from /programming/7404116/defining-the-midpoint-of-a-colormap-in-matplotlib
Input
-----
cmap : The matplotlib colormap to be altered.
start : Offset from lowest point in the colormap's range.
Defaults to 0.0 (no lower ofset). Should be between
0.0 and `midpoint`.
midpoint : The new center of the colormap. Defaults to
0.5 (no shift). Should be between 0.0 and 1.0. In
general, this should be 1 - vmax/(vmax + abs(vmin))
For example if your data range from -15.0 to +5.0 and
you want the center of the colormap at 0.0, `midpoint`
should be set to 1 - 5/(5 + 15)) or 0.75
stop : Offset from highets point in the colormap's range.
Defaults to 1.0 (no upper ofset). Should be between
`midpoint` and 1.0.'''
epsilon = 0.001
start, stop = 0.0, 1.0
min_val, max_val = min(0.0, min_val), max(0.0, max_val) # Edit #2
midpoint = 1.0 - max_val/(max_val + abs(min_val))
cdict = {'red': [], 'green': [], 'blue': [], 'alpha': []}
# regular index to compute the colors
reg_index = np.linspace(start, stop, 257)
# shifted index to match the data
shift_index = np.hstack([np.linspace(0.0, midpoint, 128, endpoint=False), np.linspace(midpoint, 1.0, 129, endpoint=True)])
for ri, si in zip(reg_index, shift_index):
if abs(si - midpoint) < epsilon:
r, g, b, a = cmap(0.5) # 0.5 = original midpoint.
else:
r, g, b, a = cmap(ri)
cdict['red'].append((si, r, r))
cdict['green'].append((si, g, g))
cdict['blue'].append((si, b, b))
cdict['alpha'].append((si, a, a))
newcmap = matplotlib.colors.LinearSegmentedColormap(name, cdict)
plt.register_cmap(cmap=newcmap)
return newcmap
แก้ไข:ฉันพบปัญหาที่คล้ายกันอีกครั้งเมื่อข้อมูลบางส่วนของฉันอยู่ในช่วงจากค่าบวกเล็กน้อยไปจนถึงค่าบวกที่มากขึ้นโดยที่ค่าที่ต่ำมากจะถูกเปลี่ยนเป็นสีแดงแทนที่จะเป็นสีขาว ฉันแก้ไขโดยการเพิ่มบรรทัดEdit #2
ในรหัสด้านบน
หากคุณไม่คิดที่จะหาอัตราส่วนระหว่าง vmin, vmax และศูนย์นี่เป็นแผนที่เชิงเส้นพื้นฐานที่ค่อนข้างดีจากสีน้ำเงินเป็นสีขาวเป็นสีแดงซึ่งจะกำหนดสีขาวตามอัตราส่วนz
:
def colormap(z):
"""custom colourmap for map plots"""
cdict1 = {'red': ((0.0, 0.0, 0.0),
(z, 1.0, 1.0),
(1.0, 1.0, 1.0)),
'green': ((0.0, 0.0, 0.0),
(z, 1.0, 1.0),
(1.0, 0.0, 0.0)),
'blue': ((0.0, 1.0, 1.0),
(z, 1.0, 1.0),
(1.0, 0.0, 0.0))
}
return LinearSegmentedColormap('BlueRed1', cdict1)
รูปแบบ cdict ค่อนข้างง่าย: แถวคือจุดในการไล่ระดับสีที่สร้างขึ้น: รายการแรกคือค่า x (อัตราส่วนตามการไล่ระดับสีจาก 0 ถึง 1) ส่วนที่สองคือค่าสิ้นสุดของส่วนก่อนหน้าและ ค่าที่สามคือค่าเริ่มต้นสำหรับส่วนถัดไป - หากคุณต้องการการไล่ระดับสีที่ราบรื่นค่าสองส่วนหลังจะเหมือนกันเสมอ ดูเอกสารสำหรับรายละเอียดเพิ่มเติม
LinearSegmentedColormap.from_list()
tuples (val,color)
และส่งพวกเขาเป็นรายการกับ ข้อโต้แย้งของวิธีการนี้ที่color
val0=0<val1<...<valN==1
ฉันมีปัญหาที่คล้ายกัน แต่ฉันต้องการให้ค่าสูงสุดเป็นสีแดงเต็มและตัดค่าสีน้ำเงินที่ต่ำออกไปทำให้ดูเหมือนว่าด้านล่างของแถบสีถูกตัดออกไป สิ่งนี้ใช้ได้ผลสำหรับฉัน (รวมถึงความโปร่งใสเพิ่มเติม):
def shift_zero_bwr_colormap(z: float, transparent: bool = True):
"""shifted bwr colormap"""
if (z < 0) or (z > 1):
raise ValueError('z must be between 0 and 1')
cdict1 = {'red': ((0.0, max(-2*z+1, 0), max(-2*z+1, 0)),
(z, 1.0, 1.0),
(1.0, 1.0, 1.0)),
'green': ((0.0, max(-2*z+1, 0), max(-2*z+1, 0)),
(z, 1.0, 1.0),
(1.0, max(2*z-1,0), max(2*z-1,0))),
'blue': ((0.0, 1.0, 1.0),
(z, 1.0, 1.0),
(1.0, max(2*z-1,0), max(2*z-1,0))),
}
if transparent:
cdict1['alpha'] = ((0.0, 1-max(-2*z+1, 0), 1-max(-2*z+1, 0)),
(z, 0.0, 0.0),
(1.0, 1-max(2*z-1,0), 1-max(2*z-1,0)))
return LinearSegmentedColormap('shifted_rwb', cdict1)
cmap = shift_zero_bwr_colormap(.3)
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 5 + 5
plt.plot([0, 10*np.pi], [0, 20*np.pi], color='c', lw=20, zorder=-3)
plt.imshow(Z, interpolation='nearest', origin='lower', cmap=cmap)
plt.colorbar()