Дискретная палитра Matplotlib

99

Я пытаюсь создать дискретную цветовую шкалу для диаграммы рассеяния в matplotlib

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

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

обычно тег будет целым числом от 0 до 20, но точный диапазон может измениться.

до сих пор я использовал настройки по умолчанию, например

plt.colorbar()

что дает непрерывный диапазон цветов. В идеале мне нужен набор из n дискретных цветов (в этом примере n = 20). Еще лучше было бы получить значение тега 0 для получения серого цвета и 1-20 для красочного.

Я нашел несколько сценариев "поваренной книги", но они очень сложные, и я не могу думать, что они являются правильным способом решения, казалось бы, простой проблемы.

bph
источник
1
помогает то или это ?
Франческо Монтесано
спасибо за ссылки, но второй пример - это то, что я имею в виду об очень сложных средствах для выполнения (казалось бы) тривиальной задачи - 1-я ссылка полезна
bph
2
Я нашел эту ссылку очень полезно в Дискретизируя существующую цветовую палитру: gist.github.com/jakevdp/91077b0cae40f8f8244a
BallpointBen

Ответы:

95

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

Для изображений я часто использую cmap.set_bad () и конвертирую свои данные в массив с маскировкой numpy. Было бы намного проще сделать 0 серым, но я не мог заставить это работать с разбросом или настраиваемым cmap.

В качестве альтернативы вы можете создать свой собственный 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,
7
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. Документация значительно улучшилась по сравнению
Рутгер Кэссис
65

Вы можете последовать этому примеру :

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

что дает следующее изображение:

poormans_contour

Дэвид Цвикер
источник
14
CMAP = cm.get_cmap ( «струи», 20) , то разброс (х, у, с = теги, CMAP = CMAP) получает меня часть пути туда - его очень трудно найти полезную документацию по Matplotlib
бут
Ссылка вроде не работает, к вашему сведению.
Куинн Калвер
46

Приведенные выше ответы хороши, за исключением того, что в них нет правильного размещения галочки на шкале цветов. Мне нравится иметь галочки в середине цвета, чтобы отображение числа → цвет было более четким. Вы можете решить эту проблему, изменив пределы вызова 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), эта несогласованная маркировка приведет к вводящей в заблуждение и неправильной шкале цветов.
Э. Дэвис
E., я думаю, вы правы, что изменение пределов - лучшее решение, поэтому я удалил другой, хотя ни один из них не справился бы хорошо с "шумом". Некоторые настройки потребуются для обработки непрерывных данных.
ben.dichter
39

Чтобы установить значения выше или ниже диапазона цветовой карты, вы захотите использовать 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
10

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

Это несложно, но поскольку это заняло у меня некоторое время, это может помочь другим не терять столько времени, сколько я :)

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

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

Enzoupi
источник
Пытался воспроизвести это, однако код не запускается, потому что tmp не определен. Также неясно, что такое "pos" в лямбда-функции. Благодарность!
Джордж Лю,
@GeorgeLiu Действительно вы писали! Я сделал ошибку при копировании / вставке, и теперь она исправлена! Фрагмент кода запущен! Что касается, posя не совсем уверен, почему он здесь, но он запрашивается FuncFormatter () ... Может быть, кто-то еще может просветить нас об этом!
Enzoupi
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)

что производит изображение ниже. В nвызове to Set1указывает первые nцвета этой палитры, а последний nв вызове 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, чтобы сгенерировать свою цветовую карту, или если вам просто нужна статическая цветовая карта, я работал над приложением, которое могло бы помочь.

ChrisC
источник
это выглядит круто, возможно, излишне для моих нужд - не могли бы вы предложить способ пометить значение серого на существующей палитре? чтобы значения 0 были серыми, а остальные - цветами?
bph
@Hiett, а как насчет создания RGB-массива color_list на основе ваших значений y и передачи его в ListedColormap? Вы можете пометить значение с помощью color_list [y == value_to_tag] = gray_color.
ChrisC