Definiowanie punktu środkowego mapy kolorów w matplotlib

88

Chcę ustawić środkowy punkt mapy kolorów, tj. Moje dane mają zakres od -5 do 10, chcę, aby zero było środkiem. Myślę, że sposobem na to jest podklasa normalizacji i użycie normy, ale nie znalazłem żadnego przykładu i nie jest dla mnie jasne, co dokładnie mam zaimplementować.

tillsten
źródło
Nazywa się to „rozbieżną” lub „dwubiegunową” mapą kolorów, gdzie centralny punkt mapy jest ważny, a dane znajdują się powyżej i poniżej tego punktu. sandia.gov/~kmorel/documents/ColorMaps
endolit
3
Wszystkie odpowiedzi w tym wątku wydają się dość skomplikowane. Łatwe w użyciu rozwiązanie jest pokazane w tej doskonałej odpowiedzi , która w międzyczasie znalazła się również w dokumentacji matplotlib, sekcja Normalizacja niestandardowa: dwa zakresy liniowe .
ImportanceOfBeingErnest

Odpowiedzi:

16

Zauważ, że w matplotlib w wersji 3.1 została dodana klasa DivergingNorm . Myślę, że obejmuje twój przypadek użycia. Można go używać w następujący sposób:

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

W matplotlib 3.2 nazwa klasy została zmieniona na TwoSlopesNorm

macKaiver
źródło
Wygląda to interesująco, ale wydaje się, że należy to wykorzystać do przekształcenia danych przed wykreśleniem. Legenda paska kolorów będzie odnosić się do przekształconych danych, a nie do oryginalnych.
bli
3
@bli tak nie jest. normrobi normalizacji obrazu. normsidź ramię w ramię z colormaps.
Paul H
1
Irytujące jest to, że jest przestarzałe od wersji 3.2 i nie ma dokumentu, jak go zastąpić: matplotlib.org/3.2.0/api/_as_gen/ ...
daknowles
1
Tak, dokumenty są niejasne. Myślę, że została zmieniona na TwoSlopeNorm: matplotlib.org/3.2.0/api/_as_gen/ ...
macKaiver
91

Wiem, że to późna pora na grę, ale właśnie przeszedłem przez ten proces i wymyśliłem rozwiązanie, które być może jest mniej niezawodne niż normalizacja podklas, ale znacznie prostsze. Pomyślałem, że dobrze byłoby podzielić się tym tutaj dla potomności.

Funkcja

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

Przykład

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

Wyniki przykładu:

wprowadź opis obrazu tutaj

Paul H.
źródło
Wielkie dzięki za wspaniały wkład! Jednak kod nie był w stanie zarówno kadrować, jak i przesuwać tej samej mapy kolorów, a Twoje instrukcje były nieco nieprecyzyjne i wprowadzające w błąd. Teraz to naprawiłem i pozwoliłem sobie edytować Twój post. Ponadto umieściłem go w jednej z moich osobistych bibliotek i dodałem Cię jako autora. Mam nadzieję, że nie przeszkadza.
TheChymera
@TheChymera mapa kolorów w prawym dolnym rogu została zarówno przycięta, jak i nowsza. Możesz używać tego według własnego uznania.
Paul H
Tak, ma, niestety wygląda to tylko w przybliżeniu dobrze jako zbieg okoliczności. Jeśli starti stopnie wynoszą odpowiednio 0 i 1, po wykonaniu tej czynności reg_index = np.linspace(start, stop, 257)nie można już zakładać, że wartość 129 jest środkiem oryginalnego cmap, dlatego całe przeskalowanie nie ma sensu przy każdym kadrowaniu. Ponadto, startpowinny być od 0 do 0,5 i stopod 0,5 do 1, nie zarówno od 0 do 1, jak poinstruować.
TheChymera
@TheChymera Wypróbowałem twoją wersję i pomyślałem o niej dwa razy. 1) wydaje mi się, że wszystkie utworzone przez Ciebie indeksy mają długość 257, aw matplotlib domyślnie jest to 256? 2) załóżmy, że moje dane mieszczą się w zakresie od -1 do 1000, są zdominowane przez pozytywy i dlatego więcej poziomów / warstw powinno trafić do gałęzi dodatniej. Ale twoja funkcja daje 128 poziomów zarówno negatywom, jak i pozytywom, więc myślę, że byłoby bardziej „sprawiedliwe” podzielenie poziomów nierównomiernie.
Jason,
Jest to doskonałe rozwiązanie, ale kończy się niepowodzeniem, jeśli midpointdane są równe 0 lub 1. Zobacz moją odpowiedź poniżej, aby znaleźć proste rozwiązanie tego problemu.
DaveTheScientist
22

