Контурный график Cartopy с логнормальными значениями показывает слишком бледные цвета

Вопрос или проблема

Я пытаюсь построить данные (.csv) по муниципалитетам (.shp) на карте Скандинавии, используя contourf. Эти данные имеют большой диапазон значений (см. список внизу), для чего я использую логарифмическое масштабирование, чтобы показать уровень детализации в нижних значениях. Код, который я использую, работал хорошо до недавнего времени. Смотрите изображение ниже слева.

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

Карта, показывающая результаты за прошлый месяц с правильными цветами Карта, показывающая результаты на этой неделе с неправильными цветами

Если есть более простой способ отображения значений на логарифмической шкале, я тоже буду рад услышать!

Вот мой код:

import cartopy.crs as ccrs
import numpy as np
from shapely.geometry import Polygon
import cartopy
from cartopy.io import shapereader
import matplotlib
import pandas
import geopandas

projection = ccrs.Mercator()
transform = ccrs.PlateCarree()
extent = [4.5, 31.9, 54.7, 71.24]
cmap = matplotlib.cm.get_cmap('Reds')

# Получить границы
resolution = '10m'
category = 'cultural'
name="admin_0_countries"

shpfilename = shapereader.natural_earth(resolution, category, name)
shpfilenamemunicipalities="path/to/ScandinaviaKommuner.shp"

# Прочитать файл shapefile с использованием geopandas
df = geopandas.read_file(shpfilename)
df2 = geopandas.read_file(shpfilenamemunicipalities)

# Выбрать интересующие страны
scan3 = df[ df['ADMIN'].isin(['Norway', 'Finland', 'Sweden']) ]
scan3_dissolved = scan3.dissolve(by='LEVEL')
poly = [scan3_dissolved['geometry'].values[0]]

# Создать маску
stamen_terrain = cimgt.Stamen('terrain_background')
st_proj = stamen_terrain.crs  # проекция, используемая изображениями Stamen
ll_proj = ccrs.PlateCarree()

def rect_from_bound(xmin, xmax, ymin, ymax):
    """Возвращает список (x,y)'s для прямоугольника"""
    xs = [xmax, xmin, xmin, xmax, xmax]
    ys = [ymax, ymax, ymin, ymin, ymax]
    return [(x, y) for x, y in zip(xs, ys)]

pad1 = .1  # отступ, единицы в градусах
exts = [poly[0].bounds[0] - pad1, poly[0].bounds[2] + pad1, poly[0].bounds[1] - pad1, poly[0].bounds[3] + pad1];
msk = Polygon(rect_from_bound(*exts)).difference(poly[0].simplify(0.01))
msk_stm  = st_proj.project_geometry(msk, ll_proj)  # проекция геометрии на проекцию, используемую stamen

# Загрузить данные .csv
firedata = pandas.read_csv('/path/to/CountryLevel_FireData.csv', delimiter=";", decimal=".")
firedatamunicipalities = pandas.read_csv('/path/to/MunicipalityLevel_FireData.csv', delimiter=";", decimal=".")

# Подмножество данных .csv
countries = firedata['Country'].tolist()
municipalities = firedatamunicipalities['Municipality'].tolist()
fireextents = firedatamunicipalities['FireIntensity'].tolist()

# Нормализовать данные о пожаре от 0 до 1 для извлечения цвета и логарифмическое преобразование
fireextents_log = list(range(0,966))
for fireextent, log in zip(fireextents, fireextents_log):
    if fireextent>0:
        fireextents_log[log] = np.log(fireextent)
    else:
        fireextents_log[log] = np.nan

fireextents_norm = (fireextents_log-np.nanmin(fireextents_log))/(np.nanmax(fireextents_log) - np.nanmin(fireextents_log))

# Создать график
fig, axs = plt.subplots(nrows = 1, ncols = 1,
                        subplot_kw = {'projection': projection},
                        layout="constrained")

axs.set_extent(extent)
axs.add_feature(feature.BORDERS, linewidth=linewidth_countrylines, zorder=zorder_countrylines)
axs.set_extent(extent)
axs.axis('off')
axs.add_geometries(shapely.get_parts(poly[0]), crs=transform, facecolor=facecolor_polygon,
                      edgecolor=edgecolor_polygon, linewidth=linewidth_polygon, zorder=18)
axs.add_geometries(msk_stm, st_proj, facecolor=facecolor_landmask, edgecolor=edgecolor_landmask,
                      zorder=16)

for municipality, fireextent_norm in zip(municipalities, fireextents_norm):
    poly3 = df2.loc[df2['Municipali'] == municipality]['geometry'].values
    if fireextent_norm > 0:
        rgba2 = cmap3(fireextent_norm)
    else:
        rgba2 = 'white'
    axs.add_geometries(poly3, crs=transform, facecolor=rgba2, edgecolor="black", linewidth=linewidth_municipality, zorder=14)

dummy_scat = axs.scatter(fireextents, fireextents, c=fireextents, cmap=cmap, zorder=0,
                          norm=matplotlib.colors.LogNorm())
cbar = fig.colorbar(dummy_scat, ax=axs, orientation='horizontal', shrink=1, pad=0.01)
cbar.set_label(colorbarlabel1, size=fontsize, labelpad=15)
cbar.ax.tick_params(labelsize=labelsize)

plt.show()

Часть данных о границах пожара, чтобы показать большой диапазон значений ниже. Как видно, значения действительно достигают диапазона 10³ и должны отображаться соответственно:

