Дискретна кольорова панель Matplotlib


95

Я намагаюся зробити дискретну кольорову панель для розсіяного графіку в matplotlib

У мене є дані x, y і для кожної точки ціле значення тегу, яке я хочу представити унікальним кольором, наприклад

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

зазвичай тегом буде ціле число в діапазоні від 0-20, але точний діапазон може змінюватися

поки що я щойно використовував налаштування за замовчуванням, наприклад

plt.colorbar()

що дає безперервний діапазон кольорів. В ідеалі я хотів би набір з n дискретних кольорів (n = 20 у цьому прикладі). Ще краще було б отримати значення тегу 0, щоб отримати сірий колір, а 1-20 бути кольоровим.

Я знайшов кілька сценаріїв "кулінарної книги", але вони дуже складні, і я не можу подумати, що вони є правильним способом вирішити, здавалося б, просту проблему


1
робить це або це допомога?
Франческо Монтесано

дякую за посилання, але 2-й приклад - це те, що я маю на увазі вкрай складні засоби для виконання (здавалося б) тривіального завдання - корисне 1-е посилання
bph

1
Я знайшов це посилання дуже корисно в Діскретізіруя існуючу кольорову палітру: gist.github.com/jakevdp/91077b0cae40f8f8244a
BallpointBen

Відповіді:


91

Ви можете створити власну дискретну кольорову панель досить просто, використовуючи BoundaryNorm як нормалізатор для вашого розсіювання. Химерний біт (у моєму методі) робить 0 показ як сірий.

Для зображень я часто використовую cmap.set_bad () і перетворюю свої дані в маскований масив Numpy. Це було б набагато простіше зробити 0 сірим, але я не міг змусити це працювати з розкиданням або користувацькою картою.

Як альтернативу ви можете створити власну програму cmap з нуля або прочитати вже наявну та замінити лише деякі конкретні записи.

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)

введіть тут опис зображення

Я особисто думаю, що з 20 різними кольорами трохи важко прочитати конкретне значення, але це, звичайно, залежить від вас.


Я не впевнений, що це дозволено, але чи не могли б ви поглянути на моє запитання тут ?
vwos

6
plt.colorbar.ColorbarBaseкидає Помилка. Usempl.colorbar.ColorbarBase
zeeshan khan

Дякую за цю відповідь, дуже пропустіть її з документа. Я спробував перенести його на вітрові перцентилі, і у мене виникла помилка з кольоровим відображенням. Це інший варіант використання, але це може припустити, що він є N-1в cmap = mpl.colors.LinearSegmentedColormap.from_list('Custom cmap', cmaplist, cmap.N-1). Якщо ні, кольори не однаково розподіляються в контейнерах, і у вас проблема огорожі.
jlandercy

1
Ось код для відтворення однаково розподіленого відображення: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

Я не впевнений у цьому N-1, ви, можливо, праві, але я не можу відтворити це на своєму прикладі. Ви можете уникнути LinearSegmentedColormap(і це Nаргумент), використовуючи ListedColormap. Документи значно покращилися з 13 року, див. Наприклад: matplotlib.org/3.1.1/tutorials/colors/…
Рутгер Кассіс,

62

Ви можете наслідувати цей приклад :

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

який видає таке зображення:

бідний_контур


14
cmap = cm.get_cmap ('jet', 20), потім розкидання (x, y, c = теги, cmap = cmap) веде мене до дороги - дуже важко знайти корисну документацію для matplotlib
bph

Здається, посилання порушено, FYI.
Quinn Culver

44

Наведені вище відповіді хороші, за винятком того, що вони не мають належного розміщення галочки на кольоровій панелі. Мені подобається мати галочки посередині кольору, щоб відображення числа -> кольору було чіткішим. Ви можете вирішити цю проблему, змінивши межі виклику 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)

приклад дискретної кольорової панелі


