Definieren des Mittelpunkts einer Farbkarte in matplotlib

86

Ich möchte den Mittelpunkt einer Farbkarte festlegen, dh meine Daten gehen von -5 bis 10, ich möchte, dass Null der Mittelpunkt ist. Ich denke, der Weg, dies zu tun, besteht darin, normalisieren und die Norm zu verwenden, aber ich habe kein Beispiel gefunden und es ist mir nicht klar, was genau ich implementieren muss.

Tillsten
quelle
Dies wird als "divergierende" oder "bipolare" Farbkarte bezeichnet, bei der der Mittelpunkt der Karte wichtig ist und die Daten über und unter diesen Punkt gehen. sandia.gov/~kmorel/documents/ColorMaps
Endolith
3
Alle Antworten in diesem Thread scheinen ziemlich kompliziert zu sein. Die einfach zu verwendende Lösung wird in dieser hervorragenden Antwort gezeigt , die es inzwischen auch in die Matplotlib-Dokumentation, Abschnitt Benutzerdefinierte Normalisierung: Zwei lineare Bereiche, geschafft hat .
ImportanceOfBeingErnest

Antworten:

12

Beachten Sie, dass in matplotlib Version 3.1 die DivergingNorm- Klasse hinzugefügt wurde. Ich denke, es deckt Ihren Anwendungsfall ab. Es kann folgendermaßen verwendet werden:

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

In matplotlib 3.2 wurde die Klasse in TwoSlopesNorm umbenannt

macKaiver
quelle
Das sieht interessant aus, aber es scheint, dass dies verwendet werden muss, um die Daten vor dem Plotten zu transformieren. Die Legende der Farbleiste bezieht sich auf die transformierten Daten, nicht auf die Originaldaten.
Bli
3
@bli das ist nicht der Fall. Das normmacht die Normalisierung für Ihr Bild. normsGehen Sie Hand in Hand mit Farbkarten.
Paul H
1
Ärgerlicherweise ist dies ab 3.2 veraltet, ohne dass ein Dokument zum Ersetzen vorhanden ist: matplotlib.org/3.2.0/api/_as_gen/…
daknowles
1
Ja, die Dokumente sind unklar. Ich denke, es wurde umbenannt in TwoSlopeNorm: matplotlib.org/3.2.0/api/_as_gen/…
macKaiver
89

Ich weiß, dass dies zu spät zum Spiel kommt, aber ich habe gerade diesen Prozess durchlaufen und eine Lösung gefunden, die vielleicht weniger robust als die Normalisierung von Unterklassen ist, aber viel einfacher. Ich dachte, es wäre gut, es hier für die Nachwelt zu teilen.

Die Funktion

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

Ein Beispiel

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

Ergebnisse des Beispiels:

Geben Sie hier die Bildbeschreibung ein

Paul H.
quelle
Vielen Dank für Ihren tollen Beitrag! Allerdings war der Code nicht in der Lage sowohl Zuschneiden und Verschieben der gleichen Farbe der Karte, und Ihre Anweisungen waren etwas ungenau und irreführend. Ich habe das jetzt behoben und mir die Freiheit genommen, Ihren Beitrag zu bearbeiten. Außerdem habe ich es in eine meiner persönlichen Bibliotheken aufgenommen und Sie als Autor hinzugefügt. Ich hoffe, dass es dir nichts ausmacht.
TheChymera
@TheChymera Die Farbkarte in der unteren rechten Ecke wurde zugeschnitten und neu zentriert. Fühlen Sie sich frei, dies zu verwenden, wie Sie es für richtig halten.
Paul H
Ja, leider hat es nur zufällig einen Zufall. Wenn startund stopnicht 0 bzw. 1 sind, reg_index = np.linspace(start, stop, 257)können Sie danach nicht mehr davon ausgehen, dass der Wert 129 der Mittelpunkt der ursprünglichen cmap ist. Daher macht die gesamte Neuskalierung beim Zuschneiden keinen Sinn. Sollte startauch von 0 bis 0,5 und stopvon 0,5 bis 1 sein, nicht beide von 0 bis 1, wie Sie anweisen.
TheChymera
@ TheChymera Ich habe deine Version ausprobiert und hatte zwei Gedanken darüber. 1) Mir scheint, die von Ihnen erstellten Indizes haben alle die Länge 257, und in matplotlib ist standardmäßig 256, nehme ich an? 2) Angenommen, meine Daten reichen von -1 bis 1000, sie werden von Positiven dominiert und daher sollten mehr Ebenen / Ebenen in den positiven Zweig gehen. Aber Ihre Funktion gibt sowohl Negativen als auch Positiven 128 Ebenen, daher wäre es "fairer", die Ebenen meiner Meinung nach ungleichmäßig aufzuteilen.
Jason
Dies ist eine ausgezeichnete Lösung, schlägt jedoch fehl, wenn die midpointDaten gleich 0 oder 1 sind. Eine einfache Lösung für dieses Problem finden Sie in meiner Antwort unten.
DaveTheScientist
22

Hier ist eine Lösungsunterklasse Normalisieren. Um es zu benutzen

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

