การกำหนดจุดกึ่งกลางของ colormap ใน matplotlib


88

ฉันต้องการกำหนดจุดกึ่งกลางของ colormap คือข้อมูลของฉันเปลี่ยนจาก -5 ถึง 10 ฉันต้องการให้ศูนย์อยู่ตรงกลาง ฉันคิดว่าวิธีที่จะทำได้คือการทำให้คลาสย่อยเป็นปกติและใช้บรรทัดฐาน แต่ฉันไม่พบตัวอย่างใด ๆ และมันก็ไม่ชัดเจนสำหรับฉันว่าฉันต้องใช้อะไรบ้าง


สิ่งนี้เรียกว่า colormap แบบ "diverging" หรือ "bipolar" ซึ่งจุดกึ่งกลางของแผนที่มีความสำคัญและข้อมูลจะอยู่เหนือและใต้จุดนี้ sandia.gov/~kmorel/documents/ColorMaps
endolith

3
คำตอบทั้งหมดในชุดข้อความนี้ดูเหมือนจะค่อนข้างซับซ้อน ใช้ง่ายวิธีการแก้ปัญหาที่ปรากฏอยู่ในคำตอบที่ดีเยี่ยมนี้ซึ่งมีอยู่ในขณะเดียวกันยังทำให้มันเป็นเอกสาร matplotlib ส่วนการฟื้นฟูที่กำหนดเอง: สองช่วงเชิงเส้น
ImportanceOfBeingErnest

คำตอบ:


15

โปรดทราบว่าใน matplotlib เวอร์ชัน 3.1 ไฟล์ ได้เพิ่มคลาสDivergingNorm ฉันคิดว่ามันครอบคลุมกรณีการใช้งานของคุณ สามารถใช้งานได้ดังนี้:

from matplotlib import colors
colors.DivergingNorm(vmin=-4000., vcenter=0., vmax=10000)

ใน matplotlib 3.2 คลาสถูกเปลี่ยนชื่อเป็นTwoSlopesNorm


สิ่งนี้ดูน่าสนใจ แต่ดูเหมือนว่าจะต้องใช้เพื่อแปลงข้อมูลก่อนที่จะพล็อต คำอธิบายของแถบสีจะเกี่ยวข้องกับข้อมูลที่แปลงแล้วไม่ใช่ข้อมูลดั้งเดิม
bli

3
@bli นั่นไม่ใช่อย่างนั้น การnormทำให้เป็นมาตรฐานสำหรับภาพของคุณ normsจับมือกับ colormaps
Paul H

1
น่ารำคาญที่นี่เลิกใช้แล้วเมื่อ 3.2 โดยไม่มีเอกสารเกี่ยวกับวิธีการแทนที่: matplotlib.org/3.2.0/api/_as_gen/…
daknowles

1
ใช่เอกสารไม่ชัดเจน ฉันคิดว่ามันถูกเปลี่ยนชื่อเป็นTwoSlopeNorm: matplotlib.org/3.2.0/api/_as_gen/…
macKaiver

91

ฉันรู้ว่านี่เป็นเกมที่ล่าช้า แต่ฉันเพิ่งทำตามขั้นตอนนี้และหาวิธีแก้ปัญหาที่อาจจะมีประสิทธิภาพน้อยกว่าคลาสย่อยปกติ แต่ง่ายกว่ามาก ฉันคิดว่ามันเป็นการดีที่จะแบ่งปันที่นี่เพื่อลูกหลาน

ฟังก์ชั่น

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([])

ผลลัพธ์ของตัวอย่าง:

ป้อนคำอธิบายภาพที่นี่


