Diskrete Matplotlib-Farbleiste

94

Ich versuche, einen diskreten Farbbalken für ein Streudiagramm in matplotlib zu erstellen

Ich habe meine x-, y-Daten und für jeden Punkt einen ganzzahligen Tag-Wert, den ich mit einer eindeutigen Farbe darstellen möchte, z

plt.scatter(x, y, c=tag)

Normalerweise ist das Tag eine Ganzzahl im Bereich von 0 bis 20, aber der genaue Bereich kann sich ändern

Bisher habe ich nur die Standardeinstellungen verwendet, z

plt.colorbar()

das gibt eine kontinuierliche Auswahl an Farben. Idealerweise möchte ich eine Menge von n diskreten Farben (in diesem Beispiel n = 20). Noch besser wäre es, einen Tag-Wert von 0 zu erhalten, um eine graue Farbe zu erzeugen, und 1-20, um bunt zu sein.

Ich habe einige 'Kochbuch'-Skripte gefunden, aber sie sind sehr kompliziert und ich kann nicht glauben, dass sie der richtige Weg sind, um ein scheinbar einfaches Problem zu lösen

bph
quelle
1
hilft das oder das ?
Francesco Montesano
danke für links, aber das zweite Beispiel ist das, was ich über äußerst überkomplizierte Mittel meine, um eine (scheinbar) triviale Aufgabe auszuführen - der erste Link ist nützlich
bph
1
Ich fand diesen Link sehr hilfreich bei der Diskretisierung eines vorhandenen colormap: gist.github.com/jakevdp/91077b0cae40f8f8244a
BallpointBen

Antworten:

91

Sie können ganz einfach eine benutzerdefinierte diskrete Farbleiste erstellen, indem Sie ein BoundaryNorm als Normalisierer für Ihre Streuung verwenden. Das skurrile Bit (in meiner Methode) lässt 0 als grau erscheinen.

Für Bilder verwende ich oft cmap.set_bad () und konvertiere meine Daten in ein numpy maskiertes Array. Das wäre viel einfacher, 0 grau zu machen, aber ich konnte dies nicht mit der Streuung oder der benutzerdefinierten cmap zum Laufen bringen.

Alternativ können Sie Ihre eigene cmap von Grund auf neu erstellen oder eine vorhandene vorlesen und nur einige bestimmte Einträge überschreiben.

import numpy as np
import matplotlib as mpl
import matplotlib.pylab as plt

fig, ax = plt.subplots(1, 1, figsize=(6, 6))  # setup the plot

x = np.random.rand(20)  # define the data
y = np.random.rand(20)  # define the data
tag = np.random.randint(0, 20, 20)
tag[10:12] = 0  # make sure there are some 0 values to show up as grey

cmap = plt.cm.jet  # define the colormap
# extract all colors from the .jet map
cmaplist = [cmap(i) for i in range(cmap.N)]
# force the first color entry to be grey
cmaplist[0] = (.5, .5, .5, 1.0)

# create the new map
cmap = mpl.colors.LinearSegmentedColormap.from_list(
    'Custom cmap', cmaplist, cmap.N)

# define the bins and normalize
bounds = np.linspace(0, 20, 21)
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)

# make the scatter
scat = ax.scatter(x, y, c=tag, s=np.random.randint(100, 500, 20),
                  cmap=cmap, norm=norm)

# create a second axes for the colorbar
ax2 = fig.add_axes([0.95, 0.1, 0.03, 0.8])
cb = plt.colorbar.ColorbarBase(ax2, cmap=cmap, norm=norm,
    spacing='proportional', ticks=bounds, boundaries=bounds, format='%1i')

ax.set_title('Well defined discrete colors')
ax2.set_ylabel('Very custom cbar [-]', size=12)

Geben Sie hier die Bildbeschreibung ein

Ich persönlich denke, dass es mit 20 verschiedenen Farben etwas schwierig ist, den spezifischen Wert zu lesen, aber das liegt natürlich bei Ihnen.

