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
python
matplotlib
bph
źródło
źródło
Odpowiedzi:
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)
Osobiście uważam, że przy 20 różnych kolorach trochę trudno jest odczytać konkretną wartość, ale to oczywiście zależy od Ciebie.
źródło
plt.colorbar.ColorbarBase
zgłasza błąd. Użyjmpl.colorbar.ColorbarBase
N-1
wcmap = 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ą.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)
N-1
, może masz rację, ale nie mogę tego powtórzyć na moim przykładzie. Możesz uniknąćLinearSegmentedColormap
(i jegoN
argumentu), używając plikuListedColormap
. Dokumentacja znacznie się poprawiła od '13, patrz na przykład: matplotlib.org/3.1.1/tutorials/colors/ ...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:
źródło
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)
źródło
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.Aby ustawić wartości powyżej lub poniżej zakresu mapy kolorów, będziesz chciał użyć metod
set_over
iset_under
z mapy kolorów. Jeśli chcesz oznaczyć konkretną wartość, zamaskuj ją (tj. Utwórz zamaskowaną tablicę) i użyj tejset_bad
metody. (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()
źródło
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()
źródło
pos
to, 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ć!Badałem te pomysły i oto moje pięć centów warte. Unika wywoływania,
BoundaryNorm
a także określanianorm
jako argumentu doscatter
icolorbar
. 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.
Set1
np. ma 9 łatwo rozróżnialnych kolorów itab20
moż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
n
wywołaniu doSet1
określa pierwszen
kolory tej mapy kolorów, a ostatnian
w wywołaniufrom_list
określa, aby utworzyć mapę zn
kolorami (domyślnie 256). Aby ustawićcm
jako domyślną mapę kolorów zplt.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)
źródło
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.
źródło