Oto podklasa rozwiązania Normalizuj. Aby z niego skorzystać

norm = MidPointNorm(midpoint=3)
imshow(X, norm=norm)

Oto klasa:

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
tillsten
źródło
Czy można używać tej klasy oprócz skalowania logów lub logów sym-log bez konieczności tworzenia większej liczby podklas? Mój obecny przypadek użycia już używa „norm = SymLogNorm (linthresh = 1)”
AnnanFay,
Idealnie, dokładnie tego szukałem. Może powinieneś dodać zdjęcie, aby pokazać różnicę? Tutaj punkt środkowy jest wyśrodkowany na pasku, w przeciwieństwie do innych normalizatorów punktu środkowego, w których punkt środkowy można przeciągnąć w kierunku krańców.
hałaśliwy
18

Najłatwiej jest po prostu użyć argumentów vmini (zakładając, że pracujesz z danymi obrazu), a nie podklasy .vmaximshowmatplotlib.colors.Normalize

Na przykład

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

wprowadź opis obrazu tutaj

Joe Kington
źródło
1
Czy można zaktualizować przykład do krzywej gaussowskiej, abyśmy mogli lepiej zobaczyć gradację koloru?
Dat Chu
3
Nie podoba mi się to rozwiązanie, ponieważ nie wykorzystuje pełnej dynamiki dostępnych kolorów. Chciałbym również skorzystać z przykładu normalizacji, aby zbudować normalizację typu symlog.
tillsten
2
@tillsten - W takim razie jestem zdezorientowany ... Nie możesz użyć pełnego zakresu dynamiki paska kolorów, jeśli chcesz, aby 0 w środku, prawda? W takim razie potrzebujesz skali nieliniowej? Jedna skala dla wartości powyżej 0, druga dla wartości poniżej? W takim przypadku musisz przejść podklasę Normalize. Za chwilę dodam przykład (zakładając, że ktoś inny mnie w tym nie pobije ...).
Joe Kington,
@Joe: Masz rację, to nie jest liniowe (a dokładniej dwie części liniowe). Używając vmin / vmax, nie jest używana zmiana koloru dla wartości mniejszych niż -5 (co ma sens w niektórych zastosowaniach, ale nie w moim).
tillsten
2
dla danych ogólnych w Z:vmax=abs(Z).max(), vmin=-abs(Z).max()
endolith
13

Tutaj tworzę podklasę, Normalizea następnie minimalny przykład.

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

Wynik: pic-1

Ten sam przykład z tylko dodatnimi danymi vals = np.array([[1., 3], [6, 10]])

pic-2

Nieruchomości:

  • Punkt środkowy otrzymuje kolor środkowy.
  • Górne i dolne zakresy są przeskalowywane przez to samo przekształcenie liniowe.
  • Na pasku kolorów pokazany jest tylko kolor, który pojawia się na obrazku.
  • Wydaje się działać dobrze, nawet jeśli vminjest większy niż midpoint(chociaż nie przetestowano wszystkich przypadków skrajnych).

To rozwiązanie jest inspirowane klasą o tej samej nazwie z tej strony

icemtel
źródło
3
Najlepsza odpowiedź ze względu na swoją prostotę. Inne odpowiedzi są najlepsze tylko wtedy, gdy jesteś już ekspertem Matplotlib i próbujesz zostać super-ekspertem. Większość osób poszukujących odpowiedzi w matplotlib próbuje po prostu coś zrobić, aby wrócić do domu do swojego psa i / lub rodziny, a dla nich ta odpowiedź jest najlepsza.
sapo_cosmico
To rozwiązanie wydaje się rzeczywiście najlepsze, ale nie działa! Właśnie uruchomiłem skrypt testowy i wynik jest zupełnie inny (tylko z niebieskimi kwadratami i bez czerwonego). @icemtel, czy możesz sprawdzić? (poza problemem z wcięciem def __call__)
Filipe
Ok, znalazłem problem (y): liczby w obliczeniach normalized_mini normalized_maxsą traktowane jako liczby całkowite. Po prostu ustaw je jako 0,0. Ponadto, aby uzyskać prawidłowe wyniki twojej figury, musiałem użyć vals = sp.array([[-5.0, 0.0], [5.0, 10.0]]) . W każdym razie dzięki za odpowiedź!
Filipe
Cześć @Filipe Nie mogę odtworzyć twojego problemu na moim komputerze (Python 3.7, matplotlib 2.2.3 i myślę, że powinno być tak samo w nowszych wersjach). Jaką masz wersję? W każdym razie zrobiłem małą edycję, tworząc tablicę typu float i naprawiłem problem z wcięciami. Dzięki za zwrócenie uwagi
icemtel
Hmm ... właśnie próbowałem z pythonem3 i też działa. Ale używam pythona2.7. Dziękuję za naprawienie i za odpowiedź. Jest bardzo prosty w użyciu! :)
Filipe
5