ขอบคุณมากสำหรับผลงานที่ยอดเยี่ยมของคุณ! อย่างไรก็ตามโค้ดไม่สามารถทั้งการครอบตัดและเลื่อนแผนที่สีเดียวกันได้และคำแนะนำของคุณไม่ชัดเจนและทำให้เข้าใจผิดเล็กน้อย ตอนนี้ฉันได้แก้ไขแล้วและใช้เสรีภาพในการแก้ไขโพสต์ของคุณ นอกจากนี้ฉันได้รวมไว้ในห้องสมุดส่วนตัวของฉันและเพิ่มคุณเป็นผู้เขียน ฉันหวังว่าคุณจะไม่รังเกียจ
TheChymera

@TheChymera colormap ที่มุมขวาล่างถูกครอบตัดและล่าสุด อย่าลังเลที่จะใช้สิ่งนี้ตามที่เห็นสมควร
Paul H

ใช่มันน่าเศร้าที่มันดูเป็นเรื่องบังเอิญโดยประมาณเท่านั้น ถ้าstartและstopไม่ใช่ 0 และ 1 ตามลำดับหลังจากที่คุณทำreg_index = np.linspace(start, stop, 257)คุณจะไม่สามารถสมมติว่าค่า 129 เป็นจุดกึ่งกลางของ cmap ดั้งเดิมได้อีกต่อไปดังนั้นการปรับขนาดทั้งหมดจึงไม่สมเหตุสมผลเมื่อใดก็ตามที่คุณครอบตัด นอกจากนี้startควรอยู่ระหว่าง 0 ถึง 0.5 และstop0.5 ถึง 1 ไม่ใช่ตั้งแต่ 0 ถึง 1 ตามที่คุณสั่ง
TheChymera

@TheChymera ฉันลองใช้เวอร์ชันของคุณและมีความคิดสองอย่างเกี่ยวกับเรื่องนี้ 1) สำหรับฉันแล้วดูเหมือนว่าดัชนีที่คุณสร้างขึ้นนั้นมีความยาว 257 ทั้งหมดและใน matplotlib นั้นมีค่าเริ่มต้นเป็น 256 ฉันถือว่า? 2) สมมติว่าข้อมูลของฉันอยู่ในช่วง -1 ถึง 1,000 มันถูกครอบงำด้วยค่าบวกดังนั้นระดับ / เลเยอร์เพิ่มเติมควรไปที่สาขาบวก แต่ฟังก์ชันของคุณให้ 128 ระดับสำหรับทั้งเชิงลบและเชิงบวกดังนั้นฉันคิดว่า "ยุติธรรม" มากกว่าที่จะแบ่งระดับที่ไม่สม่ำเสมอ
Jason

นี่เป็นวิธีแก้ปัญหาที่ยอดเยี่ยม แต่จะล้มเหลวหากmidpointข้อมูลมีค่าเท่ากับ 0 หรือ 1 ดูคำตอบของฉันด้านล่างสำหรับการแก้ไขปัญหาง่ายๆ
DaveTheScientist

22

นี่คือโซลูชันย่อยคลาส 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

เป็นไปได้ไหมที่จะใช้คลาสนี้นอกเหนือจากการบันทึกหรือการปรับขนาดแบบ sym-log โดยไม่ต้องสร้างคลาสย่อยเพิ่มเติม กรณีการใช้งานปัจจุบันของฉันใช้ "norm = SymLogNorm (linthresh = 1)" อยู่แล้ว
AnnanFay

สมบูรณ์แบบนี่คือสิ่งที่ฉันกำลังมองหา บางทีคุณควรเพิ่มรูปภาพเพื่อแสดงความแตกต่าง? ที่นี่จุดกึ่งกลางจะอยู่กึ่งกลางของแท่งตรงข้ามกับจุดกึ่งกลางอื่น ๆ ที่สามารถลากจุดกึ่งกลางไปยังแขนขาได้
พูดไม่ออก

18

มันง่ายที่สุดที่จะเพียงแค่ใช้vminและvmaxข้อโต้แย้งimshow(สมมติว่าคุณกำลังทำงานกับข้อมูลภาพ) มากกว่า matplotlib.colors.Normalizesubclassing

