Дискретна кольорова панель 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.