{
library(tidyverse) # Основная библиотека в экосистеме
library(showtext) # Рендер текста
library(ggtext) # Рендер текста
library(sysfonts) # Загрузка шрифтов
library(ggridges) # Красивые геометрии
library(formattable) # Для визуализации таблички
library(viridis) # Цветовые палитры
library(rcartocolor) # Цветовые палитры
# Для работы с картами:
library(sf)
library(terra)
library(tidyterra)
showtext_auto()
# Загружаем шрифт:
GET('https://github.com/ETymch/Econometrics_2023/raw/main/Plotting/HSESans-Regular.otf', write_disk('HSESans-Regular.otf', overwrite = T))
GET('https://github.com/ETymch/Econometrics_2023/raw/main/Plotting/HSESans-Bold.otf', write_disk('HSESans-Bold.otf', overwrite = T))
GET('https://github.com/ETymch/Econometrics_2023/raw/main/Plotting/HSESans-Italic.otf', write_disk('HSESans-Italic.otf', overwrite = T))
GET('https://github.com/ETymch/Econometrics_2023/raw/main/Plotting/HSESans-SemiBold.otf', write_disk('HSESans-SemiBlod.otf', overwrite = T))
font_add(family = 'HSE Sans',
regular = "HSESans-Regular.otf",
bold = 'HSESans-Bold.otf',
italic = 'HSESans-Italic.otf',
bolditalic = 'HSESans-SemiBlod.otf'
)
}Пиво и карты
Визуализация данных - творческий процесс
Многие считают, что датавиз очень похож на рисование, но я бы больше сравнил это с конструктором LEGO. Каждый график - комбинация слоёв, геометрий и декоративных элементов тем. Но если нет материалов (данных), то каким бы замечательным визуализатором вы ни были, ничего не выйдет.
Сегодня мы разберём технические особенности визуализации в ggplot2 на данных об алкогольных предпочтениях россиян.

Гистограммы малоинформативны

