简体   繁体   English

Matplotlib:使用索引颜色值以发散颜色图在颜色条中居中颜色

[英]Matplotlib: Center colors in colorbar with diverging colormap using indexed color values

This is partially two questions:这部分是两个问题:

  • How to center a (diverging) colormap around some given value?如何将(发散的)颜色图围绕某个给定值居中?
  • How to do that and at the same time map indexes in data to values in colormap?如何做到这一点,同时将数据中的索引映射到颜色图中的值? (further explained below) (下面进一步解释)

Some types of data, eg BMI score, have a natural mid-point.某些类型的数据,例如 BMI 分数,有一个自然的中点。 In matplotlib, there are several diverging colormaps .在 matplotlib 中,有几个不同的颜色图 I want the center of the colormap, ie the "middle" of the spectrum to be on the "ideal" BMI score, independent of what distribution of BMI scores is plotted.我希望颜色图的中心,即光谱的“中间”位于“理想”BMI 分数上,与绘制的 BMI 分数分布无关。

BMI class thresholds are: bmi_threshold = [16, 17, 18.5, 25, 30, 35] . BMI 类阈值是: bmi_threshold = [16, 17, 18.5, 25, 30, 35]

In the code below I make a scatter-plot of 300 random BMI values, with weight on x-axis and height on y-axis, as shown in the image below it.在下面的代码中,我绘制了 300 个随机 BMI 值的散点图,x 轴为体重,y 轴为身高,如下图所示。

In the first image, I have used np.digitize(bmi, bmi_threshold) as c -parameter to the ax.scatter() -call, but then each value in colorbar also become in range(7) , whereas I want the colorbar ticks to be in BMI scores (approxx. 15-40).在第一张图像中,我使用np.digitize(bmi, bmi_threshold)作为ax.scatter()调用的c参数,但随后ax.scatter()每个值也变为range(7) ,而我想要颜色条刻度BMI 分数(约 15-40)。 ( bmi is the array of 300 random bmi scores corresponding to x and y ) bmi是对应于xy的 300 个随机 bmi 分数的数组)

BMI thresholds are not evenly spread out, so the distance from digitized class indexes eg between 2 and 3 , is will not be correctly represented if I merely change the tick labels in the colorbar. BMI 阈值不是均匀分布的,因此如果我仅更改颜色栏中的刻度标签,则与数字化类别索引的距离(例如23之间)将无法正确表示。

In the second image, which is used with the code as shown below, does not seem to be centered correctly at the "ideal" BMI score of 22. I try to use the technique from " Make a scatter colorbar display only a subset of the vmin/vmax " to adjust the color range in the colorbar, but it doesn't seem to work as (I) expected.在与如下所示的代码一起使用的第二张图像中,似乎没有正确居中在“理想”BMI 分数 22 处。我尝试使用“ 使散点色条仅显示一个子集”中的技术vmin/vmax " 来调整颜色栏中的颜色范围,但它似乎不像 (I) 预期的那样工作。

Further, I think I could emphasize the "center" aka "ideal" scores by "squeezing" the colors by setting low and high in cmap(np.linspace(low, high, 7)) to values outside [0, 1], eg [-0.5,1.5], but then I have even more trouble to center the colorbar.此外,我认为我可以通过将cmap(np.linspace(low, high, 7)) lowhigh设置为 [0, 1] 之外的值来“挤压”颜色,从而强调“中心”又名“理想”分数,例如 [-0.5,1.5],但是我更难将颜色条居中。

What am I doing wrong, and how can I achieve this?我做错了什么,我怎样才能做到这一点?

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib as mpl

np.random.seed(4242)

# Define BMI class thresholds
bmi_thresholds = np.array([16, 17, 18.5, 25, 30, 35])

# Range to sample BMIs from
max_bmi = max(bmi_thresholds)*0.9
min_bmi = min(bmi_thresholds)*0.3

# Convert meters into centimeters along x-axis
@mpl.ticker.FuncFormatter
def m_to_cm(m, pos):
    return f'{int(m*100)}'

