Настройка цветовых карт для геопостроения

Я рисую несколько географиков с помощью cartopy. Использование нормализации двух наклонов и цветовой карты «RdBu_r» для построения поля температуры воздуха.

color_map = 'RdBu_r'
fig = plt.figure(figsize=(16, 12))
ax = plt.axes(projection=prj)

norm = TwoSlopeNorm(vmin=np.min(data), vcenter=273.15, vmax=np.max(data))

filled_contour = ax.contourf(data['longitude'], data['latitude'], data, norm=norm, levels=15, transform=ccrs.PlateCarree(), cmap=color_map, extend='both')

isotherm_0 = ax.contour(data['longitude'], data['latitude'], data, levels=[273.15], colors='green', transform=ccrs.PlateCarree(), linewidths=0.5, linestyles='dashed')

Я выполняю нормализацию, чтобы покрыть отрицательные температуры синими оттенками, а положительные температуры красными оттенками. Если набор данных содержит широкий диапазон отрицательных и положительных температур, географик выглядит в порядке:

Но если будет много положительных температур и мало слегка отрицательных, то карта не будет построена так, как ожидалось: На изображении видно, как слегка отрицательные температуры (минимум 272,6236 К) окрашены в темно-синий оттенок (крайнее синее значение для карты цветов), а не в светло-голубой, как для температур чуть ниже нуля.

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

ОБНОВЛЯТЬ: Поскольку в моем проекте нет таких естественных экстремальных температур, я взял фиксированный массив и применил к нему цветовую карту:

# Compute min and max of data
min_val, max_val = np.min(data), np.max(data)

# Define the center (K or C)
center = 273.15 if min_val>150 else 0

# Define the temperature range for colormapping
levels = np.arange(-80, 81, 1) if center==0 else np.arange(193.15, 354.15, 1)

color_map = 'RdBu_r'

filled_contour = ax.contourf(data['lon'], data['lat'], data, levels=levels, transform=ccrs.PlateCarree(), cmap=color_map, extend='both')

В результате цвета хорошо распределены, но в районе 0 градусов цвета слишком слабые (почти белые):

Поэтому я добавил разные варианты цветовой карты: «Красные» только для положительных температур, «Синие» для отрицательных и «RdBu_r» для обеих. И здесь я определил соответствующую нормализацию:

# Determine normalization and colormap
if min_val >= center:
    norm = Normalize(vmin=center, vmax=max_val)
    color_map = 'Reds'
elif max_val < center:
    norm = Normalize(vmin=min_val, vmax=center)
    color_map = 'Blues'
else:
    norm = TwoSlopeNorm(vmin=min_val, vcenter=center, vmax=max_val)
    color_map = 'RdBu_r'

Затем рисование с помощью norm=norm дало мне следующее: С точки зрения цветов это лучше, хотя мне нужно обрезать цветовую полосу. На этом изображении есть синий цвет для отрицательных температур, но он не очень репрезентативен: он слишком яркий для температур чуть ниже нуля.

Наконец, чтобы обрезать цветовую полосу, я добавил следующее:

filled_contour.set_clim(min_val-5, max_val+6) 

Что дало мне это: И хотя цветовая полоса здесь не была обрезана, цвета здесь распределены еще более естественно: светло-голубой для негатива (но не слишком белый) и приятный градиент для позитива. Тем не менее, мне нужно как-то обрезать цветовую полосу...

Другая проблема возникла с различными вариантами цветовой карты, например, только для положительных температур. Цветовая карта (для диапазона значений -80, ..., 80) распределяется неправильно, начиная с vmin=0:

norm = Normalize(vmin=center, vmax=max_val)
color_map = 'Reds'

Думаю, мне придется определить другой диапазон уровней для положительных температур (например, np.arange(0, 81, 1)). Однако мне все это кажется быстрым и грязным обходным путем. Мне бы хотелось, чтобы для этого объекта существовал какой-нибудь умный автоматический способ. Эта возня с цветом занимает гораздо больше времени, чем работа с данными и т. д., никогда бы не подумал, что это настолько сложно...

norm = plt.Normalize(vmin=np.min(data), vmax=np.max(data)) ?
Timeless 08.07.2024 20:50

@Timeless Да, это работает, но таким образом он одинаково отображает цветовую карту на весь диапазон значений, таким образом окрашивая положительные температуры также синими оттенками. Как я уже говорил, для температур около нуля (273,15 К) я хочу, чтобы отрицательные температуры были окрашены синими оттенками, а положительные температуры - красными оттенками. Хотя я использую метод Normalize() для случаев, когда температуры только положительные (цветовая карта «Красные») или только отрицательные (цветовая карта «Синий»).

Outlaw 09.07.2024 10:45
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
2
73
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В основном это связано с фиксированным количеством ячеек в аргументе levels функции contourf.

Согласно документации , int levels обычно генерирует равноотстоящие друг от друга контурные линии между минимумом и максимумом данных (иногда это может привести к меньшему количеству ячеек, чтобы сделать значения разделения более приятными на цветовой панели. Например, ваш второй пример имеет только 11 уровней , меньше 15 вы указали). Если ваши данные имеют слишком большой максимум (), то небольшие значения ниже 273,15 попадут в один контейнер, поэтому будет только один цвет.

Одним из простых решений является увеличение количества уровней. Скажем, если мы хотим, чтобы на стороне было как минимум 3 цвета с относительно меньшими диапазонами, тогда установите levels=(np.ptp(data)/(np.min((273.15-np.min(data), np.max(data)-273.15))/3)).astype(int) в ax.contourf. Но будьте осторожны: это может привести к слишком большому количеству уровней, когда максимум или минимум будут слишком близки к 273,15.

Вы также можете попробовать указать уровни вручную, используя фиксированный массив и фиксированный TwoSlopeNorm.

Спасибо, я воспользовался вашим советом и дополнил свой вопрос новой информацией.

Outlaw 14.07.2024 12:34
Ответ принят как подходящий

Для достижения своей цели я использовал фиксированный массив и фиксированную цветовую карту.

Я получил цветовую карту «RdBu_r» и применил ее к температурному диапазону [-50, +50], чтобы получить:

cm = {
            -50: "#053061",
            -49: "#073466",
            ...
            49: "#750421",
            50: "#6c011f" }

temperature_range = list(cm.keys())  #range of values for plotting
temperature_range = temperature_range[::step]  #if _step_-degree gradation is required
colors = list(cm.values())

# if I want the colormap to fit the real given range of values 
# instead of a general [-50, +50] range, I use the following 
# temperature_range. It must be equal on both sides of zero 
# since I use binary colormap, where middle part is always zero degrees: 
lim = np.ceil(np.max(np.abs(dataset)))
temperature_range = np.arange(-lim, lim+1, step)

custom_cmap = ListedColormap(colors, name = "rdbu_colormap")
norm = BoundaryNorm(temperature_range, custom_cmap.N)

filled_contour = ax.contourf(lons, lats, data, norm=norm, levels=temperature_range,
                             transform=ccrs.PlateCarree(), cmap=custom_cmap, extend='both')
cbar = plt.colorbar(filled_contour, ax=ax, orientation='horizontal', pad=0.05, aspect=50)

Другие вопросы по теме