Почему?
- Они не рассказывают распределении данных в выборке.
- Это может запутать вас и читателя.
В идеале каждый график рассказывает какую-то историю. Наверняка в ваших любимых книгах есть не один слой смысла а несколько. Поэтому вы их и любите. В приведённой столбиковой диаграмме слой смысла ровно один поэтому в современной аналитике подача информации может быть менее примитивной. В конце концов, многие восхищаются иллюстрациями журнала The Economist или Bloomberg, но когда дело доходит до собственных визуализаций всё деградирует до столбиков с односложным посылом.
Делаем график про алкогольные предпочтения
Загрузка библиотек и шрифтов
Загрузка данных
Моя большая признательность за данные проекту RLMS HSE. В рамках добросовестного использования прилагаю ссылку на источник: Российский мониторинг экономического положения и здоровья населения НИУ ВШЭ (RLMS HSE)», проводимый Национальным исследовательским университетом “Высшая школа экономики” и ООО «Демоскоп» при участии Центра народонаселения Университета Северной Каролины в Чапел Хилле и Института социологии Федерального научно-исследовательского социологического центра РАН. (Сайты обследования RLMS HSE: http://www.hse.ru/rlms и https://rlms-hse.cpc.unc.edu).
Как выглядит табличка?

Сначала всегда точки: geom_point
Ящик с усами: geom_boxplot
Разбросанные точки: geom_jitter
Прозрачность: alpha = [0,1]
Можно раскрасить точки
В шапке добавим aes(color =Какая-то переменная)`.
Другие геометрии
Функции плотности распределения: ggridges.
Палитры viridis
Темы для графиков
Темы для графиков
Шрифты и размеры текста
Единый шрифт добавляется при помощи включения base_family, base_size в настройках темы. Шрифты загружаются и рисуются при помощи библиотек sysfonts, showtext и ggtext. Последняя добавляет html инструменты для управления шрифтами.
Можно создать свою тему
Или изменить уже существующую: +theme(). Также я изменил формат легенды при помощи guides().
Можно добавить текст
Для удобства создадим отдельную табличку anno с данными для аннотации.
anno <- df %>% count(Напиток) # Табличка
plot <- plot +
annotate('text', # Аннотация может быть не только текстом, но и линией, линией со стрелкой, прямоугольником и многим другим.
x = 95, y = anno$Напиток, # Координаты аннотаций.
label = paste0('n = ', anno$Напиток), # формат аннотации
vjust = -0.8) # Сдвинуть по вертикали вниз на 0.8
plotЗаголовки
Добавляем заголовки labs, модифицируем тему. Привожу полную версию кода.
df %>%
ggplot(aes(x = Возраст, y = Напиток, color = Возраст)) +
geom_point(position=position_jitterdodge(0.2), alpha = 0.15) +
ggridges::geom_density_ridges(fill = '#442F3D', quantile_lines = T, quantiles = 2, color = 'grey20', linewidth = 0.5, alpha = 0.2) +
annotate('text', x = 95, y = anno$Напиток, label = paste0('n = ', anno$Напиток), vjust = -0.8) +
theme_minimal(base_family = 'HSE Sans', base_size = 14) +
theme(legend.position = 'bottom',
legend.margin=margin(t = -0.2, unit='cm'),
panel.grid.minor.x = element_blank(),
panel.grid.minor.y = element_blank(),
panel.grid.major.y = element_blank(),
axis.title.y = element_blank(),
legend.title = element_blank(),
plot.title.position = 'plot',
plot.caption = element_text(size = 7, face = 'italic'),
plot.subtitle = element_markdown(hjust = 0.0, size = 13),
plot.title = element_markdown(hjust = 0.0,
size = 18, face = 'bold')
) +
guides(color = guide_colorbar(barwidth = 12, barheight = 0.5)) +
scale_fill_carto_c(palette = "TealRose", direction = -1) +
scale_color_carto_c(palette = "TealRose", direction = -1) +
labs(x = 'Возраст, лет',
title = 'Каждому возрасту - свой напиток',
subtitle = "<span style = 'color:#D98994;'>Более молодые</span> предпочитают коктейли или пиво,<br> <span style = 'color:#009392;'> познавшие жизнь </span>- водку, самогон и вино.",
caption = 'Данные: Российский мониторинг экономического положения и здоровья населения НИУ ВШЭ (RLMS HSE), http://www.hse.ru/rlms')Также цветом можно выделить пол
Мне эта версия нравится даже больше, потому что информация о возрасте не дублируется.
df %>%
ggplot(aes(x = Возраст, y = Напиток, color = factor(Пол, levels = c(1,2)))) +
geom_point(position=position_jitterdodge(0.5), alpha = 0.25) +
ggridges::geom_density_ridges(fill = '#442F3D', quantile_lines = T, quantiles = 2, color = 'grey20', linewidth = 0.5, alpha = 0.15) +
annotate('text', x = 95, y = anno$Напиток, label = paste0('n = ', anno$Напиток), vjust = -0.8) +
theme_minimal(base_family = 'HSE Sans', base_size = 14) +
theme(legend.position = 'none',
legend.margin=margin(t = -0.2, unit='cm'),
panel.grid.minor.x = element_blank(),
panel.grid.minor.y = element_blank(),
panel.grid.major.y = element_blank(),
axis.title.y = element_blank(),
legend.title = element_blank(),
plot.title.position = 'plot',
plot.caption = element_text(size = 7, face = 'italic'),
plot.subtitle = element_markdown(hjust = 0.0, size = 13),
plot.title = element_markdown(hjust = 0.0,
size = 18, face = 'bold')
) +
scale_fill_manual(values = c('#E39921', '#E482B5')) +
scale_color_manual(values = c('#E39921', '#E482B5')) +
labs(x = 'Возраст, лет',
title = 'Каждому - свой напиток',
subtitle = "<span style = 'color:#E482B5;'>Женщины </span>заметно чаще <span style = 'color:#E39921;'>мужчин</span> выбирают вино и коктейли.<br><span style = 'color:#E482B5;'>Женщины</span> начинают употреблять самогон в более позднем возрасте, чем <span style = 'color:#E39921;'>мужчины</span>.",
caption = 'Данные: Российский мониторинг экономического положения и здоровья населения НИУ ВШЭ (RLMS HSE), http://www.hse.ru/rlms')Что предлагает ИИ? phi4 от Microsoft.
Я попросил языковую модель предложить свой вариант визуализации. Её код выглядел так:
df %>%
mutate(age_group = ifelse(Возраст < 40, "<40", ">=40")) %>%
mutate(Пол = ifelse(Пол == 1, 'Мужчины', 'Женщины')) %>%
ggplot(aes(x = Напиток, fill = factor(Пол, levels = c('Мужчины', 'Женщины')))) +
geom_bar(position = position_dodge(), stat = 'count') +
facet_wrap(~age_group) +
theme_minimal() +
scale_fill_manual(values = c('blue', 'pink')) + # неплохое сочетание цветов
labs(title = 'Распределение предпочпочтений по возрастным группам', # Да, именно предпочпочтений😊
x = "Напиток",
y = "Кол-во людей")Довольно средне, не правда ли? Зная ggplot2 за минуту этот график можно улучшить для более приятной версии.
После ручных правок
df %>%
mutate(age_group = ifelse(Возраст < 40, "Возраст < 40 лет", " Возраст >= 40 лет") %>% factor(levels = c("Возраст < 40 лет", " Возраст >= 40 лет"))) %>%
mutate(Пол = ifelse(Пол == 1, 'Мужчины', 'Женщины')) %>%
ggplot(aes(x = Напиток, fill = factor(Пол, levels = c('Мужчины', 'Женщины')))) +
geom_bar(position = position_dodge(), stat = 'count', alpha = 0.9) +
facet_wrap(~age_group) + # отличная находка phi4
theme_classic(base_family = 'HSE Sans', base_size = 14) + # Такая тема мне нравится больше
scale_fill_manual(values = c('blue', 'pink')) +
labs(title = 'Распределение предпочтений по возрастным группам',
x = "Напиток",
y = "Кол-во людей") +
coord_flip() + # так намного лучше
theme(legend.position = 'bottom',
legend.title = element_blank(),
axis.title.y = element_blank(),
plot.title.position = 'plot',
plot.title = element_markdown(hjust = 0.0,
size = 16, face = 'bold'))Мне нравится, что phi4 предложил разбить график на две клетки, позволил продемонстрировать замечательную команду facet_wrap(), которая позволяет создавать композиции из нескольких графиков.
Плюсы:
В ванильной ggplot2 около 70 геометрий. Если добавить геометрии из модификаций, то счёт идёт на сотни. Есть также могучая библиотека
ggforceи многие другие, которые расширяют набор геометрий.Можно делать свои шаблоны для графиков.
Удобно автоматизировать.
Можно строить карты.
Можно делать анимации, но их применение в аналитике ограничено.
Композиции графиков
Библиотека
patchwork.Соединение графиков в ансамбль в одну строчку:
plot_1 + plot_2Есть более простой способ: добавить
+facet_wrapвggplot.
Пример композиции
Мне очень нравится эта композиция из графиков, потому что эта композиция рассказывает монолитную историю. Как сделать такой график я показывал здесь.
Карты городов
Можно построить карту Москвы на кадастровых данных, которые в своём обучающем материале собрал Марсель Салихов. Данные, возможно, опубликую в будущем. В основе визуализации библиотеки sf и tidyterra.
kadastr %>% # Табличка
ggplot() + # Строим график
geom_sf(aes(fill = `Объект культурного наследия`, color = `Объект культурного наследия`)) + # слой полигонов
geom_sf(data = kadastr_na, color = '#4D483E', size = 1, alpha =0.9) + # слой точек для домов, которые не измерены
scale_color_manual(values = c('#D05546', '#4D483E')) + # Цветовая шкала color - это, как правило, про контуры объекта
scale_fill_manual(values = c('#D05546', '#4D483E')) + # Шкала заливки, fill - как правило, про внутреннюю заливку объекта.
theme_void(base_family = 'HSE Sans') + # Пустая тема
theme(legend.position = 'bottom',
text = element_text(colour = "white"),
plot.background = element_rect(fill = 'black', color = '#D05546'),
legend.margin = margin(-20,0,0,0, 'pt'),
plot.margin = margin(-16, -16, 0, -16, "pt")
)Заключение
Сегодня мы рассмотрели несколько мотивирующих примеров визуализации в ggplot2. Строить графики - не сложно, в большинстве случаев можно уложиться в несколько строчек кода. Современные языки программирования, такие как R, достаточно просты в освоении, а код на них легко читается и выглядит лаконично. С помощью кода и инструментов парсинга можно автоматизировать построение графиков для регулярных отчётов или углубить понимание данных при помощи комбинирования геометрий и игры с цветом. Спасибо за чтение, приятного путешествия в мир визуализации данных.
