Matplotlib แถบสีไม่ต่อเนื่อง


99

ฉันกำลังพยายามสร้างแถบสีแยกสำหรับ scatterplot ใน matplotlib

ฉันมีข้อมูล x, y ของฉันและแต่ละจุดมีค่าแท็กจำนวนเต็มซึ่งฉันต้องการแสดงด้วยสีที่ไม่ซ้ำกันเช่น

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

โดยทั่วไปแท็กจะเป็นจำนวนเต็มตั้งแต่ 0-20 แต่ช่วงที่แน่นอนอาจเปลี่ยนแปลงได้

จนถึงตอนนี้ฉันเพิ่งใช้การตั้งค่าเริ่มต้นเช่น

plt.colorbar()

ซึ่งให้ช่วงสีที่ต่อเนื่องกัน ตามหลักการแล้วฉันต้องการชุดสีที่ไม่ต่อเนื่อง n (n = 20 ในตัวอย่างนี้) ที่ดีไปกว่านั้นคือการได้รับค่าแท็กเป็น 0 เพื่อสร้างสีเทาและ 1-20 มีสีสัน

ฉันพบสคริปต์ 'ตำราอาหาร' บางส่วน แต่มันซับซ้อนมากและฉันไม่คิดว่ามันเป็นวิธีที่ถูกต้องในการแก้ปัญหาที่ดูเหมือนง่าย ๆ


1
ไม่นี้หรือนี้ความช่วยเหลือ?
Francesco Montesano

ขอบคุณสำหรับลิงค์ แต่ตัวอย่างที่ 2 คือสิ่งที่ฉันหมายถึงเกี่ยวกับวิธีการที่ซับซ้อนอย่างมากในการทำงานเล็กน้อย (ดูเหมือน) เล็กน้อย - ลิงก์ที่ 1 มีประโยชน์
bph

2
ผมพบว่าการเชื่อมโยงนี้เป็นประโยชน์อย่างมากในการ discretizing ตารางสีที่มีอยู่: gist.github.com/jakevdp/91077b0cae40f8f8244a
BallpointBen

คำตอบ:


94

คุณสามารถสร้างแถบสีแยกที่กำหนดเองได้อย่างง่ายดายโดยใช้ BoundaryNorm เป็น normalizer สำหรับการกระจายของคุณ บิตที่เล่นโวหาร (ในวิธีการของฉัน) ทำให้การแสดง 0 เป็นสีเทา

สำหรับรูปภาพฉันมักจะใช้ cmap.set_bad () และแปลงข้อมูลของฉันเป็นอาร์เรย์ที่มาสก์จำนวนมาก นั่นจะง่ายกว่ามากในการสร้าง 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แสดงข้อผิดพลาด ใช้mpl.colorbar.ColorbarBase
zeeshan khan

ขอบคุณสำหรับคำตอบนี้คิดถึงจริงๆจาก doc ฉันพยายามเปลี่ยนตำแหน่งสำหรับวินด์โรสของเปอร์เซ็นไทล์และฉันมีข้อบกพร่องเกี่ยวกับการแมปสี มันเป็นกรณีการใช้งานที่แตกต่างกัน แต่มันอาจจะชี้ให้เห็นว่ามันเป็นใน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/…
Rutger Kassies

64

คุณสามารถทำตามตัวอย่างนี้:

#!/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 ('jet', 20) จากนั้นกระจาย (x, y, c = tags, cmap = cmap) ทำให้ฉันมีส่วนร่วม - มันยากมากที่จะหาเอกสารที่เป็นประโยชน์สำหรับ matplotlib
bph

ดูเหมือนว่าลิงก์จะเสีย FYI
Quinn Culver

45

คำตอบข้างต้นเป็นคำตอบที่ดียกเว้นว่าไม่มีการวางเห็บบนแถบสี ฉันชอบให้ขีดอยู่ตรงกลางของสีเพื่อให้การแมปสีจำนวน -> ชัดเจนยิ่งขึ้น คุณสามารถแก้ปัญหานี้ได้โดยเปลี่ยนขีด จำกัด ของการโทร 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. Davis

อีฉันคิดว่าคุณคิดถูกแล้วที่การเปลี่ยนขีด จำกัด เป็นวิธีแก้ปัญหาที่ดีกว่าดังนั้นฉันจึงลบอีกอันหนึ่งออกแม้ว่าจะไม่สามารถจัดการกับ "เสียงรบกวน" ได้ จำเป็นต้องมีการปรับเปลี่ยนบางอย่างเพื่อจัดการข้อมูลต่อเนื่อง
ben.dichter

39

หากต้องการตั้งค่าให้สูงกว่าหรือต่ำกว่าช่วงของ colormap คุณจะต้องใช้set_overและset_underวิธีการของ colormap หากคุณต้องการตั้งค่าสถานะเฉพาะให้มาสก์ค่านั้น (เช่นสร้างอาร์เรย์ที่มาสก์) และใช้set_badวิธีการ (ดูเอกสารประกอบสำหรับคลาส colormap พื้นฐาน: 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()

ป้อนคำอธิบายภาพที่นี่


กำลังพยายามจำลองสิ่งนี้ แต่โค้ดไม่ทำงานเนื่องจากไม่ได้กำหนด 'tmp' ยังไม่ชัดเจนว่า 'pos' คืออะไรในฟังก์ชันแลมด้า ขอบคุณ!
George Liu

@GeorgeLiu แน่นอนคุณเขียน! ฉันคัดลอก / วางผิดพลาดและตอนนี้เป็น fxed! ข้อมูลโค้ดกำลังทำงาน! เกี่ยวกับposฉันไม่แน่ใจทั้งหมดว่าทำไมจึงอยู่ที่นี่ แต่ FuncFormatter ร้องขอ () ... อาจมีคนอื่นสามารถให้ความกระจ่างแก่เราได้!
Enzoupi

7

ฉันได้ตรวจสอบแนวคิดเหล่านี้และนี่คือห้าเซ็นต์ของฉัน มันหลีกเลี่ยงการเรียกBoundaryNormเช่นเดียวกับการระบุnormเป็นอาร์กิวเมนต์ไปและscatter แต่ฉันได้พบวิธีการกำจัดโทรค่อนข้างยืดยาวไปที่ใดcolorbarmatplotlib.colors.LinearSegmentedColormap.from_list

พื้นหลังบางส่วนคือ matplotlib ให้ colormaps เชิงคุณภาพที่เรียกว่ามีวัตถุประสงค์เพื่อใช้กับข้อมูลที่ไม่ต่อเนื่อง 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ในการเรียกร้องให้Set1ระบุแรกnสีของตารางสีนั้นและสุดท้ายnในการเรียกร้องให้from_list ระบุในการสร้างแผนที่ที่มีnสี (ค่าเริ่มต้นเป็น 256) ในการตั้งค่าcmเป็น colormap เริ่มต้นด้วย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)

scatterplot ที่มีสีไม่ต่อเนื่อง


1

ฉันคิดว่าคุณต้องการดูสี ListColormapเพื่อสร้าง colormap ของคุณหรือถ้าคุณต้องการแค่ colormap แบบคงที่ฉันใช้แอพที่อาจช่วยได้


ที่ดูดีและอาจจะมากเกินไปสำหรับความต้องการของฉันคุณช่วยแนะนำวิธีการติดแท็กค่าสีเทาลงใน colormap ที่มีอยู่ได้ไหม เพื่อให้ค่า 0 ออกมาเป็นสีเทาและค่าอื่น ๆ ออกมาเป็นสี?
bph

@Hiett แล้วการสร้างอาร์เรย์ RGB color_list ตามค่า y ของคุณและส่งต่อไปยัง ListedColormap คุณสามารถแท็กค่าด้วย color_list [y == value_to_tag] = gray_color
ChrisC
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.