Dyskretny pasek kolorów Matplotlib

99

Próbuję stworzyć dyskretny pasek kolorów dla wykresu rozrzutu w matplotlib

Mam swoje dane x, y i dla każdego punktu wartość tagu całkowitą, którą chcę przedstawić za pomocą unikalnego koloru, np.

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

zazwyczaj tag będzie liczbą całkowitą z przedziału od 0 do 20, ale dokładny zakres może się zmienić

do tej pory korzystałem tylko z ustawień domyślnych, np

plt.colorbar()

co daje ciągłą gamę kolorów. Idealnie chciałbym mieć zestaw n dyskretnych kolorów (w tym przykładzie n = 20). Jeszcze lepiej byłoby uzyskać wartość tagu równą 0, aby uzyskać szary kolor, a 1-20 - kolorową.

Znalazłem kilka skryptów do `` książek kucharskich '', ale są one bardzo skomplikowane i nie sądzę, że są właściwym sposobem rozwiązania pozornie prostego problemu

bph
źródło
1
czy to czy to pomaga?
Francesco Montesano
dzięki za linki, ale drugi przykład jest tym, co mam na myśli, mówiąc o ogromnie zbyt skomplikowanych środkach do wykonania (pozornie) trywialnego zadania - pierwszy link jest przydatny
bph
2
Uważam, że ten link jest bardzo pomocny w dyskretyzacji istniejącej mapy
BallpointBen

Odpowiedzi:

94

Możesz dość łatwo utworzyć niestandardowy dyskretny pasek kolorów, używając BoundaryNorm jako normalizatora dla twojego rozproszenia. Dziwacznym elementem (w mojej metodzie) jest wyświetlanie 0 jako szarego.

W przypadku obrazów często używam cmap.set_bad () i konwertuję moje dane na tablicę z maską numpy. Byłoby znacznie łatwiej ustawić 0 szare, ale nie mogłem tego zmusić do pracy z rozproszeniem lub niestandardowym cmapem.

Alternatywnie możesz stworzyć własny cmap od podstaw lub odczytać istniejący i nadpisać tylko niektóre określone wpisy.

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)

wprowadź opis obrazu tutaj

Osobiście uważam, że przy 20 różnych kolorach trochę trudno jest odczytać konkretną wartość, ale to oczywiście zależy od Ciebie.

Rutger Kassies
źródło
Nie jestem pewien, czy jest to dozwolone, ale czy możesz spojrzeć na moje pytanie tutaj ?
vwos
7
plt.colorbar.ColorbarBasezgłasza błąd. Użyjmpl.colorbar.ColorbarBase
zeeshan khan
Dziękuję za tę odpowiedź, naprawdę tęsknię za tym od doktora. Próbowałem przetransponować to na windroses percentyli i miałem błąd z mapowaniem kolorów. To inny przypadek użycia, ale może sugerować, że jest N-1w cmap = mpl.colors.LinearSegmentedColormap.from_list('Custom cmap', cmaplist, cmap.N-1). Jeśli nie, kolory nie są równomiernie rozłożone w pojemnikach i występuje problem z barierą ogrodzeniową.
jlandercy
1
Oto kod do odtworzenia równo rozproszonego mapowania: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
Nie jestem pewien N-1, może masz rację, ale nie mogę tego powtórzyć na moim przykładzie. Możesz uniknąć LinearSegmentedColormap(i jego Nargumentu), używając pliku ListedColormap. Dokumentacja znacznie się poprawiła od '13, patrz na przykład: matplotlib.org/3.1.1/tutorials/colors/ ...
Rutger Kassies
64

Możesz naśladować ten przykład :

#!/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()

który tworzy następujący obraz:

poormans_contour

David Zwicker
źródło
14
cmap = cm.get_cmap ( 'strumień', 20), a następnie rozproszenia (x, y, c = znaczniki cmap = cmap) pobiera mi częściowo w nie - jego bardzo trudno jest znaleźć użyteczne dokumentacji matplotlib
BPH
Link wygląda na uszkodzony, do Twojej wiadomości.
Quinn Culver
45

Powyższe odpowiedzi są dobre, z wyjątkiem tego, że nie mają odpowiedniego umieszczenia kleszcza na pasku kolorów. Lubię mieć znaczniki w środku koloru, aby odwzorowanie liczb -> kolorów było bardziej wyraźne. Możesz rozwiązać ten problem, zmieniając limity wywołania matshow:

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)

przykład dyskretnego paska kolorów