เช่น

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()

ป้อนคำอธิบายภาพที่นี่


1
เป็นไปได้หรือไม่ที่จะอัปเดตตัวอย่างเป็นเส้นโค้งแบบเกาส์เซียนเพื่อให้เราเห็นการไล่ระดับของสีได้ดีขึ้น
Dat Chu

3
ฉันไม่ชอบโซลูชันนี้เพราะไม่ได้ใช้ไดนามิกเรนจ์สีที่มีอยู่ทั้งหมด นอกจากนี้ฉันต้องการตัวอย่างของการทำให้เป็นมาตรฐานเพื่อสร้างการทำให้เป็นมาตรฐานแบบ symlog
จนถึง

2
@tillsten - ฉันสับสนแล้ว ... คุณไม่สามารถใช้ช่วงไดนามิกเต็มของแถบสีถ้าคุณต้องการ 0 ตรงกลางใช่ไหม? คุณต้องการมาตราส่วนที่ไม่ใช่เชิงเส้นหรือไม่? หนึ่งมาตราส่วนสำหรับค่าที่สูงกว่า 0 หนึ่งสำหรับค่าด้านล่าง? Normalizeในกรณีที่ว่าใช่คุณจะต้องซับคลาส ฉันจะเพิ่มตัวอย่างเพียงเล็กน้อย (สมมติว่ามีคนอื่นไม่ได้เอาชนะฉัน ... )
Joe Kington

@ โจ: คุณพูดถูกมันไม่ใช่เชิงเส้น (ตรงกว่านั้นคือสองส่วนเชิงเส้น) การใช้ vmin / vmax จะไม่ใช้ colorange สำหรับค่าที่เล็กกว่า -5 (ซึ่งสมเหตุสมผลในบางแอปพลิเคชัน แต่ไม่ใช่ของฉัน)
จนถึง

2
สำหรับข้อมูลทั่วไปใน Z:vmax=abs(Z).max(), vmin=-abs(Z).max()
endolith

13

ที่นี่ฉันสร้างคลาสย่อย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()

ผลลัพธ์: รูปที่ 1

ตัวอย่างเดียวกันกับข้อมูลเชิงบวกเท่านั้น vals = np.array([[1., 3], [6, 10]])

รูปที่ 2

คุณสมบัติ:

  • จุดกึ่งกลางได้สีกลาง
  • ช่วงบนและล่างจะถูกปรับขนาดโดยการแปลงเชิงเส้นเดียวกัน
  • เฉพาะสีที่ปรากฏบนภาพเท่านั้นที่จะแสดงในแถบสี
  • ดูเหมือนว่าจะทำงานได้ดีแม้ว่าvminจะใหญ่กว่าmidpointก็ตาม (ไม่ได้ทดสอบขอบทั้งหมด)

โซลูชันนี้ได้รับแรงบันดาลใจจากคลาสที่มีชื่อเดียวกันจากหน้านี้


3
คำตอบที่ดีที่สุดเนื่องจากความเรียบง่าย คำตอบอื่น ๆ จะดีที่สุดก็ต่อเมื่อคุณเป็นผู้เชี่ยวชาญ Matplotlib ที่พยายามจะเป็นผู้เชี่ยวชาญขั้นสูง ผู้หาคำตอบ matplotlib ส่วนใหญ่พยายามทำอะไรบางอย่างเพื่อกลับบ้านไปหาสุนัขและ / หรือครอบครัวของพวกเขาและสำหรับพวกเขาคำตอบนี้ดีที่สุด
sapo_cosmico

วิธีนี้ดูเหมือนจะดีที่สุด แต่ไม่ได้ผล! ฉันเพิ่งเรียกใช้สคริปต์ทดสอบและผลลัพธ์แตกต่างกันอย่างสิ้นเชิง (รวมเฉพาะสี่เหลี่ยมสีน้ำเงินและไม่มีสีแดง) @icemtel ช่วยตรวจสอบหน่อยได้ไหม (ข้างปัญหากับการเยื้องบนdef __call__ )
ฟิ

