Как стать автором
Обновить

Генерация цветовых градиентов для дашбордов Dash и отдельных графиков Plotly

Уровень сложностиСредний
Время на прочтение5 мин
Количество просмотров1.1K

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

С формальностями покончено, перейдем к сути. В моей работе мне необходимо поддерживать несколько интерактивных приложений‑дашбордов, написанных с помощью библиотеки Dash. Они все разные, но у них есть нечто общее: в зависимости от выбранных параметров, дашборды визуализируют поведение разных сущностей, внутри которых разное количество категорий. Например, в разных регионах разное количество менеджеров, реализуется разное количество товарных групп и так далее. Кроме того, в любой момент это количество может увеличиться. Это создает сложности для применения выбранной цветовой схемы.

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

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

light_green_rgb = [135, 182, 96]
light_green = '87b660'

light_blue_rgb = [64, 92, 162]
light_blue = '#405ca2'

dark_blue_rgb = [38, 47, 78]
dark_blue = '#262f4e'

neutral_white = '#f2f2f2'

Цвета выглядят так. Если интересно, как я делаю визуализацию цветовых схем, напишите, я напишу об этом.

Цветовая схема графика
Цветовая схема графика

Для описания моей мысли, я сначала создам данные. Они синтетические и касаются какого-то виртуального параметра, который теоретически можно измерить у животных.

import random

#Инициализирую список animals_list, содержащий рандомный список животных
animals_list = ['Лев', 'Дельфин', 'Павлин', 'Панда', 'Жираф', 'Коала', 'Фламинго', 'Кенгуру', 'Орангутан', 'Крокодил', 'Колибри', 'Белый медведь', 'Зебра', 'Бурый медведь', 'Тигр', 'Слон', 'Леопард', 'Белка', 'Черепаха', 'Попугай']
#Инициализирую списки случайных значений длиной 50
animal_count = len(animals_list)
lists = []
for _ in range(animal_count):
    # Случайным образом задаю диапазон для генерации
    min_range = random.randint(10, 30)
    max_range = random.randint(50, 200)
    # Генерируем список случайных чисел в выбранном диапазоне
    lst = [random.randint(min_range, max_range) for _ in range(50)]
    lists.append(lst)

Теперь создам Box Plot, содержащий рандомное количество видов животных.

fig = go.Figure()
count_animals = random.randint(1, 20)
for i in range(0, count_animals):
    fig.add_trace(go.Box(y=lists[i], quartilemethod="linear", name=animals_list[i]))
fig.update_layout(height = 400, width = 800,
                  title_text=(f'Boxplot распределения значений вымышленного параметра для {count_animals} видов животных'), 
                  title_font=dict(size=15), title_y=0.97,
                  paper_bgcolor=neutral_white, plot_bgcolor=neutral_white,
                  margin = dict(t=30, l=10, r=10, b=10), 
                  xaxis_tickfont_size=10, yaxis_tickfont_size=10,
                  legend_font=dict(size=10))
fig.show()

Так будет выглядеть график для трех животных:

Box Plot для трех животных
Box Plot для трех животных

И вот так для 16:

Box Plot для шестнадцати животных
Box Plot для шестнадцати животных

Чтобы уйти от буйства красок в ситуации, когда количество категорий заранее не известно, я создаю список цветов, который представляет собой «плавное перетекание» из первого цвета в последний в списке.

import random
count_animals = random.randint(1, 20)

#создаю список цветов, длина которого равна рандомному значению count_animals
count = count_animals
animal_colors = []
color_step_1 = ((light_green_rgb[0]-light_blue_rgb[0]) / (count-1))
color_step_2 = ((light_green_rgb[1]-light_blue_rgb[1]) / (count-1))
color_step_3 = ((light_green_rgb[2]-light_blue_rgb[2]) / (count-1))

for i in range(count):
    r = int(light_blue_rgb[0] + color_step_1 * i)
    g = int(light_blue_rgb[1] + color_step_2 * i)
    b = int(light_blue_rgb[2] + color_step_3 * i)
    rgb = f'rgb({r}, {g}, {b})'
    animal_colors.append(rgb)

