Это частично два вопроса:
- Как центрировать (расходящуюся) цветовую карту вокруг некоторого заданного значения?
- Как это сделать и одновременно использовать индексы карты в данныхк значениям в карте цветов?(подробнее объясняется ниже)
Некоторые типы данных, например, показатель ИМТ, имеют естественную среднюю точку.В 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()
