ggplot2
: одномерные данныеУ библиотеки ggplot2
есть своя философия, поняв которую, строить графики гораздо легче. Во-первых, графики, созданные с помощью ggplot2
, многослойные, то есть строятся они поэтапно, по слоям. Сначала указывается датафрейм, с которой мы работаем, и интересующие нас показатели (первый слой), затем указывается тип графика (второй слой), затем настройки для подписей, легенды и прочее (остальные слои). Все слои добавляются через оператор +
.
Начнем разбираться с ggplot2
на примере гистограмм.
Мы поработаем с файлом Excel, в котором содержатся данные по стоимости минимального набора продуктов в регионах России в 2020 году. На первом листе файла сохранены данные в разрезе регионов, на втором — в разрезе районов, на третьем — паспорт данных или, другими словами, их описание от Росстата. Для начала загрузим первый лист файла:
library(readxl)
regs <- read_excel("data.xls")
Столбцы REG
и OKR
соответствуют региону и федеральному округу, остальные столбцы — месяцам (с января по октябрь).
Визуализируем распределение стоимости минимального набора продуктов по регионам с помощью гистограммы. Что нам даст такая визуализация? Во-первых, мы будем знать, какие значения стоимости встречается чаще, а какие — реже. Во-вторых, мы поймем, есть ли в данных сдвиг («перекос») в сторону больших или меньших значений.
library(tidyverse)
Построим гистограмму для стоимости минимального набора продуктов в октябре (X10
). Внутри главного слоя ggplot()
укажем датафрейм regs
, а сам показатель, значения которого идут по оси x
, запишем внутри aes()
, функции, которая отвечает за эстетику (aes
- aesthetics), за наполнение графика. А потом допишем слой geom_histogram()
, который отвечает за тип графика.
ggplot(data = regs, aes(x = X10)) + geom_histogram()
Гистограмма получилась не очень красивой, и не только из-за тёмного цвета. Она получилась какой-то «дырявой». Почему? По умолчанию ggplot строит гистограмму с 30 столбцами, поэтому в нашем случае столбцы получились слишком узкими. Дробление на интервалы для столбцов гистограммы получилось слишком детальным, поэтому некоторые интервалы оказались пустыми – в них не попало ни одно значение, из-за чего и появились «дырки». Собственно, поэтому R нам и выдал предупреждение Pick better value with binwidth
. Поменяем шаг у гистограммы, ширину столбца, вручную, добавив binwidth
внутри geom_histogram()
. Заодно поменяем цвет заливки и границ столбцов, а также сделаем тему чёрно-белой (bw, от black-white):
ggplot(data = regs, aes(x = X10)) +
geom_histogram(binwidth = 500,
fill = "darkblue",
color = "white") +
theme_bw()
Теперь проинтерпретируем полученный график. Первое, что бросается в глаза — это наличие нетипично большого значения — столбца, стоящего далеко справа. В то время как большинство значений сконцентрированы в районе от 2500 до 6000 рублей, значение для этого региона превосходит 10000 руб. Выясним, что это за регион, воспользовавшись фильтрацией:
regs %>% filter(X10 > 10000) %>% select(REG)
Нетипичным регионом оказался Чукотский автономный округ. Это во многом ожидаемо, цены на продукты питания, особенно на фрукты и овощи, в несколько раз выше средних цен по России. Что еще дает нам гистограмма? То, что обычно стоимость минимального набора продуктов питания не превышает 5000 рублей, при этом само распределение показателя скошено вправо — есть пять регионов с более высокими значениями, которые образуют «хвост» справа на графике.
С содержательным смсылом графика разобрались, давайте вернемся к оформлению. Сделаем подписи к графику более информативными — добавим слой labs()
и поместим в него аргументы, отвечающие за заголовок графика, подпись по оси x
и подпись по оси y
:
ggplot(data = regs, aes(x = X10)) +
geom_histogram(binwidth = 500,
fill = "darkblue",
color = "white") +
theme_bw() +
labs(title = "Стоимость минимального набора\nпродуктов питания",
x = "Стоимость в октябре 2020",
y = "Количество регионов")
Теперь давайте внесем более содержательные изменения. Добавим на график вертикальную линию, отчерчивающую среднее значение показателя, чтобы можно было посмотреть на распределение значений по регионам относительно среднего. Воспользуемся слоем geom_vline()
, v от vertical и сообщим R, в каком месте вертикальная линия должна пересекать горизонтальную ось — xintercept
:
ggplot(data = regs, aes(x = X10)) +
geom_histogram(binwidth = 500,
fill = "darkblue",
color = "white") +
theme_bw() +
labs(title = "Стоимость минимального набора\nпродуктов питания",
x = "Стоимость в октябре 2020",
y = "Количество регионов") +
geom_vline(xintercept = mean(regs$X10), lty = 2, color = "red")
Можем проделать то же самое для медианы:
ggplot(data = regs, aes(x = X10)) +
geom_histogram(binwidth = 500,
fill = "darkblue",
color = "white") +
theme_bw() +
labs(title = "Стоимость минимального набора\nпродуктов питания",
x = "Стоимость в октябре 2020",
y = "Количество регионов") +
geom_vline(xintercept = mean(regs$X10), lty = 2, color = "red") +
geom_vline(xintercept = median(regs$X10), lty = 2, color = "limegreen")
Дополнение: если хотим добавить легенду для вертикальных линий — проще будет собрать их в отдельный датафрейм (адаптировано отсюда):
# dataframe from 3 columns: xintercept, Lines, color
lines <- data.frame(xintercept = c(mean(regs$X10), median(regs$X10)),
Lines = c("Sample mean", "Sample median"),
color = c("red", "limegreen"))
# add this dataframe to aes() inside geom_vline()
# and scale colors
ggplot(data = regs, aes(x = X10)) +
geom_histogram(binwidth = 500,
fill = "darkblue",
color = "white") +
theme_bw() +
labs(title = "Стоимость минимального набора\nпродуктов питания",
x = "Стоимость в октябре 2020",
y = "Количество регионов") +
geom_vline(data = lines,
aes(xintercept = xintercept, color = Lines), lty = 2) +
scale_color_manual(values = lines$color)
На этом закончим с гистограммами и перейдем к другому типу графика — сглаженному графику плотности распределения. На английском языке и в R этот тип графика называется density plot или kernel density plot. К этому графику можно относиться как к сглаженной версии гистограммы. Сначала частоты по оси y
переводятся из абсолютных значений в доли, затем столбцы плавно очерчиваются, чтобы получить общую линию, характеризующую распределение показателя. Построим сглаженный график плотности распределения для того же показателя X10
. Для этого все слои оставим на месте, только вместо geom_histogram()
напишем geom_density()
и впишем туда цвет заливки и контура:
options(scipen = 999)
ggplot(data = regs, aes(x = X10)) +
geom_density(fill = "lightblue",
color = "navy") +
theme_bw() +
labs(title = "Стоимость минимального набора\nпродуктов питания",
x = "Стоимость в октябре 2020",
y = "Плотность распределения")
Готово! В нашем случае гистограмма и график плотности распределения показывают одни и те же особенности распределения данных, в частности, длинный хвост справа и нетипично большие значения. Однако стоит иметь в виду, что график плотности распределения не всегда хорошо отражает форму распределения данных. Такой график нежелательно использовать, если данных мало (обычно менее 30 наблюдений) и если на гистограмме встречаются «дырки», которые показывают отсутствие данных на определенных участках. В обоих случаях сглаживание будет сопровождаться потерей точности. Например, на гистограмме будет четко видно, что частота встречаемости значений на некотором участке равна 0 (столбца просто нет), а при сглаживании на этом месте образуется небольшая закрашенная область, что может ввести в заблуждение людей, изучающих график. Глядя на эти недостатки, может показаться, что графики плотности менее полезны, чем гистограммы, однако это не так, их удобно использовать для визуализации распределений по группам.
ggplot2
: группировка данных на графикахВыберем из датафрейма regs
три федеральных округа: Центральный, Приволжский и Сибирский.
regions <- c("Центральный федеральный округ",
"Приволжский федеральный округ",
"Сибирский федеральный округ")
three <- regs %>% filter(OKR %in% regions)
Теперь попробуем построить в одной плоскости три гистограммы, одна гистограмма для каждого округа. С помощью ggplot2
это сделать гораздо проще, чем с помощью обычной функцииhist(
): не нужно сохранять строки в отдельные датафреймы и после строить гистограммы, можно просто добавить еще один аргумент для группировки. Допишем в aes()
аргумент group
и укажем, что группировка должна производиться по столбцу OKR
:
ggplot(data = three, aes(x = X10, group = OKR)) +
geom_histogram()
Пока ничего, связанного с разными группами, не видно. Это потому, что цвет заливки у всех групп пока одинаковый. Исправим это, добавим внутри aes()
аргумент fill
и сообщим, что заливка тоже должна осуществляться в соответствии с округом:
ggplot(data = three, aes(x = X10, group = OKR, fill = OKR)) +
geom_histogram()
Стало попонятнее, но графики накладываются друг на друга. Добавим прозрачность (аргумент alpha
) и заодно изменим шаг у гистограмм:
ggplot(data = three, aes(x = X10, group = OKR, fill = OKR)) +
geom_histogram(binwidth = 500, alpha = 0.5)
График стал более читаемым, но все же сравнивать гистограммы в таком виде друг с другом неудобно. Для решения этой проблемы есть два пути. Первый: построить другой тип графиков для визуализации и сравнения распределений. Второй: построить графики в отдельных окнах, а не в одной плоскости. Сначала пойдем по первому пути. И тут нам как раз пригодятся сглаженные графики плотности распределения, о которых мы говорили в предыдущем уроке. Изменим слой с histogram
на density
:
ggplot(data = three, aes(x = X10, group = OKR, fill = OKR)) +
geom_density(alpha = 0.5)
Выглядит получше. Что мы здесь видим? Во-первых, в целом, самые дорогие минимальные продуктовые наборы в Сибирском федеральном округе, а самыне дешевые — в Приволжском федеральном округе. Этот вывод мы можем сделать, сравнив расположение графиков по горизонтальной оси. Во-вторых, регионы Приволжского федерального округа наименее разнообразные с точки зрения цен. График плотности, полученный для этого округа, является самым узким, что соответствует более низкому разбросу значений относительно среднего. В-третьих, распределение цен в Центральном федеральном округе довольно интересное. Заметно, что у графика есть два «горба» и «хвост» справа. Все это свидетельствует о том, что в этом округе есть три явные группы регионов: с довольно низкими ценами (первый «горб»), со средними ценами (второй «горб») и небольшое количество регионов, скорее всего один, с относительно высокой стоимостью минимального продуктового набора (нетипично большое значение справа). В других округах таких явных групп не наблюдается.
Итак, мы содержательно проинтерпретировали график, теперь вернемся к техническим вопросам. По умолчанию R самостоятельно выбирает цвета для графиков при группировке. Если групп две, он выбирает розовый и голубой цвета, поскольку R изначально активно использовался в биологии, если три – добавляет зеленый и так далее. Но, конечно, задать цвета можно самостоятельно. Например, перечислить их вручную в аргументе scale_fill_manual()
.
ggplot(data = three, aes(x = X10, group = OKR, fill = OKR)) +
geom_density(alpha = 0.5) +
scale_fill_manual(values = c("violet", "yellow", "lightblue"))
Если бы мы захотели изменить цвет линий или точек, а не заливки, мы бы воспользовались слоем scale_color_manual()
. В помощью этого слоя можно также скорректировать подписи в легенде. Заголовок легенды вводится в аргументе name
, подписи к цветам — в аргументе labels
.
ggplot(data = three, aes(x = X10, group = OKR, fill = OKR)) +
geom_density(alpha = 0.5) +
scale_fill_manual(values = c("violet", "yellow", "lightblue"),
name = "Округ",
labels = c("Приволжский ФО",
"Сибирский ФО",
"Центральный ФО"))
Итак, с первым способом решения проблемы с несколькими гистограммами на графике мы справились, заменили их на графики плотности. Второй способ такой — построить гистограммы в отдельных окнах, но в пределах одного графика. Такие окна с графиками в R называются фасетками (от английского facets). Построим гистограммы для показателя X10
таким образом, чтобы гистограмма для каждого округа находилась в отдельной фасетке. Сделать это легко, достаточно добавить еще один слой, слой facet_wrap()
:
ggplot(data = three, aes(x = X10)) +
geom_histogram(binwidth = 500) +
facet_wrap(~OKR)
Переменная группировки, в нашем случае OKR
, указывается после «тильды» (~
) внутри слоя facet_wrap()
. Сочетать разбиение на группы-фасетки с изменением цвета тоже можно — вернем аргумент fill
в aes()
:
ggplot(data = three, aes(x = X10, fill = OKR)) +
geom_histogram(binwidth = 500) +
facet_wrap(~OKR)
Осталось поправить подписи к каждой фасетке, так как исходные подписи слишком длинные. Для этого нужно создать поименованный вектор соответствий старого названия и нового, а потом указать его внутри слоя facet_wrap()
.
labels <- c("Приволжский федеральный округ" = "Приволжский",
"Сибирский федеральный округ" = "Сибирский",
"Центральный федеральный округ" = "Центральный")
ggplot(data = three, aes(x = X10, fill = OKR)) +
geom_histogram(binwidth = 500) +
facet_wrap(~OKR, labeller = as_labeller(labels))
Аналогично предыдущим графикам, здесь можно скорректировать цвета. Давайте это сделаем или выключим легенду, так как она дублирует информацию об округах:
ggplot(data = three, aes(x = X10, fill = OKR)) +
geom_histogram(binwidth = 500, alpha = 0.8, color = "white") +
facet_wrap(~OKR) +
theme_bw() +
scale_fill_manual(values = c("violet", "yellow", "lightblue")) +
theme(legend.position = "none")
Теперь давайте посмотрим на новый тип графика — линейный, и построим его по группам, чтобы сравнить динамику стоимости минимального набора продуктов по месяцам в трех округах. Для удобства загрузим данные со второго листа файла Excel, где они уже агрегированы по округам:
okrs <- read_excel("data.xls", sheet = 2)
Выберем нужные нам округа:
o_three <- okrs %>% filter(OKR %in% regions)
Построим линейный график для каждого округа. По горизонтальной оси будут идти месяцы, по вертикальной оси — значения стоимости.
ggplot(data = o_three, aes(x = mon, y = price, group = OKR)) +
geom_line()
Чтобы оживить график, добавим разные цвета и типы линий, а также поиграем с темами:
ggplot(data = o_three, aes(x = mon, y = price,
group = OKR,
color = OKR)) +
geom_line(aes(linetype = OKR)) +
labs(x = "Месяц", y = "Цена", linetype = "Округ") +
guides(color = FALSE) +
theme_classic()
## Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> =
## "none")` instead.
ggplot(data = o_three, aes(x = mon, y = price,
group = OKR,
color = OKR)) +
geom_line(aes(linetype = OKR)) +
labs(x = "Месяц", y = "Цена", linetype = "Округ") +
guides(color = FALSE) +
theme_minimal()
## Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> =
## "none")` instead.
ggplot(data = o_three, aes(x = mon, y = price,
group = OKR,
color = OKR)) +
geom_line(aes(linetype = OKR)) +
labs(x = "Месяц", y = "Цена", linetype = "Округ") +
guides(color = FALSE) +
theme_dark()
## Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> =
## "none")` instead.