ตกลงฉันพบปัญหา: ตัวเลขในการคำนวณของ normalized_minและnormalized_maxถูกนำมาเป็นจำนวนเต็ม ใส่เป็น 0.0 vals = sp.array([[-5.0, 0.0], [5.0, 10.0]]) นอกจากนี้เพื่อให้ได้ผลผลิตที่ถูกต้องของรูปของคุณผมต้องใช้ ขอบคุณสำหรับคำตอบ!
Filipe

สวัสดี @ Filipe ฉันไม่สามารถทำให้เกิดปัญหาของคุณในเครื่องของฉันได้ (Python 3.7, matplotlib 2.2.3 และฉันคิดว่าควรจะเหมือนกันในเวอร์ชันที่ใหม่กว่า) คุณมีรุ่นอะไร? อย่างไรก็ตามฉันได้ทำการแก้ไขเล็กน้อยเพื่อสร้างอาร์เรย์ของประเภทโฟลตและแก้ไขปัญหาการเยื้อง ขอขอบคุณที่ชี้ให้เห็น
icemtel

อืม .. เพิ่งลองกับ python3 แล้วมันก็ใช้ได้เหมือนกัน แต่ฉันใช้ python2.7 ขอบคุณสำหรับการแก้ไขและสำหรับคำตอบ ใช้งานง่ายมาก! :)
Filipe

5

ไม่แน่ใจว่าคุณยังคงหาคำตอบอยู่ สำหรับฉันการพยายามคลาสย่อย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 = 10, vmin = -5 และ linthresh = 1e-4

อย่าลังเลที่จะปรับ "ค่าคงที่" (เช่นVMAX) ที่ด้านบนของสคริปต์เพื่อยืนยันว่าทำงานได้ดี


ขอบคุณสำหรับข้อเสนอแนะดังที่แสดงด้านล่างฉันประสบความสำเร็จในคลาสย่อย แต่รหัสของคุณยังคงมีประโยชน์อย่างมากในการทำให้เครื่องหมายถูกต้อง
จนถึง

4

ฉันใช้คำตอบที่ยอดเยี่ยมจาก 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ในรหัสด้านบน


นี่ดูดี แต่ดูเหมือนว่าข้อโต้แย้งเปลี่ยนไปจากคำตอบของ Paul H (และความคิดเห็น) ... คุณสามารถเพิ่มตัวอย่างการเรียกคำตอบของคุณได้หรือไม่?
Filipe

1

หากคุณไม่คิดที่จะหาอัตราส่วนระหว่าง 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) ส่วนที่สองคือค่าสิ้นสุดของส่วนก่อนหน้าและ ค่าที่สามคือค่าเริ่มต้นสำหรับส่วนถัดไป - หากคุณต้องการการไล่ระดับสีที่ราบรื่นค่าสองส่วนหลังจะเหมือนกันเสมอ ดูเอกสารสำหรับรายละเอียดเพิ่มเติม


1
นอกจากนี้ยังมีตัวเลือกในการระบุภายในLinearSegmentedColormap.from_list()tuples (val,color)และส่งพวกเขาเป็นรายการกับ ข้อโต้แย้งของวิธีการนี้ที่color val0=0<val1<...<valN==1
maurizio

0

ฉันมีปัญหาที่คล้ายกัน แต่ฉันต้องการให้ค่าสูงสุดเป็นสีแดงเต็มและตัดค่าสีน้ำเงินที่ต่ำออกไปทำให้ดูเหมือนว่าด้านล่างของแถบสีถูกตัดออกไป สิ่งนี้ใช้ได้ผลสำหรับฉัน (รวมถึงความโปร่งใสเพิ่มเติม):

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()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.