1
Я згоден з тим, що розміщення галочки посередині відповідного кольору дуже корисно при перегляді дискретних даних. Ваш другий спосіб правильний. Однак ваш перший метод, як правило, помилковий : ви позначаєте галочки значеннями, які не відповідають їх розміщенню на кольоровій панелі. set_ticklabels(...)слід використовувати лише для контролю форматування етикетки (наприклад, десяткове число тощо). Якщо дані дійсно дискретні, ви можете не помітити жодних проблем. Якщо в системі є шум (наприклад, 2 -> 1,9), це суперечливе маркування призведе до введення в оману неправильної кольорової панелі.
Е. Девіс

Д., я думаю, ви маєте рацію, що зміна меж є найкращим рішенням, тому я прибрав інше - хоча жоден з них не впорався б із "шумом" добре. Для обробки безперервних даних знадобляться деякі коригування.
ben.dichter

37

Щоб встановити значення вище або нижче діапазону палітри, ви хочете використовувати set_overі set_underметоди палітри. Якщо ви хочете позначити певне значення, замаскуйте його (тобто створіть маскований масив) і використовуйте set_badметод. (Подивіться на документацію до базового класу кольорових карт: http://matplotlib.org/api/colors_api.html#matplotlib.colors.Colormap )

Здається, ви хочете щось подібне:

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

введіть тут опис зображення


це дійсно добре - я намагався використовувати set_under, але не включав vmin, тому я не думаю, що він щось робив
bph

9

Ця тема вже добре висвітлена, але я хотів додати щось більш конкретне: я хотів бути впевненим, що певне значення буде зіставлено з цим кольором (а не з будь-яким кольором).

Це не складно, але оскільки мені це зайняло деякий час, це може допомогти іншим не втратити стільки часу, скільки я :)

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

введіть тут опис зображення


Намагався повторити це, проте код не запускається, оскільки 'tmp' не визначено. Також незрозуміло, що таке "pos" у лямбда-функції. Дякую!
Джордж Лю

@GeorgeLiu Справді, ти писав! Я зробив помилку копіювання / вставлення, і це тепер виправлено! Фрагмент коду запущений! Що стосується, posя не зовсім впевнений, чому він тут, але про це вимагає FuncFormatter () ... Можливо, хтось інший може нас про це просвітити!
Ензупі

7

Я досліджував ці ідеї, і ось мої п'ять центів варті. Це дозволяє уникнути дзвінків BoundaryNorm, а також вказати normяк аргумент scatterі colorbar. Однак я не знайшов способу усунути досить затятий заклик до matplotlib.colors.LinearSegmentedColormap.from_list.

Деяким передумовою є те, що matplotlib надає так звані якісні кольорові карти, призначені для використання з дискретними даними. Set1, наприклад, має 9 легко розрізнюваних кольорів і tab20може використовуватися для 20 кольорів. За допомогою цих карт може бути природно використовувати їх перші n кольорів, щоб розфарбувати графіки розсіяння з n категоріями, як це робить наступний приклад. Приклад також створює кольорову панель із n дискретних кольорів, відповідним чином позначених.

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)

який створює зображення нижче. Значення nin у виклику Set1вказує перші nкольори цієї кольорової карти , а останнє nу виклику to from_listвказує на побудову карти з nкольорами (за замовчуванням 256). Для того, щоб встановити cmяк типову карту кольорів за допомогою plt.set_cmap, я виявив, що потрібно дати їй ім'я та зареєструвати, а саме:

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)

розкиданий сюжет із несхваленими кольорами


1

Я думаю, ви хотіли б поглянути на colors.ListedColormap, щоб сформувати вашу карту кольорів, або якщо вам просто потрібна статична карта кольорів, я працював над додатком, який може допомогти.


це виглядає круто, можливо, надмірно для моїх потреб - чи можете ви запропонувати спосіб позначення сірого значення на існуючій кольоровій карті? так що 0 значення виходять сірими, а інші виходять кольорами?
bph

@Hiett, а як щодо створення масиву RGB color_list на основі ваших значень y та передачі цього до ListedColormap? Ви можете позначити значення за допомогою color_list [y == value_to_tag] = grey_color.
ChrisC
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.