Hier ist die Klasse:

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
quelle
Ist es möglich, diese Klasse zusätzlich zur Protokoll- oder Sym-Protokoll-Skalierung zu verwenden, ohne weitere Unterklassen erstellen zu müssen? Mein aktueller Anwendungsfall verwendet bereits "norm = SymLogNorm (linthresh = 1)"
AnnanFay
Perfekt, genau das habe ich gesucht. Vielleicht sollten Sie ein Bild hinzufügen, um den Unterschied zu demonstrieren? Hier ist der Mittelpunkt im Balken zentriert, im Gegensatz zu anderen Mittelpunktnormalisierern, bei denen der Mittelpunkt zu den Extremitäten gezogen werden kann.
gaborous
18

Es ist am einfachsten, nur die Argumente vminund zu vmaxzu verwenden imshow(vorausgesetzt, Sie arbeiten mit Bilddaten), anstatt eine Unterklasse zu erstellen matplotlib.colors.Normalize.

Z.B

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

Geben Sie hier die Bildbeschreibung ein

Joe Kington
quelle
1
Ist es möglich, das Beispiel auf eine Gauß-Kurve zu aktualisieren, damit wir die Abstufung der Farbe besser sehen können?
Dat Chu
2
Diese Lösung gefällt mir nicht, da sie nicht den gesamten Dynamikbereich der verfügbaren Farben nutzt. Außerdem möchte ich ein Beispiel für Normalisierung verwenden, um eine Symlog-artige Normalisierung zu erstellen.
Tillsten
2
@tillsten - Ich bin dann verwirrt ... Sie können nicht den gesamten Dynamikbereich der Farbleiste nutzen, wenn Sie 0 in der Mitte haben möchten, oder? Sie wollen dann eine nichtlineare Skala? Eine Skala für Werte über 0, eine für Werte unter 0? In diesem Fall müssen Sie eine Unterklasse erstellen Normalize. Ich werde gleich ein Beispiel hinzufügen (vorausgesetzt, jemand anderes schlägt mich nicht ...).
Joe Kington
@ Joe: Du hast recht, es ist nicht linear (genauer gesagt zwei lineare Teile). Bei Verwendung von vmin / vmax wird der Farbbereich für Werte kleiner als -5 nicht verwendet (was in einigen Anwendungen sinnvoll ist, in meinen jedoch nicht).
Tillsten
2
für generische Daten in Z:vmax=abs(Z).max(), vmin=-abs(Z).max()
Endolith
12

Diese Lösung wurde von einer gleichnamigen Klasse auf dieser Seite inspiriert

Hier erstelle ich eine Unterklasse von Normalizegefolgt von einem minimalen Beispiel.

import scipy as sp
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 sp.ma.masked_array(sp.interp(value, x, y))


vals = sp.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()

Ergebnis: Bild-1

Und das gleiche Beispiel mit nur positiven Daten vals = sp.array([[1., 3], [6, 10]])

Bild-2

Um es zusammenzufassen - diese Norm hat folgende Eigenschaften:

  • Der Mittelpunkt erhält die mittlere Farbe.
  • Obere und untere Bereiche werden auf die gleiche Weise neu skaliert, sodass bei korrekter Farbkarte die Farbsättigung dem Abstand vom Mittelpunkt entspricht.
  • In der Farbleiste werden nur Farben angezeigt, die auf dem Bild angezeigt werden.
  • Scheint gut zu funktionieren, auch wenn vmines größer als ist midpoint(es wurden jedoch nicht alle Randfälle getestet).
icemtel
quelle
2
Beste Antwort aufgrund seiner Einfachheit. Die anderen Antworten sind nur dann am besten, wenn Sie bereits Matplotlib-Experte sind und versuchen, ein Super-Experte zu werden. Die meisten Matplotlib-Antwortsuchenden versuchen nur, etwas zu erledigen, um zu ihrem Hund und / oder ihrer Familie nach Hause zu gehen, und für sie ist diese Antwort am besten.
sapo_cosmico
Diese Lösung scheint zwar die beste zu sein, funktioniert aber nicht! Ich habe gerade das Testskript ausgeführt und das Ergebnis ist völlig anders (nur mit blauen Quadraten und ohne rote). @icemtel, kannst du das bitte überprüfen? (neben dem Problem mit der Einrückung auf def __call__)
Filipe
Ok, ich habe das Problem (die Probleme) gefunden: Die Zahlen bei der Berechnung von normalized_minund normalized_maxwerden als ganze Zahlen genommen. Setzen Sie sie einfach als 0.0. Um die korrekte Ausgabe Ihrer Figur zu erhalten, musste ich auch verwenden vals = sp.array([[-5.0, 0.0], [5.0, 10.0]]) . Trotzdem danke für die Antwort!
Filipe
Hallo @Filipe, ich kann Ihr Problem auf meinem Computer nicht reproduzieren (Python 3.7, matplotlib 2.2.3, und ich denke, sollte bei neueren Versionen dasselbe sein). Welche Version hast du? Wie auch immer, ich habe eine kleine Bearbeitung vorgenommen, die das Array vom Typ float erstellt, und das Einrückungsproblem behoben. Vielen Dank für den Hinweis
icemtel
Hmm .. Ich habe es gerade mit Python3 versucht und es funktioniert auch. Aber ich benutze Python2.7. Vielen Dank für die Korrektur und für die Antwort. Es ist sehr einfach zu bedienen! :)
Filipe
5

