Идея
Сегодня я хочу показать:
- Как работать с необычными геометриями,
- Текстовыми аннотациями,
- Как удобно и просто комбинировать графики, чтобы графики вместе рассказывали историю.
Для этого мы будем использовать данные платёжного баланса об экспорте Россией услуг по транспортировке грузов различными путями. Мы хотим рассказать, что произошло с экспортом этих услуг в 2022 году. А именно:
- Рассказать о том, как исторически развивался экспорт Россией этих услуг.
- Показать, насколько он снизился. Сделать на этом смысловой акцент на графике.
- Показать какие виды услуг по транспортировке грузов снизились больше прочих.
Все эти цели будут определять то, какие графики мы будем строить и как именно они будут скомбинированы в итоговом графике. Таким образом, чтобы показать изменение в 2022 г. нужно отдельно выделить 2021 (мирный) год и 2022 год, сделать акцент на разнице между ними. Также я считаю, что необходим ретроспективный график с большим горизонтом, чтобы читатель графика понимал, как примерно всё развивалось до 2021 г.
Мы наметили план той истории, которую хотим сообщить. Теперь приступим к технической части!
Подготовка рабочего пространства
Библиотеки
library(tidyverse) # Обработка данных
library(readxl) # Работа с excel файлами в R
library(showtext) # Рендер текста в ggplot2
library(sysfonts) # загрузка шрифтов в R
library(httr) # Скачать файлы из сети
library(patchwork) # комбинирование графиков
showtext_auto() # рендер текста в ggplot при помощи showtext
Давайте на этот раз загрузим все стили шрифта HSE Sans. Сначала я скачиваю шрифт с github при помощи библиотеки hhtr
, затем устанавливаю через sysfonts
.
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'
) # Установка HSE Sans, вместо bolditalic я загружаю полужирный.
Загрузим сырые данные Банка России из платёжного баланса за 2021 и 2022 гг. Выберем то, что нам нужно.
Выгрузка данных
GET('https://github.com/ETymch/Econometrics_2023/raw/main/Datasets/difference.xls', write_disk('difference.xls', overwrite = T))
## Response [https://raw.githubusercontent.com/ETymch/Econometrics_2023/main/Datasets/difference.xls]
## Date: 2023-09-21 13:06
## Status: 200
## Content-Type: application/octet-stream
## Size: 58.9 kB
## <ON DISK> C:\Users\evgen\econometrics_2023\Econ_site\content\gg\Tutorial_2\difference.xls
data <- read_excel('difference.xls', skip = 4)[c(29,41,68,80,86),2:3] %>%
mutate(cat = c('Морской', 'Воздушный', 'ЖД', 'Авто', "Трубопроводный"),
Diff = `2022` - `2021`)
data
## # A tibble: 5 × 4
## `2021` `2022` cat Diff
## <dbl> <dbl> <chr> <dbl>
## 1 1595. 1436. Морской -159.
## 2 3099. 919. Воздушный -2180.
## 3 1232. 951. ЖД -282.
## 4 1154. 1162. Авто 7.58
## 5 1923. 1810. Трубопроводный -114.
Посмотрите на табличку data. Часто мы визуализируем это столбиками. Я покажу, как это можно сделать в следующих постах. Сегодня давайте воспользуемся продвинутыми возможностями визуализации в R, сделаем графики-бублики!
Для третьего графика нам понадобятся данные о совокупном экспорте услуг по перевозке грузов в млрд долл.
GET('https://github.com/ETymch/Econometrics_2023/raw/main/Datasets/totals.xlsx', write_disk('totals.xlsx', overwrite = T))
## Response [https://raw.githubusercontent.com/ETymch/Econometrics_2023/main/Datasets/totals.xlsx]
## Date: 2023-09-21 13:06
## Status: 200
## Content-Type: application/octet-stream
## Size: 11.2 kB
## <ON DISK> C:\Users\evgen\econometrics_2023\Econ_site\content\gg\Tutorial_2\totals.xlsx
totals <- read_excel('totals.xlsx') %>%
mutate(date = seq(as.Date('2003-01-01'), length.out = length(Год), by = 'year'),
Сумма = Сумма /10^6
)
Подготовка данных
Подготовим данные для графиков бубликов.
data_percents_21 <-
data %>%
mutate(percent = (`2021` / 1000) %>% round(1)) # в млрд долл
data_percents_22 <-
data %>%
mutate(percent = (`2022` /1000) %>% round(1)) # в млрд долл
# Функция для подготовки данных
make_donut_coords <- function(df, cat, percent){
df %>%
arrange(desc(cat)) %>%
mutate(ypos = cumsum(percent) - 0.5*percent ) %>% # координаты для подписей
mutate(ymax = cumsum(percent)) %>%
mutate(ymin = c(0, head(ymax, n=-1))
) %>%
mutate(label_pos = (ymax + ymin) / 2) %>%
mutate(percent_label = percent) # формат подписей
}
# Применяем нашу функцию
data_1 <- make_donut_coords(data_percents_21, cat, `2021`)
data_2 <- make_donut_coords(data_percents_22, cat, `2022`)
Подготовим данные для линейного графика. Я хочу дополнительно выделить цветом изменение объёма услуг в 2021-2022 гг. Для этого я разбиваю один ряд на два, чтобы позже выделить из разными цветами в ggplot.
totals_plt <-
totals %>%
mutate(series_1 = c(Сумма[1:nrow(totals)-1], NA), # первая часть, будет жёлтенькой
series_2 = c(rep(NA, nrow(totals) - 2), totals$Сумма[(nrow(totals)-1):nrow(totals)]) # Последние наблюдения - красные. Они же вынесены в бублики.
) %>%
select(date, series_1, series_2) %>% # выбираю нужное
pivot_longer(series_1:series_2) # делаю длинную табличку, чтобы кормить её ggplot.
Рисование
Внешние параметры графиков лучше выносить вовне. Так будет проще что-то менять в будущем.
size_header = 8 # размер заголовка
size_text = 3 # размер названий и подписей
size_center = 8 # размер текста в центре
label_position = 2.1
colors <- c('#CEB09F','#9B1631','#71797E','#0A99C3', '#040273') # цвета по порядку: Дорожная пыль для автомобильного транспорта, тёмно-красный - авиа, стальной - ЖД, морская волна - морской транспорт, цвет морских глубин - трубопроводный транспорт.
colors_text <- c('#040273', '#0A99C3', '#71797E','#9B1631', '#CEB09F')
Важно! Как и в прошлом материале, чтобы получить понимание, как всё работает, не запускайте код полностью. Запустите первые несколько строк. Попробкйте поменять параметры, получите интуицию, как и что работает, затем продолжайте добавлять строки кода.
Графики-бублики:
plot_1 <-
data_1 %>%
ggplot(aes(ymax = ymax, ymin = ymin, xmax = 4, xmin = 3, fill = cat)) + # Для geom_rect нужны эти aes. Попробуйте поменять xmin и xmax - Посмотрите, что получится!
geom_rect(alpha = 0.8) + # Основная геометрия - столбики. Но эти столбики можно сделать более интересными!
geom_text(x=label_position, aes(y=label_pos, label=percent_label), size=size_text, family = 'HSE Sans', color = colors_text) + # Подписи
coord_polar(theta = 'y') + # Сделаем график-пирог!
xlim(c(-1, 4)) + # Вырежем центр пирога - получится график бублик. Зачем вырезать центр? В центре мы хотим что-то написать!
annotate('text', x = -1, y = 1, label = '2021', family = 'HSE Sans', fontface = 'bold', size = size_center, alpha = 0.4) + # Подпись в центре
#annotate('text', x = 0.2, y = 3 * 3 / 2 , label = '(млрд $)', family = 'HSE Sans', fontface = 'bold', size = size_center / 3, alpha = 0.7) + # Изначально я хотел сделать подпись с единицами измерения в центре, но перепробовав несколько вариантов решил, что без этого можно обойтись.
scale_fill_manual(values = c(colors)) +
theme_void(base_family = 'HSE Sans') +
#theme(legend.position = 'bottom') +
xlim(c(-1, 4))
plot_2 <-
data_2 %>%
ggplot(aes(ymax = ymax, ymin = ymin, xmax = 4, xmin = 3, fill = cat)) +
geom_rect(alpha = 0.8) +
geom_text(x = label_position, aes(y = label_pos, label = percent_label), size = size_text, family = 'HSE Sans', color = colors_text) +
coord_polar(theta = 'y') +
annotate('text', x = -1, y = 1, label = '2022', family = 'HSE Sans', fontface = 'bold', size = size_center, alpha = 0.4) +
#annotate('text', x = 0.2, y = 3.14 , label = '(млрд $)', family = 'HSE Sans', fontface = 'bold', size = size_center / 3, alpha = 0.7) +
scale_fill_manual(values = c(colors)) +
theme_void(base_family = 'HSE Sans') +
#theme(legend.position = 'bottom') +
xlim(c(-1, 4))
Собираем один график из нескольких при помощи библиотеки patchwork
.
plot_1 + plot_2 + # комбинация графиков 1 и 2
plot_annotation(caption = 'млрд долл.') + # подпись с единицами измерений
plot_layout(guides = 'collect') & # сделать единую легенду
theme(legend.position = 'bottom', # легенду - вниз
legend.title = element_blank(), # название легенды нам не нужно
legend.text = element_text(size = 8)) # Размер текста легенды
Добавим сверху историю.
colors_plt_3 <- c("#FFD54E", "#A80002")
plot_3 <-
totals_plt %>%
ggplot(aes(x = date, y = value, color = name)) + # горизонтальная ось - даты, вертикальная значение (экспорт транспортных услуг), цвет - ряд.
geom_line(show.legend = F, alpha = 0.9) + # линия. Иногда полезно сразу написать, чтобы легенда не отображалась.
geom_point(alpha = 0.4, show.legend = F) + # если комбинировать линии с точками - будет красивее.
scale_color_manual(values = colors_plt_3) + # красивые цвета, чтобы выделить нужное по смыслу.
theme_minimal(base_family = 'HSE Sans') + # Тема и шрифт
theme(axis.title.y = element_text(size = 8), # Уменьшил размер шрифта названия оси
plot.title = element_text(hjust = 0.5, face = 'bold'), # Название графика - по середине, жирный шрифт
panel.grid.major.x = element_blank(), # убираем ненужную разметку
panel.grid.minor.x = element_blank(),
panel.grid.minor.y = element_blank(),
axis.title.x = element_blank()) + # Убираем название оси х.
labs(y = 'Млрд долл.', # Текст на графике, ось x
title = 'Российский экспорт услуг по \n транспортировке грузов') # \n - для переноса на следующую строку. Бывает очень полезно.
plot_3 / (plot_1 + plot_2) +
plot_annotation(caption = 'млрд долл.') + # комбинируем графики
plot_layout(guides = 'collect', heights = c(2,5)) & # выкладка, общая легенда, соотношение сторон.
theme(legend.position = 'bottom', # легенда - внизу
legend.title = element_blank(),
legend.text = element_text(size = 8),
legend.key = element_rect(size = 1), # рамка у цветных квадратиков. Так красивее, не спорьте.
legend.key.size = unit(0.4, 'cm'), # размер цветных квадратиков
plot.caption = element_text(family = 'HSE Sans', face = 'italic') # Подпись внизу - курсивом
)
Сохраним график в хорошем разрешении.
ggsave('export_trans.svg')