Bilah warna diskrit Matplotlib

100

Saya mencoba membuat colorbar diskrit untuk sebar di matplotlib

Saya memiliki data x, y dan untuk setiap titik nilai tag integer yang ingin saya wakili dengan warna yang unik, misalnya

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

biasanya tag akan berupa bilangan bulat mulai dari 0-20, tetapi kisaran persisnya dapat berubah

sejauh ini saya baru saja menggunakan pengaturan default, mis

plt.colorbar()

yang memberikan rentang warna yang berkelanjutan. Idealnya saya ingin satu set n warna diskrit (n = 20 dalam contoh ini). Lebih baik lagi untuk mendapatkan nilai tag 0 untuk menghasilkan warna abu-abu dan 1-20 menjadi berwarna.

Saya telah menemukan beberapa skrip 'buku masak' tetapi sangat rumit dan saya tidak dapat berpikir itu adalah cara yang tepat untuk menyelesaikan masalah yang tampaknya sederhana

bph
sumber
1
apakah ini atau ini membantu?
Francesco Montesano
terima kasih untuk tautannya tetapi contoh kedua adalah yang saya maksud tentang cara yang sangat rumit untuk melakukan tugas yang (tampaknya) sepele - tautan pertama berguna
bph
2
Saya menemukan tautan ini sangat membantu dalam membedakan peta warna yang
BallpointBen

Jawaban:

96

Anda dapat membuat bilah warna diskrit kustom cukup mudah dengan menggunakan BoundaryNorm sebagai normalizer untuk pencar Anda. Bit yang unik (dalam metode saya) membuat 0 muncul sebagai abu-abu.

Untuk gambar saya sering menggunakan cmap.set_bad () dan mengubah data saya menjadi array bertopeng numpy. Itu akan jauh lebih mudah untuk membuat 0 abu-abu, tapi saya tidak bisa membuatnya bekerja dengan pencar atau cmap kustom.

Sebagai alternatif, Anda dapat membuat cmap Anda sendiri dari awal, atau membacakan cmap yang sudah ada dan mengganti hanya beberapa entri tertentu.

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)

masukkan deskripsi gambar di sini

Saya pribadi berpikir bahwa dengan 20 warna berbeda agak sulit untuk membaca nilai spesifiknya, tetapi itu terserah Anda tentu saja.

Rutger Kassies
sumber
Saya tidak yakin apakah ini diizinkan, tetapi dapatkah Anda melihat pertanyaan saya di sini ?
vwos
7
plt.colorbar.ColorbarBasemelempar Error. Gunakanmpl.colorbar.ColorbarBase
zeeshan khan
Terima kasih atas jawaban ini, sangat merindukan dari dok. Saya mencoba mentransposenya untuk windroses persentil dan saya memiliki bug dengan pemetaan warna. Ini adalah kasus penggunaan yang berbeda, tetapi mungkin menyarankan bahwa itu N-1dalam cmap = mpl.colors.LinearSegmentedColormap.from_list('Custom cmap', cmaplist, cmap.N-1). Jika tidak, warna tidak didistribusikan secara merata di dalam tempat sampah dan Anda memiliki masalah penghalang pagar.
jlandercy
1
Berikut adalah kode untuk mereproduksi pemetaan yang terdistribusi secara merata: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
Saya tidak yakin tentang N-1, Anda mungkin benar tetapi saya tidak dapat meniru dengan contoh saya. Anda mungkin menghindari LinearSegmentedColormap(dan Nargumennya) dengan menggunakan file ListedColormap. Dokumen telah meningkat pesat sejak '13, lihat contoh: matplotlib.org/3.1.1/tutorials/colors/…
Rutger Kassies
65

Anda bisa mengikuti contoh ini :

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

yang menghasilkan gambar berikut:

poormans_contour

David Zwicker
sumber
14
cmap = cm.get_cmap ('jet', 20) lalu sebar (x, y, c = tag, cmap = cmap) membuat saya berpisah - sangat sulit untuk menemukan dokumentasi yang berguna untuk matplotlib
bph
Tautan sepertinya rusak, FYI.
Quinn Culver
46

Jawaban di atas bagus, kecuali mereka tidak memiliki penempatan centang yang tepat pada bilah warna. Saya suka memiliki tanda centang di tengah warna sehingga angka -> pemetaan warna lebih jelas. Anda dapat mengatasi masalah ini dengan mengubah batas panggilan 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)

contoh colorbar diskrit