# Number of samples
n = 300

# Heights in range 0.50 to 2.20 meters
x = np.linspace(0.5, 2.2, n) 
# Random BMI values in range [min_bmi, max_bmi]
bmi = np.random.rand(n)*(max_bmi-min_bmi) + min_bmi  
# Compute corresponding weights
y = bmi * x**2      

# Prepare plot with labels, etc.
fig, ax = plt.subplots(figsize=(10,6))
ax.set_title(f'Random BMI values. $n={n}$')
ax.set_ylabel('Weight in kg')
ax.set_xlabel('Height in cm')
ax.xaxis.set_major_formatter(m_to_cm)
ax.set_ylim(min(y)*0.95, max(y)*1.05)
ax.set_xlim(min(x), max(x))

# plot bmi class regions (i.e. the "background")
for i in range(len(bmi_thresholds)+1):
    area_min = bmi_thresholds[i-1] if i > 0 else 0
    area_max = bmi_thresholds[i] if i < len(bmi_thresholds) else 10000#np.inf
    area_color = 'g' if i == 3 else 'y' if i in [2,4] else 'orange' if i in [1,5] else 'r'
    ax.fill_between(x, area_min * x**2, area_max * x**2, color=area_color, alpha=0.2, interpolate=True)

# Plot lines to emphasize regions, and additional bmi score lines (i.e. 10 and 40)    
common_plot_kwargs = dict(alpha=0.8, linewidth=0.5)
for t in (t for t in np.concatenate((bmi_thresholds, [10, 40]))):
    style = 'g-' if t in [18.5, 25] else 'r-' if t in [10,40] else 'k-' 
    ax.plot(x, t * x**2, style, **common_plot_kwargs)

# Compute offset from target_center to median of data range 
target_center = 22
mid_bmi = np.median(bmi)
s = max(bmi) - min(bmi)
d = target_center - mid_bmi
# Use offset to normalize offset as to the range [0, 1]
high = 1 if d < 0 else (s-d)/s
low = 0 if d >= 0 else -d/s


# Use normalized offset to create custom cmap to centered around ideal BMI?
cmap = plt.get_cmap('PuOr')
colors = cmap(np.linspace(low, high, 7))
cmap = mpl.colors.LinearSegmentedColormap.from_list('my cmap', colors)

# plot random BMIs
c = np.digitize(bmi, bmi_thresholds)
sax = ax.scatter(x, y, s=15, marker='.', c=bmi, cmap=cmap)

cbar = fig.colorbar(sax, ticks=np.concatenate((bmi_thresholds, [22, 10, 40])))
plt.tight_layout()

I found a decent solution here:我在这里找到了一个不错的解决方案:

http://chris35wills.github.io/matplotlib_diverging_colorbar/http://chris35wills.github.io/matplotlib_diverging_colorbar/

They created a normalization class using this code:他们使用以下代码创建了一个规范化类:

class MidpointNormalize(colors.Normalize):
    def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
        self.midpoint = midpoint
        colors.Normalize.__init__(self, vmin, vmax, clip)

    def __call__(self, value, clip=None):
        # I'm ignoring masked values and all kinds of edge cases to make a
        # simple example...
        x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
        return np.ma.masked_array(np.interp(value, x, y), np.isnan(value))

The class is used by doing something like this:通过执行以下操作来使用该类:

elev_max=3000; mid_val=0;

plt.imshow(ras, cmap=cmap, clim=(elev_min, elev_max), norm=MidpointNormalize(midpoint=mid_val,vmin=elev_min, vmax=elev_max))
plt.colorbar()
plt.show()

You can use the matplotlib built-in function that does the same thing:您可以使用执行相同操作的matplotlib内置函数:

matplotlib.colors.TwoSlopeNorm

See: https://matplotlib.org/3.2.2/gallery/userdemo/colormap_normalizations_diverging.html请参阅: https : //matplotlib.org/3.2.2/gallery/userdemo/colormap_normalizations_diverging.html

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM