Читать первым в Telegram-канале «Код Дурова»
Разбираем, зачем нужна визуализация, её принципы, виды и использование диаграмм — с примерами кода графиков на Python, а также главных ошибок начинающих специалистов.
В этом нам поможет Ольга Матушевич, наставница на курсе «Аналитик данных» и Мастерской в Практикуме.
Зачем визуализировать данные
Представим, что начинающему аналитику дают задачу — рассчитать конверсию за последнюю неделю по устройствам и источникам трафика. Специалист быстро выполняет расчёты с помощью Python и представляет результат в виде таблицы.
Казалось бы, задача решена верно, в срок и безо всяких визуализаций. Но проблема в том, что работать с такой таблицей очень сложно. Что бы ни захотел узнать с её помощью руководитель — например, какое сочетание источника трафика и устройства даёт наилучшую конверсию, — ему придётся потратить на это много времени.
Намного удобней было бы работать с тепловой картой той же таблицы. Превратить её в тепловую карту можно несколькими строками кода:
import seaborn as sns
import matplotlib.pyplot as plt
# Создание тепловой карты с использованием модуля Seaborn
plt.figure(figsize=(10, 6))
sns.heatmap(df.set_index('Источник трафика'), annot=True)
plt.title('Тепловая карта конверсии по источникам трафика и устройствам')
plt.show()
Теперь руководитель может легко найти самую лучшую конверсию: это самое светлое пятно на тепловой карте — сочетание мобильных на Android и контекстной рекламы. Попробуем сделать и другие таблицы не только верными, но и удобными в работе — даже если при этом они перестанут быть таблицами.
Погрузитесь в сферу на курсе «Аналитик данных» — начать учиться можно бесплатно. Во вводном модуле выясните причину массовой поломки гаджетов на фабрике робокотов и поймёте, хотите ли развиваться в этой сфере дальше.
Основные принципы визуализации
Чтобы визуализация аналитических данных приносила пользу, при составлении нужно придерживаться главных принципов:
Логика
Включает в себя постановку цели и тип визуализации, правильную сортировку, единообразие графиков и т. д. Разберём подробнее каждый пункт:
- Цель. Прежде чем формировать график или диаграмму, важно понимать, что именно нужно показать. Стоит сформулировать конкретную задачу: главная ошибка — делать картинку ради картинки.
- Сортировка. Данные должны быть расположены в логическом порядке в зависимости от цели: от большего к меньшему или наоборот.
- Подходящий тип визуализации. Задача аналитика — упростить восприятие информации: например, если показателей слишком много или если общее количество не равно 100%, круговая диаграмма не подойдёт. Ниже обсудим выбор типа графика подробней.
- Корректное название. Признак хорошего графика — он понятен без поясняющих материалов, разбора кода и изучения истории компании. К теме названий графиков ещё вернёмся.
- Соответствующая легенда. Чаще всего ненужная легенда появляется из заимствованного кода, который аналитик пытался приспособить под свои нужды. Если на графике всего одна переменная, стоит удалить легенду — она тут явно лишняя.
- Единообразные графики. При сравнении стоит использовать графики одного типа: пользователь запутается, если «до» представлено в виде линейной диаграммы, а «после» — в виде столбчатой.
- Правильные подписи. Подписывать данные нужно непосредственно на графике: так мозг воспринимает картину целиком и быстрее делает выводы по диаграммам.
import matplotlib.pyplot as plt
# Генерация случайных данных для прибыли компании за 5 лет
profit = [1.5, 1.9, 2.7, 2.2, 2.8]
# Создание графика
fig, ax2 = plt.subplots(figsize=(6, 4))
# Настройка графика
ax2.plot(profit, color='darkgreen')
ax2.set_title('Динамика прибыли компании за 2016–2020 гг. (млн рублей)')
ax2.set_xlabel('Год')
ax2.set_ylabel('Прибыль (млн рублей)')
ax2.set_xticks(range(len(profit)))
ax2.set_xticklabels(['2016', '2017', '2018', '2019', '2020'])
# Искусственное завышение диапазона оси OY от 1.3 до 3 млн
# Если этого не сделать – данные по последнему столбцу будут выше
# границы графика
ax2.set_ylim(1.3, 3)
# Добавление значений прибыли на графике
for i, txt in enumerate(profit):
ax2.annotate(f'{txt:.1f}', (i, profit[i]), textcoords="offset points", xytext=(0,10), ha='center')
plt.tight_layout()
plt.show()
Лаконичность
Чем проще и минималистичней график, тем лучше. Это относится к дизайну, подписям и структуре — не стоит использовать один график для нескольких задач сразу:
- Минимум элементов. При визуализации стоит убирать лишнюю информацию, логотипы, даты, визуальный шум. Чем проще график — тем быстрее отображаемая информация доходит до мозга.
- Лаконичный дизайн. Например, не стоит использовать 3D-эффекты: они отвлекают внимание от информации и не решают задачу.
- Одна задача — один график. Не нужно пытаться с помощью одного графика представить всю информацию о компании за год.
Цвет
Цвет — важная составляющая визуализации. При выборе цвета для графиков стоит ориентироваться на:
- Общепринятые цвета. «Правильно» выделяется зелёным, «неправильно» — красным. При работе с иностранными компаниями стоит заранее изучить, какие общепринятые цвета используют, — они могут отличаться.
- Единую цветовую гамму. Если один график в сером цвете, а второй — в ярком, он перетянет на себя внимание и покажется более значимым.
- Контрастные цвета. Часто в диаграммах по умолчанию используют зелёный, оранжевый и синий. С ними получаются не очень стильные, зато понятные графики.
Виды диаграмм
Аналитики используют более 60 способов визуализации данных: различные виды диаграмм, графиков и карт. Рассмотрим наиболее популярные техники и варианты визуализации данных — а также опишем, для каких целей они больше подходят.
Линейный график — Line Chart
Часто его же называют просто График — Plot. Идеально подходит для демонстрации измерения данных во времени, количественного состояния, развития данных и соотношения или взаимосвязи между данными. Может отображать сразу несколько переменных.
Изначально этот тип визуализации использовался, когда данные получали редко — например, раз в три часа, как на графике выше, — но при этом нужно было хотя бы примерно оценить, что же происходит в периоды, о которых нет точных данных. Самый простой способ это сделать — соединить две ближайшие точки прямой линией: просто и эффективно. Сейчас некоторые данные — например, посещаемость сайта — можно получать чуть ли не с точностью до минуты: тогда непрерывная линия получается из полностью известных данных.
Круговая диаграмма — Pie chart
Этот способ визуализации подходит для демонстрации частей целого. Идеально, если нужно показать одну или две части от общего целого, например долю компании на рынке. Если сумма изображаемых частей отличается от 100%, эта диаграмма не подойдет. Также её лучше не использовать, если показателей больше пяти, — исчезает наглядность.
Столбчатая диаграмма — Bar Chart
Это графическое представление данных в виде прямоугольных столбцов, где длина каждого столбца пропорциональна значению, которое он представляет. Столбцы можно менять местами без потери смысла. Подходит для точного сравнения и демонстрации зависимости данных. Идеальна для точного сравнения категорийных данных, когда показателей больше пяти.
Горизонтальная столбчатая диаграмма — Horizontal Bar Chart
Отличие от обычной столбчатой диаграммы — на горизонтальной можно удобно разместить длинные подписи данных — как на иллюстрации ниже: диаграммы помогают определить, на какой точке каждый фрукт продаётся лучше.
import pandas as pd
import matplotlib.pyplot as plt
# Генерируем данные для графика
fruits = ['Яблоки', 'Бананы', 'Груши', 'Апельсины', 'Ананасы', 'Манго', 'Малина', 'Клубника', 'Виноград']
sales_store1 = [15, 20, 12, 18, 14, 25, 10, 16, 11]
sales_store2 = [12, 18, 15, 16, 13, 22, 9, 14, 10]
df = pd.DataFrame({'Фрукт': fruits, 'Магазин 1': sales_store1, 'Магазин 2': sales_store2})
# Сортировка данных по продажам в Магазине 1
df_sorted = df.sort_values(by='Магазин 1', ascending=False)
# Создание сабплота
fig, axs = plt.subplots(1, 2, figsize=(12, 6))
# График типа bar на левом поле
df_sorted.plot(kind='bar', x='Фрукт', y=['Магазин 1', 'Магазин 2'], ax=axs[0])
axs[0].set_ylabel('Продажи (тонны)')
# Убираем не нужную подпись оси "Фрукты"
axs[0].set_xlabel('')
axs[0].set_title('Продажи фруктов на столбчатой диаграмме')
# График типа barh на правом поле
df_sorted.plot(kind='barh', x='Фрукт', y=['Магазин 1', 'Магазин 2'], ax=axs[1])
axs[1].set_xlabel('Продажи (тонны)')
# Убираем не нужную подпись оси "Фрукты"
axs[1].set_ylabel('')
axs[1].set_title('Продажи фруктов на горизонтальной столбчатой диаграмме')
plt.tight_layout()
plt.show()
Столбчатая диаграмма с накоплением — Stacked Column Chart
Каждый столбец такой диаграммы состоит из нескольких сегментов, которые представляют разные подкатегории или составляющие части данных. Это помогает оценить как общий результат, так и вклад в него каждого отдельного компонента. Для создания такой диаграммы должно быть разбиение по двум категориям. Например, на иллюстрации выше было разбиение по товарам и магазинам: но если бы магазин был один, столбчатую диаграмму с накоплением построить бы не получилось.
import pandas as pd
import matplotlib.pyplot as plt
# Генерируем данные для графика
fruits = ['Яблоки', 'Бананы', 'Груши', 'Апельсины', 'Ананасы', 'Манго', 'Малина', 'Клубника', 'Виноград']
sales_store1 = [15, 20, 12, 18, 14, 25, 10, 16, 11]
sales_store2 = [12, 18, 15, 16, 13, 22, 9, 14, 10]
# Сложение списков продаж для обоих магазинов
total_sales = np.array(sales_store1) + np.array(sales_store2)
df = pd.DataFrame({'Фрукт': fruits, 'Магазин 1': sales_store1, 'Магазин 2': sales_store2, 'Всего': total_sales})
# Сортировка данных по столбцу 'Всего' от большего к меньшему
df_sorted = df.sort_values(by='Всего', ascending=False)
# Построение горизонтальной столбчатой диаграммы с накоплением
df_sorted.set_index('Фрукт')[['Магазин 1', 'Магазин 2']].plot(kind='barh', stacked=True)
plt.title('Продажи фруктов в магазинах с накоплением (от большего к меньшему)')
# Убираем не нужную подпись оси "Фрукты"
plt.ylabel('')
plt.xlabel('Продажи')
plt.legend(title='Магазины')
plt.show()
Нормированная столбчатая диаграмма — Normalized Stacked Bar Chart
В отличие от обычной столбчатой диаграммы с накоплением, где высота столбца представляет сумму значений всех категорий, в нормированной столбчатой диаграмме каждый столбец имеет высоту 100% и отображает долю каждой подкатегории от общего значения. Это помогает увидеть структуру данных: такая диаграмма ничего не скажет об общей сумме продаж, но покажет, что и где продаётся лучше.
import pandas as pd
import matplotlib.pyplot as plt
# Генерируем данные для графика
fruits = ['Яблоки', 'Бананы', 'Груши', 'Апельсины', 'Ананасы', 'Манго', 'Малина', 'Клубника', 'Виноград']
sales_store1 = [15, 20, 12, 18, 14, 25, 10, 16, 11]
sales_store2 = [12, 18, 15, 16, 13, 22, 9, 14, 10]
df = pd.DataFrame({'Фрукт': fruits, 'Магазин 1': sales_store1, 'Магазин 2': sales_store2})
# Создаем сабплот
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(15, 6))
# Левая часть: нормированная диаграмма сортированных продаж по доле продаж в магазине 1
# Вычисляем общую сумму продаж каждого фрукта в обоих магазинах
total_sales = df['Магазин 1'] + df['Магазин 2']
# Нормализуем данные
normalized_sales_store1 = df['Магазин 1'] / total_sales
normalized_sales_store2 = df['Магазин 2'] / total_sales
# Сортируем данные по доле продаж в магазине 1
df_sorted_by_normalized_store1 = df.iloc[normalized_sales_store1.sort_values(ascending=False).index]
axes[0].barh(df_sorted_by_normalized_store1['Фрукт'], normalized_sales_store1[df_sorted_by_normalized_store1.index], label='Магазин 1')
axes[0].barh(df_sorted_by_normalized_store1['Фрукт'], normalized_sales_store2[df_sorted_by_normalized_store1.index], left=normalized_sales_store1[df_sorted_by_normalized_store1.index], label='Магазин 2')
axes[0].set_title('Доля продаж каждого фрукта в обоих магазинах')
axes[0].set_xlabel('Доля продаж')
axes[0].set_ylabel('')
axes[0].legend()
# Правая часть: структура продаж в каждом магазине
# Создаем DataFrame df1
df1 = df.drop('Всего', axis=1).T
# Устанавливаем название колонки "магазин"
df1.index.name = 'магазин'
# Переименовываем столбцы в соответствии с названиями фруктов
df1.columns = df1.iloc[0]
# Удаляем строку с названиями фруктов, так как они стали названиями столбцов
df1 = df1.drop('Фрукт')
# Нормализуем данные для каждого фрукта
df1_normalized = df1.div(df1.sum(axis=1), axis=0)
# Построение нормированной горизонтальной столбчатой диаграммы с накоплением
df1_normalized.plot(kind='barh', stacked=True, figsize=(10, 6), ax=axes[1])
axes[1].set_title('Структура продаж в магазинах')
axes[1].set_xlabel('Доля продаж')
axes[1].set_ylabel('')
axes[1].legend(title='Фрукты', bbox_to_anchor=(1.05, 1), loc='upper left')
# Добавление воздуха между диаграммами — иначе один график наползёт на подписи к данным другого
plt.subplots_adjust(wspace=0.5)
plt.show()
Специальные виды диаграмм
Рассмотрим несколько более редких видов диаграмм. Они не входят в стандартный базовый набор, но, тем не менее, очень полезны для аналитика данных.
Тепловая карта — Heat Map
Графическое представление данных с помощью разных цветов. Например, более активные области отображаются более светлыми цветами, а менее активные — тёмными. Возможны и другие цветовые шкалы. Этот тип диаграммы лучше всего использовать, когда данных много, но их можно представить в виде таблицы или разместить на карте. Диаграммы могут выглядеть как реальные географические карты — как на иллюстрации ниже — или как раскрашенные таблицы.
Гистограмма — Histogram
Визуальное представление распределения значений данных, где высота каждого столбца соответствует частоте возникновения определённого значения или диапазона значений. Похожа на столбчатую диаграмму, но тут между столбцами почти никогда нет расстояний. В этом графике по оси OY всегда будет частота. Гистограмма поможет понять, нормальное распределение или нет, предположить значение его моды, увидеть выбросы. Именно с этого типа графиков часто начинается исследовательский анализ данных. Пример гистограммы — на рисунке ниже.
Ящик с усами — Boxplot
Этот график позволяет очень компактно и наглядно представлять порядковые статистики одномерного закона, такие как квартили, медиана, наблюдаемые минимальное и максимальное значения выборки, а также отображать выбросы. Как и у гистограммы, на одной из осей расположена частота, поэтому её можно не подписывать. Этот тип графика часто используется в исследовательском анализе вместе с гистограммой или вместо неё.
Точечная диаграмма — Scatter Plot
На точечной диаграмме каждая точка представляет собой одно наблюдение и показывает значение одной переменной в зависимости от значения другой переменной. Используется для исследования силы и направления связи между двумя непрерывными переменными, то есть для понимания, есть ли между переменными корреляция. Другое распространённое применение — визуализация разделения на кластеры в машинном обучении.
Диаграмма «плоское дерево» — Flat Tree Diagram
Это графическое представление иерархии данных в виде дерева, где каждый узел представляет собой категорию или переменную, а ветви — связи между ними. Диаграмма подходит для выявления важности отдельных компонентов целого. Для этого используется не только высота — как в столбчатой диаграмме с накоплением, — но и ширина. За счёт выхода во второе измерение диаграмме «плоское дерево» удаётся компактно показывать многосоставные и неоднородные структуры данных.
Ошибки начинающих аналитиков
Одна из наиболее частых ошибок новичков — неудачные названия для диаграммы и неясные подписи осей. Разберём эти ошибки подробней:
- Отсутствие названия. На рисунке ниже — типичный пример графика без названия. Что это — связь количества отработанных часов и заработка в час, среднего чека и количества покупателей в магазине, потраченных денег на уроки игры в гольф и результатов? Без подписи установить, что показывает график, невозможно.
- Слишком общее название. Названия вроде «График 1», «Диаграмма 2» тоже нельзя назвать удачными. Ни на один вопрос, заданный выше, они не ответили. Это может быть полезное начало названия — особенно если в работе специалиста много графиков, — но его необходимо продолжить.
Уже лучше выглядят названия: «Связь количества отработанных часов и заработка в час», «Связь среднего чека и количества покупателей в магазинах», «Связь потраченных денег на уроки игры в гольф и результатов». Но они также не отвечают на все вопросы. Разберём второй пример — «Связь среднего чека и количества покупателей в магазинах». Сразу возникают дополнительные вопросы: «В магазинах какой сети? За какой период?» Оптимальный вариант названия: «Связь среднего чека и количества покупателей в магазинах сети “Х” (данные за октябрь 2021 года)».
- Нет подписей осей. Снова возникают вопросы: «А где тут что? Где средний чек, а где количество покупателей?» Пока нет подписей осей, их можно расставить произвольно и сильно ошибиться в выводах.
- Не указаны единицы измерения. В чём измеряются покупатели? Это количество человек? Или тысяч человек? Или сотен человек? А чек — это рубли или доллары? Пока всё это не указано, ошибки неизбежны.
- Использованы два языка при оформлении диаграмм. Часто можно увидеть диаграммы с названием на русском языке и с подписями осей на английском. Причина в том, что Рython может использовать названия столбцов при оформлении диаграммы, если иное не задано вручную. Такие диаграммы необходимо дорабатывать.
В аналитике данных много направлений. Если вам интересна эта сфера, но пока больше вопросов и неуверенности, чем ответов, — попробуйте бесплатный курс «Какую профессию выбрать в анализе данных».
Рекомендация для начинающих специалистов
Любую увиденную диаграмму нужно стараться изучить и найти ответы на вопросы:
- Понятна ли эта диаграмма? Какие вопросы по данным она оставила без ответа? Какие изменения можно внести, чтобы диаграмма отвечала на эти вопросы?
- Какие ошибки допущены при составлении этой диаграммы?
- Что нравится в этой диаграмме? Что из этого можно использовать в своей работе в будущем?
При таком подходе любая статья о данных превратится в обучающее пособие по визуализации.