print(fireextents)
>>> [4183.6667, 1378.0, 1065.0, 991.6, 908.0, 612.0, 548.0625, 520.0, 412.7778, 331.0, 324.0, 280.5, 259.3333, 259.0, 234.0, 222.0, 212.6667, 199.0, 169.0, 158.3333, 153.5, 148.6, 147.5, 145.0, 133.8, 130.0, 108.3333, 105.25, 103.0, 103.0, 90.0, 86.0, 84.3, 82.1667, 80.6, 80.0, 79.8571, 75.0, 74.0, 71.0, 65.3, 63.0, 59.6, 58.0, 58.0, 57.6667, 55.0, 55.0, 54.9, 54.8636, 53.25, 51.0, 50.4667, 49.0, 49.0, 49.0, 48.7143, 48.0, 47.1071, 47.0, 46.5, 46.0, 45.0, 43.0, 42.2, 42.0, 41.0, 40.0, 38.2, 35.4, 34.9, 34.9, 34.5, 34.0, 34.0, 34.0, 33.8, 33.0, 33.0, 33.0, 32.8, 32.0, 31.0, 30.3333, 30.0, 30.0, 30.0, 29.6667, 29.5, 29.4, 29.3, 29.2857, 28.1667, 28.0, 28.0, 28.0, 27.0, 27.0, 26.0, 26.0, 26.0, 26.0, 26.0, 26.0, 25.8, 25.0, 25.0, 25.0, 25.0, 24.6, 24.5, 24.0, 24.0, 23.5, 23.1429, 23.0, 23.0, 22.0, 21.3, 19.0, 19.0, 19.0, 19.0, 19.0, 18.0, 17.5, 17.0, 16.5, 15.9167, 15.8, 15.5, 15.5, 15.3, 15.0714, 15.0, 15.0, 15.0, 14.5, 14.0, 13.5, 13.3333, 13.0, 12.5, 12.5, 12.2941, 12.0588, 12.0, 12.0, 12.0, 11.6, 11.0, 10.6667, 10.0, 9.6667, 9.6667, 9.5, 9.5, 9.0, 8.5714, 8.5, 8.3, 7.1667, 7.0, 7.0, 7.0, 6.6, 6.0, 6.0, 6.0, 6.0, 5.5, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 4.5, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 3.75, 3.5, 3.5, 3.4, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 2.5, 2.2, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 1.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... 0.0, 0.0, 0.0]

Ответ или решение

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

Основные шаги для исправления:

  1. Проверка логарифмического преобразования:
    Убедитесь, что вы корректно обрабатываете значения с помощью логарифмического преобразования. Ваша текущая реализация использует np.log, что может быть неэффективным для значений, равных или меньше нуля. Вместо этого вы можете использовать np.log1p, который обрабатывает случаи, когда значения равны нулю, что может помочь избежать NaN.

    Например:

    fireextents_log = np.log1p(fireextents)
  2. Обновление нормализации:
    Когда вы нормализуете значения, вы можете использовать Normalize из matplotlib, чтобы обновить ваше преобразование значений. Убедитесь, что используете LogNorm() для векторизации логарифмических данных во время создания цветовой карты. Это также поможет в ситуации с сильно изменяющимися диапазонами данных.

    Например:

    from matplotlib.colors import LogNorm
    fireextents_norm = LogNorm()(fireextents)
  3. Использование подходящих параметров цветовой карты:
    При создании цветовой карты с использованием cmap, убедитесь, что значение, отображаемое в scatter, совпадает с нормализованными логарифмическими значениями.

  4. Проверка на изменения в версиях библиотек:
    Если код работал раньше, проверьте свои зависимости (например, Cartopy, Matplotlib), чтобы убедиться, что обновления не изменили поведение каких-либо функций. Иногда изменения в цветовой интерполяции могут быть связаны с обновлениями библиотеки.

  5. Добавление цветовой карты:
    Убедитесь, что ваша цветовая карта соответствует вашим данным. Пробуйте использовать другие цветовые карты из matplotlib, которые лучше разбивают данные на логарифмической шкале. Например, используйте cmap='viridis' или cmap='plasma'.

  6. Пример кода:
    Вот как должен выглядеть модифицированный код:

    import matplotlib.pyplot as plt
    from matplotlib.colors import LogNorm
    
    # Ваша логика получения данных остается прежней
    
    # Вместо обычной нормализации используйте LogNorm
    fireextents_log = np.log1p(fireextents)  # Логарифмическое преобразование
    fireextents_norm = (fireextents_log - np.nanmin(fireextents_log)) / (np.nanmax(fireextents_log) - np.nanmin(fireextents_log))
    
    # Создание графика
    fig, ax = plt.subplots(subplot_kw={'projection': projection})
    ax.set_extent(extent)
    
    # Добавляем геометрию
    for municipality, fireextent_norm in zip(municipalities, fireextents_norm):
       poly3 = df2.loc[df2['Municipali'] == municipality]['geometry'].values
       rgba2 = cmap(fireextent_norm) if fireextent_norm > 0 else 'white'
       ax.add_geometries(poly3, crs=transform, facecolor=rgba2, edgecolor='black')
    
    # Делаем scatter для colorbar
    scatter = ax.scatter(municipalities, fireextents, c=fireextents, cmap='Reds', norm=LogNorm())
    cbar = plt.colorbar(scatter, ax=ax, orientation='horizontal')
    cbar.set_label('Fire Intensity')
    plt.show()

Заключение:

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

Оцените материал
Добавить комментарий

Капча загружается...