Matplotlib: Центрировать цвета на цветовой панели с помощью расходящейся карты цветов с использованием индексированных значений цвета - PullRequest
0 голосов
/ 19 сентября 2018

Это частично два вопроса:

  • Как центрировать (расходящуюся) цветовую карту вокруг некоторого заданного значения?
  • Как это сделать и одновременно использовать индексы карты в данныхк значениям в карте цветов?(подробнее объясняется ниже)

Некоторые типы данных, например, показатель ИМТ, имеют естественную среднюю точку.В matplotlib есть несколько расходящихся цветовых карт .Я хочу, чтобы центр цветовой карты, т. Е. «Середина» спектра находилась на «идеальной» шкале BMI, независимо от того, какое распределение баллов BMI нанесено на график.

Пороговые значения класса BMI: bmi_threshold = [16, 17, 18.5, 25, 30, 35].

В приведенном ниже коде я строю точечную диаграмму из 300 случайных значений ИМТ с весом по оси x и высотой по оси y, как показано на рисунке ниже.

На первом изображении я использовал np.digitize(bmi, bmi_threshold) в качестве c -параметра для ax.scatter() -call, но затем каждое значение в цветовой шкале также становится в range(7), тогда как я хочу, чтобы тики цветовой шкалы были в показателях ИМТ (примерно 15-40).(bmi - это массив из 300 случайных значений bmi, соответствующих x и y)

Пороговые значения BMI не распределены равномерно, поэтому расстояние от оцифрованных индексов классов, например, между 2 и 3, будет отображаться некорректно, если я просто поменяю метки в цветовой полосе.

Во втором изображении, которое используется с кодом, как показано ниже, похоже, неправильно отцентрировано на«идеальный» показатель ИМТ 22. Я пытаюсь использовать технику из « Сделать разброс цветовой шкалы, отображающий только подмножество vmin / vmax », чтобы отрегулировать цветовой диапазон в цветовой шкале, но это некажется, работает как я ожидал.

Кроме того, я думаю, что я мог бы подчеркнуть "центральные" или "идеальные" баллы, "сжав" цвета, установив low и high в cmap(np.linspace(low, high, 7))к значениям за пределами [0, 1], например [-0,5,1,5], но тогда у меня еще больше проблем с центрированием цветовой шкалы.

Что я делаю неправильно и как мне этого добиться?

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

1 Ответ

0 голосов
/ 07 августа 2019

Я нашел достойное решение здесь:

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

Они создали класс нормализации, используя этот код:

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

Этот класс используется при выполнении чего-либокак это:

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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...