Rutger Kassies
quelle
Ich bin mir nicht sicher, ob dies erlaubt ist, aber können Sie sich meine Frage hier ansehen ?
Vwos
6
plt.colorbar.ColorbarBasewirft Fehler. Verwenden Siempl.colorbar.ColorbarBase
Zeeshan Khan
Vielen Dank für diese Antwort, ich vermisse sie wirklich aus dem Dokument. Ich habe versucht, es für Windrosen von Perzentilen zu transponieren, und ich hatte einen Fehler bei der Farbzuordnung. Es ist ein anderer Anwendungsfall, aber es könnte darauf hindeuten , dass es N-1in cmap = mpl.colors.LinearSegmentedColormap.from_list('Custom cmap', cmaplist, cmap.N-1). Wenn nicht, sind die Farben nicht gleichmäßig in den Behältern verteilt und Sie haben ein Problem mit der Zaunbarriere.
Jlandercy
1
Hier ist der Code zum Reproduzieren einer gleichmäßig verteilten Zuordnung:q=np.arange(0.0, 1.01, 0.1) cmap = mpl.cm.get_cmap('jet') cmaplist = [cmap(x) for x in q] cmap = mpl.colors.LinearSegmentedColormap.from_list('Custom cmap', cmaplist, len(q)-1) norm = mpl.colors.BoundaryNorm(q, cmap.N)
jlandercy
Ich bin mir nicht sicher N-1, Sie haben vielleicht Recht, aber ich kann es nicht mit meinem Beispiel wiederholen. Sie können das LinearSegmentedColormap(und sein NArgument) vermeiden, indem Sie a verwenden ListedColormap. Die Dokumente haben sich seit '13 stark verbessert, siehe zum Beispiel: matplotlib.org/3.1.1/tutorials/colors/…
Rutger Kassies
62

Sie könnten diesem Beispiel folgen :

#!/usr/bin/env python
"""
Use a pcolor or imshow with a custom colormap to make a contour plot.

Since this example was initially written, a proper contour routine was
added to matplotlib - see contour_demo.py and
http://matplotlib.sf.net/matplotlib.pylab.html#-contour.
"""

from pylab import *


delta = 0.01
x = arange(-3.0, 3.0, delta)
y = arange(-3.0, 3.0, delta)
X,Y = meshgrid(x, y)
Z1 = bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = Z2 - Z1 # difference of Gaussians

cmap = cm.get_cmap('PiYG', 11)    # 11 discrete colors

im = imshow(Z, cmap=cmap, interpolation='bilinear',
            vmax=abs(Z).max(), vmin=-abs(Z).max())
axis('off')
colorbar()

show()

welches das folgende Bild erzeugt:

Poormans_Contour

David Zwicker
quelle
14
cmap = cm.get_cmap ('jet', 20), dann bringt mich die Streuung (x, y, c = Tags, cmap = cmap) auf halbem Weg dorthin - es ist sehr schwierig, eine nützliche Dokumentation für matplotlib zu finden
bph
Link scheint unterbrochen zu sein, FYI.
Quinn Culver
42

Die obigen Antworten sind gut, außer dass sie keine korrekte Platzierung der Häkchen in der Farbleiste haben. Ich mag es, wenn die Häkchen in der Mitte der Farbe sind, damit die Zuordnung von Zahl -> Farbe klarer wird. Sie können dieses Problem lösen, indem Sie die Grenzen des Matshow-Aufrufs ändern:

import matplotlib.pyplot as plt
import numpy as np

def discrete_matshow(data):
    #get discrete colormap
    cmap = plt.get_cmap('RdBu', np.max(data)-np.min(data)+1)
    # set limits .5 outside true range
    mat = plt.matshow(data,cmap=cmap,vmin = np.min(data)-.5, vmax = np.max(data)+.5)
    #tell the colorbar to tick at integers
    cax = plt.colorbar(mat, ticks=np.arange(np.min(data),np.max(data)+1))

#generate data
a=np.random.randint(1, 9, size=(10, 10))
discrete_matshow(a)

Beispiel einer diskreten Farbleiste