Nie jestem pewien, czy nadal szukasz odpowiedzi. Dla mnie próba podklasy Normalizezakończyła się niepowodzeniem. Skupiłem się więc na ręcznym utworzeniu nowego zestawu danych, znaczników i etykiet znaczników, aby uzyskać efekt, do którego myślę, że chcesz.

Znalazłem scalemoduł w matplotlib, który ma klasę używaną do przekształcania wykresów liniowych według reguł 'syslog', więc używam go do transformacji danych. Następnie skaluję dane tak, aby przechodziły od 0 do 1 (co Normalizezwykle ma miejsce), ale skaluję liczby dodatnie inaczej niż liczby ujemne. Dzieje się tak, ponieważ twoje vmax i vmin mogą nie być takie same, więc .5 -> 1 może obejmować większy zakres dodatni niż .5 -> 0, zakres ujemny. Łatwiej było mi stworzyć procedurę obliczania wartości tików i etykiet.

Poniżej znajduje się kod i przykładowy rysunek.

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 i linthresh = 1e-4

Możesz dowolnie dostosować „stałe” (np. VMAX) U góry skryptu, aby potwierdzić, że zachowuje się on dobrze.

Yann
źródło
Dzięki za sugestię, jak widać poniżej, udało mi się uzyskać podklasy. Ale twój kod jest nadal bardzo przydatny do poprawiania etykiet.
dosten
4

Używałem doskonałej odpowiedzi Paula H., ale napotkałem problem, ponieważ niektóre z moich danych wahały się od negatywnych do pozytywnych, podczas gdy inne zestawy zawierały się w zakresie od 0 do pozytywnych lub od negatywnych do 0; w obu przypadkach chciałem, aby 0 było pokolorowane jako białe (środek mapy kolorów, której używam). W istniejącej implementacji, jeśli midpointwartość jest równa 1 lub 0, oryginalne mapowania nie zostały nadpisane. Możesz to zobaczyć na poniższym rysunku: wykresy przed edycją Trzecia kolumna wygląda poprawnie, ale ciemnoniebieski obszar w drugiej kolumnie i ciemnoczerwony obszar w pozostałych kolumnach powinny być białe (ich wartości danych w rzeczywistości wynoszą 0). Użycie mojej poprawki daje mi: wykresy po edycji Moja funkcja jest zasadniczo taka sama, jak funkcja Paula H, z moimi edycjami na początku forpętli:

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

EDYCJA: Ponownie napotkałem podobny problem, gdy niektóre z moich danych wahały się od małej wartości dodatniej do większej wartości dodatniej, gdzie bardzo niskie wartości były zabarwione na czerwono zamiast na biało. Naprawiłem to, dodając wiersz Edit #2w powyższym kodzie.

DaveTheScientist
źródło
Wygląda to ładnie, ale wygląda na to, że argumenty zmieniły się w porównaniu z odpowiedzią Paula H. (i komentarzami) ... Czy możesz dodać przykładowe wezwanie do swojej odpowiedzi?
Filipe
1

Jeśli nie masz nic przeciwko obliczeniu stosunku między vmin, vmax i zerem, to jest to dość podstawowa liniowa mapa od niebieskiego do białego do czerwonego, która ustawia biały zgodnie ze stosunkiem 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)

Format cdict jest dość prosty: wiersze to punkty w utworzonym gradiencie: pierwszy wpis to wartość x (stosunek wzdłuż gradientu od 0 do 1), drugi to wartość końcowa poprzedniego segmentu, a trzecia to wartość początkowa następnego segmentu - jeśli chcesz płynnych gradientów, dwa ostatnie są zawsze takie same. Więcej szczegółów znajdziesz w dokumentacji .

naught101
źródło
1
Istnieje również możliwość określenia wewnątrz LinearSegmentedColormap.from_list()krotek (val,color)i przekazania ich jako listy do colorargumentu tej metody gdzie val0=0<val1<...<valN==1.
maurizio,
0

Miałem podobny problem, ale chciałem, aby najwyższa wartość była pełna czerwieni i odcięła niskie wartości niebieskiego, dzięki czemu wyglądało to tak, jakby dolna część paska kolorów została odcięta. To zadziałało dla mnie (obejmuje opcjonalną przezroczystość):

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()
ben.dichter
źródło