ben.dichter
źródło
1
Zgadzam się, że umieszczenie znacznika w środku odpowiedniego koloru jest bardzo pomocne przy przeglądaniu danych dyskretnych. Twoja druga metoda jest poprawna. Jednak Twoja pierwsza metoda jest ogólnie błędna : oznaczasz znaczniki wartościami, które są niezgodne z ich umieszczeniem na pasku kolorów. set_ticklabels(...)powinny być używane tylko do kontrolowania formatowania etykiety (np. liczba dziesiętna itp.). Jeśli dane są naprawdę dyskretne, możesz nie zauważyć żadnych problemów. Jeśli w systemie występuje szum (np. 2 -> 1,9), to niespójne etykietowanie spowoduje wprowadzanie w błąd i nieprawidłowy pasek kolorów.
E. Davis
E., myślę, że masz rację, że zmiana limitów jest lepszym rozwiązaniem, więc usunąłem to drugie - chociaż żadne z nich nie radzi sobie dobrze z „hałasem”. Do obsługi danych ciągłych potrzebne byłyby pewne dostosowania.
ben.dichter
39

Aby ustawić wartości powyżej lub poniżej zakresu mapy kolorów, będziesz chciał użyć metod set_overi set_underz mapy kolorów. Jeśli chcesz oznaczyć konkretną wartość, zamaskuj ją (tj. Utwórz zamaskowaną tablicę) i użyj tej set_badmetody. (Zajrzyj do dokumentacji podstawowej klasy mapy kolorów: http://matplotlib.org/api/colors_api.html#matplotlib.colors.Colormap )

Wygląda na to, że chcesz czegoś takiego:

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

wprowadź opis obrazu tutaj

Joe Kington
źródło
to naprawdę dobrze - próbowałem użyć set_under, ale nie włączyłem vmin, więc nie sądzę, aby cokolwiek robił
bph
10

Ten temat jest już dobrze omówiony, ale chciałem dodać coś bardziej szczegółowego: chciałem mieć pewność, że dana wartość zostanie przypisana do tego koloru (a nie do żadnego koloru).

Nie jest to skomplikowane, ale ponieważ zajęło mi to trochę czasu, może pomóc innym nie stracić tyle czasu co ja :)

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

wprowadź opis obrazu tutaj

Enzoupi
źródło
Próbowałem to powtórzyć, ale kod nie działa, ponieważ „tmp” jest niezdefiniowane. Nie jest również jasne, czym jest „pos” w funkcji lambda. Dzięki!
George Liu
@GeorgeLiu Rzeczywiście, napisałeś! Zrobiłem błąd kopiuj / wklej i teraz jest naprawiony! Fragment kodu jest teraz uruchomiony! Jeśli chodzi o posto, nie jestem do końca pewien, dlaczego tu jest, ale prosi o to FuncFormatter () ... Może ktoś inny może nas o tym oświecić!
Enzoupi
7

Badałem te pomysły i oto moje pięć centów warte. Unika wywoływania, BoundaryNorma także określania normjako argumentu do scatteri colorbar. Jednak nie znalazłem sposobu na wyeliminowanie dość rozwlekłego wezwania domatplotlib.colors.LinearSegmentedColormap.from_list .

Pewne tło jest takie, że matplotlib udostępnia tak zwane jakościowe mapy kolorów, przeznaczone do użytku z danymi dyskretnymi. Set1np. ma 9 łatwo rozróżnialnych kolorów i tab20może być użyty do 20 kolorów. W przypadku tych map naturalne byłoby użycie ich pierwszych n kolorów na wykresach rozrzutu z n kategoriami, tak jak w poniższym przykładzie. Przykład tworzy również pasek kolorów z n dyskretnymi kolorami odpowiednio oznaczonymi.

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)

który tworzy obraz poniżej. W nwywołaniu do Set1określa pierwsze nkolory tej mapy kolorów, a ostatnia nw wywołaniu from_listokreśla, aby utworzyć mapę z nkolorami (domyślnie 256). Aby ustawić cmjako domyślną mapę kolorów z plt.set_cmap, stwierdziłem, że konieczne jest nadanie jej nazwy i zarejestrowanie, a mianowicie:

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)

wykres punktowy z dyskretnymi kolorami

Kristjan Jonasson
źródło
1

Myślę, że chciałbyś spojrzeć na kolory.ListedColormap, aby wygenerować twoją mapę kolorów, lub jeśli potrzebujesz tylko statycznej mapy kolorów. Pracowałem nad aplikacją, która może pomóc.

ChrisC
źródło
to wygląda fajnie, być może przesada jak na moje potrzeby - czy możesz zasugerować sposób na oznaczenie szarej wartości na istniejącej mapie kolorów? tak, że 0 wartości wyszły szare, a pozostałe jako kolory?
bph
@Hiett a co z generowaniem tablicy color_list RGB na podstawie wartości y i przekazaniem jej do ListedColormap? Możesz oznaczyć wartość tagiem color_list [y == value_to_tag] = gray_color.
ChrisC,