ben.dichter
quelle
1
Ich bin damit einverstanden, dass das Platzieren des Häkchens in der Mitte der entsprechenden Farbe sehr hilfreich ist, wenn Sie diskrete Daten betrachten. Ihre zweite Methode ist korrekt. Ihre erste Methode ist jedoch im Allgemeinen falsch : Sie beschriften die Häkchen mit Werten, die nicht mit ihrer Platzierung in der Farbleiste übereinstimmen. set_ticklabels(...)sollte nur zur Steuerung der Etikettenformatierung verwendet werden (z. B. Dezimalzahl usw.). Wenn die Daten wirklich diskret sind, stellen Sie möglicherweise keine Probleme fest. Wenn das System Rauschen aufweist (z. B. 2 -> 1,9), führt diese inkonsistente Beschriftung zu einer irreführenden und falschen Farbleiste.
E. Davis
E., ich denke, Sie haben Recht, dass das Ändern der Grenzwerte die überlegene Lösung ist, also habe ich die andere entfernt - obwohl keiner von beiden gut mit "Rauschen" umgehen würde. Für die Verarbeitung kontinuierlicher Daten wären einige Anpassungen erforderlich.
ben.dichter
37

Um Werte über oder unter dem Bereich der Farbkarte festzulegen, sollten Sie die Methoden set_overund set_underder Farbkarte verwenden. Wenn Sie einen bestimmten Wert kennzeichnen möchten, maskieren Sie ihn (dh erstellen Sie ein maskiertes Array) und verwenden Sie die set_badMethode. (Sehen Sie sich die Dokumentation für die Basis-Colormap-Klasse an: http://matplotlib.org/api/colors_api.html#matplotlib.colors.Colormap )

Es hört sich so an, als ob Sie so etwas wollen:

import matplotlib.pyplot as plt
import numpy as np

# Generate some data
x, y, z = np.random.random((3, 30))
z = z * 20 + 0.1

# Set some values in z to 0...
z[:5] = 0

cmap = plt.get_cmap('jet', 20)
cmap.set_under('gray')

fig, ax = plt.subplots()
cax = ax.scatter(x, y, c=z, s=100, cmap=cmap, vmin=0.1, vmax=z.max())
fig.colorbar(cax, extend='min')

plt.show()

Geben Sie hier die Bildbeschreibung ein

Joe Kington
quelle
das ist wirklich gut - ich habe versucht, set_under zu verwenden, aber vmin nicht eingeschlossen, also glaube ich nicht, dass es irgendetwas tat
bph
8

Dieses Thema ist bereits gut behandelt, aber ich wollte etwas Spezifischeres hinzufügen: Ich wollte sichergehen, dass ein bestimmter Wert dieser Farbe zugeordnet wird (keiner Farbe).

Es ist nicht kompliziert, aber da ich einige Zeit gebraucht habe, könnte es anderen helfen, nicht so viel Zeit zu verlieren wie ich :)

import matplotlib
from matplotlib.colors import ListedColormap

# Let's design a dummy land use field
A = np.reshape([7,2,13,7,2,2], (2,3))
vals = np.unique(A)

# Let's also design our color mapping: 1s should be plotted in blue, 2s in red, etc...
col_dict={1:"blue",
          2:"red",
          13:"orange",
          7:"green"}

# We create a colormar from our list of colors
cm = ListedColormap([col_dict[x] for x in col_dict.keys()])

# Let's also define the description of each category : 1 (blue) is Sea; 2 (red) is burnt, etc... Order should be respected here ! Or using another dict maybe could help.
labels = np.array(["Sea","City","Sand","Forest"])
len_lab = len(labels)

# prepare normalizer
## Prepare bins for the normalizer
norm_bins = np.sort([*col_dict.keys()]) + 0.5
norm_bins = np.insert(norm_bins, 0, np.min(norm_bins) - 1.0)
print(norm_bins)
## Make normalizer and formatter
norm = matplotlib.colors.BoundaryNorm(norm_bins, len_lab, clip=True)
fmt = matplotlib.ticker.FuncFormatter(lambda x, pos: labels[norm(x)])