fig = go.Figure()
for i in range(0, count_animals):
    fig.add_trace(go.Box(y=lists[i], quartilemethod="linear", name=animals_list[i], marker_color = animal_colors[i]))

fig.update_layout(height = 400, width = 800,
                  title_text=(f'Boxplot распределения значений вымышленного параметра для {count_animals} видов животных'), 
                              title_font=dict(size=15, color = dark_blue), title_y=0.97,
                      paper_bgcolor=neutral_white, plot_bgcolor=neutral_white,
                      margin = dict(t=30, l=10, r=10, b=10), xaxis_tickfont_size=10, yaxis_tickfont_size=10,
                      legend_font=dict(size=10))
fig.show()

Результат выполнения вышеописанного кода в зависимости от значения count_animals может выглядеть так:

Box Plot для шести видов животных
Box Plot для шести видов животных

Или так:

Box Plot для 17 видов животных
Box Plot для 17 видов животных

Такой генератор градиентов можно использовать и в утилитарных целях. Например, если есть еще одно измерение, по которому можно ранжировать наши сущности и тогда цвет позволит визуализировать еще один параметр.

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

import random
#создаем список значений нового параметра
numbers = [random.randint(20, 100) for _ in range(20)]
#создаю DataFrame из всез имеющихся значений
df = pd.DataFrame(animals_list, columns = ['animal_name'])
#добавляю столбец со списком значений для Box Plot
df['values'] = lists 
#добавляю параметр, по которому будем ранжировать DataFrame
df['ranking_parameter'] = numbers
#сразу ранжирую по столбцу 'ranking_parameter'
df = df.sort_values(by = 'ranking_parameter').reset_index()
Получившийся фрейм
Получившийся фрейм

Допустим, что в нашей вымышленной вселенной чем выше значение в столбце 'ranking_parameter', тем лучше, чем ниже, тем хуже и соответственно, это животное требует особого внимания.

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

red = '#f24924'
red_rgb = [242, 73, 36]

green = '#32cd32'
green_rgb = [50, 205, 50]

Код вцелом не очень отличается от предыдущего, разница только в том, что теперь я «тяну» данные из DataFrame, а не из списков. И, естественно, изменены имена переменных, которые хранят первый и последний цвет градиента.

count_animals = random.randint(1, 20)

count = count_animals
animal_colors = []
color_step_1 = ((green_rgb[0]-red_rgb[0]) / (count-1))
color_step_2 = ((green_rgb[1]-red_rgb[1]) / (count-1))
color_step_3 = ((green_rgb[2]-red_rgb[2]) / (count-1))

for i in range(count):
    r = int(red_rgb[0] + color_step_1 * i)
    g = int(red_rgb[1] + color_step_2 * i)
    b = int(red_rgb[2] + color_step_3 * i)
    rgb = f'rgb({r}, {g}, {b})'
    animal_colors.append(rgb)

fig = go.Figure()

for i in range(0, count_animals):
    fig.add_trace(go.Box(y=df.iloc[i]['values'], quartilemethod="linear", name=df.iloc[i]['animal_name'], marker_color = animal_colors[i]))
fig.update_layout(height = 400, width = 800,
                  title_text=(f'Boxplot распределения значений вымышленного параметра для {count_animals} видов животных'), 
                              title_font=dict(size=15, color = dark_blue), title_y=0.97,
                      paper_bgcolor=neutral_white, plot_bgcolor=neutral_white,
                      margin = dict(t=30, l=10, r=10, b=10), xaxis_tickfont_size=10, yaxis_tickfont_size=10,
                      legend_font=dict(size=10))
fig.show()

В результате запуска кода должна получиться такая картина:

Такой подход дает возможность не только увидеть распределение данных по параметру_1, но и цветом визуально разделить наши сущности по параметру_2. При этом, цвет маркера первого и последнего животного всегда одинаковый, вне зависимости от количества животных, данные для которых визуализируются.

На сегодня все)
Спасибо, что дочитали до конца.

Теги:
Хабы:
Всего голосов 4: ↑4 и ↓0+4
Комментарии4

Публикации

Работа

Data Scientist
49 вакансий

Ближайшие события