Ich bin mir nicht sicher, ob Sie noch nach einer Antwort suchen. Für mich war der Versuch, eine Unterklasse zu erstellen, Normalizeerfolglos. Daher habe ich mich darauf konzentriert, manuell einen neuen Datensatz, Ticks und Tick-Labels zu erstellen, um den Effekt zu erzielen, den Sie meiner Meinung nach anstreben.

Ich habe das scaleModul in matplotlib gefunden, das eine Klasse hat, die zum Transformieren von Liniendiagrammen nach den 'syslog'-Regeln verwendet wird, also verwende ich das, um die Daten zu transformieren. Dann skaliere ich die Daten so, dass sie von 0 auf 1 gehen (was Normalizenormalerweise der Fall ist), aber ich skaliere die positiven Zahlen anders als die negativen Zahlen. Dies liegt daran, dass Ihr vmax und Ihr vmin möglicherweise nicht identisch sind. Daher deckt .5 -> 1 möglicherweise einen größeren positiven Bereich ab als .5 -> 0, der negative Bereich jedoch. Es war für mich einfacher, eine Routine zur Berechnung der Tick- und Label-Werte zu erstellen.

Unten finden Sie den Code und eine Beispielabbildung.

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

Sie können die "Konstanten" (z. B. VMAX) oben im Skript anpassen, um zu bestätigen, dass es sich gut verhält.

Yann
quelle
Vielen Dank für Ihren Vorschlag, wie unten zu sehen, ich hatte Erfolg in der Unterklasse. Ihr Code ist jedoch immer noch sehr nützlich, um die Ticklabels richtig zu machen.
Tillsten
4

Ich habe die ausgezeichnete Antwort von Paul H verwendet, bin jedoch auf ein Problem gestoßen, da einige meiner Daten von negativ bis positiv reichten, während andere Sätze von 0 bis positiv oder von negativ bis 0 reichten. In beiden Fällen wollte ich, dass 0 weiß gefärbt wird (der Mittelpunkt der von mir verwendeten Farbkarte). Wenn bei der vorhandenen Implementierung Ihr midpointWert gleich 1 oder 0 ist, wurden die ursprünglichen Zuordnungen nicht überschrieben. Sie können dies auf dem folgenden Bild sehen: Grafiken vor dem Bearbeiten Die 3. Spalte sieht korrekt aus, aber der dunkelblaue Bereich in der 2. Spalte und der dunkelrote Bereich in den verbleibenden Spalten sollten alle weiß sein (ihre Datenwerte sind tatsächlich 0). Die Verwendung meines Fixes gibt mir Folgendes: Grafiken nach der Bearbeitung Meine Funktion ist im Wesentlichen dieselbe wie die von Paul H, mit meinen Änderungen am Anfang der forSchleife:

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

BEARBEITEN: Ich bin erneut auf ein ähnliches Problem gestoßen, als einige meiner Daten von einem kleinen positiven Wert zu einem größeren positiven Wert reichten, bei dem die sehr niedrigen Werte rot statt weiß gefärbt wurden. Ich habe es behoben, indem ich Edit #2im obigen Code eine Zeile hinzugefügt habe .

DaveTheScientist
quelle
Das sieht gut aus, aber es scheint, dass sich die Argumente gegenüber der Antwort von Paul H (und den Kommentaren) geändert haben ... Können Sie Ihrer Antwort einen Beispielaufruf hinzufügen?
Filipe
1

Wenn es Ihnen nichts ausmacht, das Verhältnis zwischen vmin, vmax und Null zu berechnen, ist dies eine ziemlich einfache lineare Karte von blau über weiß nach rot, die Weiß entsprechend dem Verhältnis festlegt 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)

Das cdict-Format ist ziemlich einfach: Die Zeilen sind Punkte in dem Verlauf, der erstellt wird: Der erste Eintrag ist der x-Wert (das Verhältnis entlang des Verlaufs von 0 zu 1), der zweite ist der Endwert für das vorherige Segment und Der dritte ist der Startwert für das nächste Segment. Wenn Sie glatte Verläufe wünschen, sind die beiden letzteren immer gleich. Weitere Informationen finden Sie in den Dokumenten .

naught101
quelle
1
Es besteht auch die Möglichkeit, innerhalb der LinearSegmentedColormap.from_list()Tupel anzugeben (val,color)und diese als Liste an das colorArgument dieser Methode zu übergeben, wobei val0=0<val1<...<valN==1.
Maurizio
0

Ich hatte ein ähnliches Problem, aber ich wollte, dass der höchste Wert voll rot ist und niedrige Blauwerte abgeschnitten werden, sodass es im Wesentlichen so aussieht, als wäre der Boden der Farbleiste abgeschnitten. Dies hat bei mir funktioniert (einschließlich optionaler Transparenz):

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
quelle