# Plot our figure
fig,ax = plt.subplots()
im = ax.imshow(A, cmap=cm, norm=norm)

diff = norm_bins[1:] - norm_bins[:-1]
tickz = norm_bins[:-1] + diff / 2
cb = fig.colorbar(im, format=fmt, ticks=tickz)
fig.savefig("example_landuse.png")
plt.show()

Geben Sie hier die Bildbeschreibung ein

Enzoupi
quelle
Ich habe versucht, dies zu replizieren, aber der Code wird nicht ausgeführt, da 'tmp' undefiniert ist. Unklar ist auch, was 'pos' in der Lambda-Funktion ist. Vielen Dank!
George Liu
@ GeorgeLiu In der Tat wurden Sie geschrieben! Ich habe einen Fehler beim Kopieren / Einfügen gemacht und es ist jetzt behoben! Der Codeausschnitt läuft jetzt! In Bezug auf posIch bin nicht ganz sicher, warum es hier ist , aber es wird durch die FuncFormatter () angefordert ... Vielleicht hat jemand anderes kann uns darüber aufklären!
Enzoupi
7

Ich habe diese Ideen untersucht und hier sind meine fünf Cent wert. Es wird vermieden, aufzurufen BoundaryNormund normals Argument für scatterund anzugeben colorbar. Ich habe jedoch keine Möglichkeit gefunden, den ziemlich langwierigen Anruf zu beseitigen matplotlib.colors.LinearSegmentedColormap.from_list.

Hintergrund ist, dass matplotlib sogenannte qualitative Farbkarten bereitstellt, die mit diskreten Daten verwendet werden sollen. Set1hat beispielsweise 9 leicht unterscheidbare Farben und tab20könnte für 20 Farben verwendet werden. Bei diesen Karten kann es natürlich sein, ihre ersten n Farben zu verwenden, um Streudiagramme mit n Kategorien zu färben, wie im folgenden Beispiel. Das Beispiel erzeugt auch eine Farbleiste mit n diskreten Farben, die entsprechend gekennzeichnet sind.

import matplotlib, numpy as np, matplotlib.pyplot as plt
n = 5
from_list = matplotlib.colors.LinearSegmentedColormap.from_list
cm = from_list(None, plt.cm.Set1(range(0,n)), n)
x = np.arange(99)
y = x % 11
z = x % n
plt.scatter(x, y, c=z, cmap=cm)
plt.clim(-0.5, n-0.5)
cb = plt.colorbar(ticks=range(0,n), label='Group')
cb.ax.tick_params(length=0)

welches das Bild unten erzeugt. Das nim Aufruf zum Angeben Set1der ersten nFarben dieser Farbkarte und das letzte nim Aufruf zum Angeben, from_list um eine Karte mit nFarben zu erstellen (der Standardwert ist 256). Um cmdie Standard-Farbkarte mit festzulegen plt.set_cmap, musste ich ihr einen Namen geben und sie registrieren, nämlich:

cm = from_list('Set15', plt.cm.Set1(range(0,n)), n)
plt.cm.register_cmap(None, cm)
plt.set_cmap(cm)
...
plt.scatter(x, y, c=z)

Streudiagramm mit diskreten Farben

Kristjan Jonasson
quelle
1

Ich denke, Sie möchten sich colours.ListedColormap ansehen , um Ihre Farbkarte zu generieren, oder wenn Sie nur eine statische Farbkarte benötigen, habe ich an einer App gearbeitet , die möglicherweise hilft.

ChrisC
quelle
das sieht cool aus, möglicherweise übertrieben für meine Bedürfnisse - könnten Sie eine Möglichkeit vorschlagen, einen Grauwert auf einer vorhandenen Farbkarte zu markieren? damit 0 Werte grau und die anderen als Farben herauskommen?
bph
@Hiett was ist mit dem Generieren einer RGB-Array-Farbliste basierend auf Ihren y-Werten und dem Übergeben dieser an ListedColormap? Sie können einen Wert mit color_list [y == value_to_tag] = grey_color markieren.
ChrisC