ben.dichter
sumber
1
Saya setuju bahwa menempatkan tanda centang di tengah warna yang sesuai sangat membantu saat melihat data diskrit. Metode kedua Anda benar. Namun, metode pertama Anda, secara umum, salah : Anda memberi label pada tanda centang dengan nilai yang tidak sesuai dengan penempatannya pada bilah warna. set_ticklabels(...)sebaiknya hanya digunakan untuk mengontrol format label (misalnya angka desimal, dll.). Jika datanya benar-benar terpisah, Anda mungkin tidak melihat adanya masalah. Jika ada gangguan dalam sistem (misalnya 2 -> 1.9), pelabelan yang tidak konsisten ini akan menghasilkan bilah warna yang salah dan menyesatkan.
E. Davis
E., saya pikir Anda benar bahwa mengubah batas adalah solusi yang lebih baik, jadi saya menghapus yang lain - meskipun keduanya tidak akan menangani "kebisingan" dengan baik. Beberapa penyesuaian akan diperlukan untuk menangani data berkelanjutan.
ben. Nichter
39

Untuk menetapkan nilai di atas atau di bawah kisaran peta warna, Anda akan ingin menggunakan set_overdan set_undermetode peta warna. Jika Anda ingin menandai nilai tertentu, tutupi (mis. Buat array bertopeng), dan gunakan set_badmetode. (Lihat dokumentasi untuk base colormap class: http://matplotlib.org/api/colors_api.html#matplotlib.colors.Colormap )

Sepertinya Anda menginginkan sesuatu seperti ini:

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

masukkan deskripsi gambar di sini

Joe Kington
sumber
itu benar-benar bagus - saya mencoba menggunakan set_under tetapi belum menyertakan vmin jadi saya tidak berpikir itu melakukan apa
bph
10

Topik ini sudah dibahas dengan baik tetapi saya ingin menambahkan sesuatu yang lebih spesifik: Saya ingin memastikan bahwa nilai tertentu akan dipetakan ke warna itu (bukan ke warna apa pun).

Ini tidak rumit tetapi karena saya butuh waktu, ini mungkin membantu orang lain agar tidak kehilangan waktu sebanyak yang saya lakukan :)

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

masukkan deskripsi gambar di sini

Enzoupi
sumber
Mencoba mereplikasi ini, namun kode tidak berjalan karena 'tmp' tidak ditentukan. Juga tidak jelas apa 'pos' dalam fungsi lambda. Terima kasih!
George Liu
@GeorgeLiu Memang Anda menulis! Saya melakukan kesalahan salin / tempel dan sekarang fxed! Potongan kode sekarang sedang berjalan! Mengenai possaya tidak sepenuhnya yakin mengapa itu ada di sini tetapi diminta oleh FuncFormatter () ... Mungkin orang lain bisa mencerahkan kami tentang itu!
Enzoupi
7

Saya telah menyelidiki ide-ide ini dan inilah nilai lima sen saya. Ini menghindari panggilan BoundaryNormserta menentukan normsebagai argumen untuk scatterdan colorbar. Namun saya tidak menemukan cara untuk menghilangkan panggilan yang bertele-tele ke matplotlib.colors.LinearSegmentedColormap.from_list.

Beberapa latar belakang adalah bahwa matplotlib menyediakan apa yang disebut peta warna kualitatif, yang dimaksudkan untuk digunakan dengan data diskrit. Set1misalnya memiliki 9 warna yang mudah dibedakan, dan tab20dapat digunakan untuk 20 warna. Dengan peta ini, wajar saja jika menggunakan n warna pertamanya untuk mewarnai plot sebar dengan n kategori, seperti contoh berikut. Contoh ini juga menghasilkan bilah warna dengan n warna berlainan diberi label yang sesuai.

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)

yang menghasilkan gambar di bawah ini. Dalam npanggilan untuk Set1menentukan nwarna pertama dari peta warna itu, dan yang terakhir ndalam panggilan untuk from_list menentukan untuk membuat peta dengan nwarna (defaultnya 256). Untuk menetapkan cmsebagai peta warna default plt.set_cmap, saya merasa perlu untuk memberinya nama dan mendaftarkannya, yaitu:

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)

scatterplot dengan warna berbeda

Kristjan Jonasson
sumber
1

Saya pikir Anda ingin melihat colors.ListedColormap untuk menghasilkan peta warna Anda, atau jika Anda hanya memerlukan peta warna statis, saya telah mengerjakan aplikasi yang mungkin membantu.

ChrisC
sumber
yang terlihat keren, mungkin berlebihan untuk kebutuhan saya - dapatkah Anda menyarankan cara memberi tag pada nilai abu-abu ke peta warna yang ada? sehingga nilai 0 keluar abu-abu dan yang lainnya keluar sebagai warna?
bph
@Hiett bagaimana dengan membuat sebuah RGB array color_list berdasarkan nilai y Anda dan meneruskannya ke ListedColormap? Anda dapat menandai nilai dengan color_list [y == value_to_tag] = gray_color.
ChrisC