ggplot2
и её возможностиБиблиотека ggplot2
позволяет строить красивые графики. Установим библиотеку.
install.packages("ggplot2")
Обратимся к библиотеке ggplot2
(и заодно к dplyr
, она нам тоже понадобится):
library(ggplot2)
library(dplyr)
Примечание: ещё есть библиотека tidyverse
, которая, помимо прочих библиотек, включает в себя и dplyr
, и ggplot2
сразу. Так что, можно установить её и подгружать всегда, когда нужно обрабатывать данные и визуализировать.
Чтобы разобраться с логикой построения графиков с помощью ggplot2
, пока будем работать с простыми данными — данными по температуре тела бобров beaver1
(к политологии вернемся на следующем занятии). Подготовим базу:
beav <- beaver1 # загрузим базу - она встроена в R
beav$id <- 1:nrow(beaver1) # добавим id
Теперь перейдем к ggplot2
. Можно считать, что у библиотеки ggplot2
есть своя философия, поняв которую, строить графики гораздо легче.
Во-первых, графики ggplot
многослойные, то есть строятся они поэтапно, по слоям. Сначала указывается датафрейм, с которым мы работаем, и интересующие нас показатели (первый слой), затем указывается тип графика (второй слой), затем настройки для подписей, легенды и прочее (остальные слои). Все слои добавляются через +
.
Во-вторых, для любого графика указывается функция aes
, сокращенно от aesthetics, в качестве аргументов которой задаются переменные интереса (которые хотим отобразить на графике), а также элементы оформления графика, которое непосредственно связано с переменными в датафрейме. О чём речь? Проще понять на примерах.
Пример 1. Строим диаграмму рассеяния для роста и веса человека, хотим, чтобы все точки на диаграмме рассеяния были зелёными.
Пример 2. Строим диаграмму рассеяния для роста и веса человека, хотим, чтобы точки на диаграмме рассеяния, соответствующие женщинам, были красными, а мужчинам — синими.
В первом примере оформление графика никак не связано со значениями переменных в датафрейме, все точки закрашиваем одним цветом. Во втором примере цвет точек зависит от значения переменной пол, то есть оформление графика связано с переменными в датафрейме. Как увидим позже, в случаях, аналогичным первому, цвет точек будет определяться за пределами aes()
, второму — внутри aes()
.
Построим первый график. До этого занятия мы не обсуждали линейные графики (line plots), но все с ними так или иначе сталкивались, когда следили за динамикой каких-то количественных показателей. Попробуем визуализировать динамику температуры тела бобров в течении времени (в качестве показателя времени будем использовать id замера температуры, так как все замеры производились последовательно, с интервалом в 10 минут).
# в aes - показатели по оси x и y
# через + указан тип графика geom_line()
ggplot(data = beav, aes(x = id, y = temp)) + geom_line()
Типы графиков можно сочетать. Добавим точки (чтобы получились точки, соединенные линиями):
# два типа: geom_line и geom_point
ggplot(data = beav, aes(x = id, y = temp)) +
geom_line() +
geom_point()
Цвета и типы точек и линий можно изменять. Сделаем это!
# синие линии
# точки поменьше
ggplot(data = beav, aes(x = id, y = temp)) +
geom_line(color = "blue") +
geom_point(size = 0.5)
Разных опций, конечно, много. Чтобы узнать о всех возможностях, можно запросить help
отдельно для оформления точек или линий:
?geom_point
?geom_line
Посмотрим теперь, в каких случаях параметры оформления графика имеет смысл указывать внутри aes()
. В «бобрином» датафрейме у нас есть переменная activ
— активность бобров (0 — не активен, 1 — активен). Представим, что мы хотим построить два линейных графика в одной плоскости: один для неактивных бобров, другой — для активных.
# group - группировка по переменной, чтобы получилось 2 отдельных графика
# color - чтобы разные группы точек были разного цвета (в зависимости от значений activ)
ggplot(data = beav, aes(x = id, y = temp, group = activ, color = activ)) +
geom_line() + geom_point()
Внимание, вопрос: почему график правильный, а легенда у него такая странная? Переменная activ
принимает всего два значения, 0 и 1, а тут целая шкала от 0 до 1 образовалась… Эта проблема возникла потому, что у нас в базе данных переменная activ
не факторная (тип factor
), а числовая (тип numeric
). Чтобы получить правильную легенду, скорректируем тип переменной:
beav <- beav %>% mutate(activ = factor(activ))
Посмотрим теперь:
ggplot(data = beav,
aes(x = id, y = temp,
group = activ, color = activ)) +
geom_line() +
geom_point()
Теперь всё верно. Но цвета поменялись. По умолчанию в R разбивка на две группы — разбивка по признаку «пол», поэтому цвета получились такими. Конечно, их можно поменять:
# scale_color_manual - задаем вектор значений цветов вручную
ggplot(data = beav,
aes(x = id, y = temp,
group = activ, color = activ)) +
geom_line() +
geom_point() +
scale_color_manual(values = c("red", "blue"))
А теперь с помощью этого же слоя scale_color_manual
поменяем названия групп, указанных в легенде графика:
# то же + аргумент labels
ggplot(data = beav,
aes(x = id, y = temp,
group = activ, color = activ)) +
geom_line() +
geom_point() +
scale_color_manual(values = c("red", "blue"),
labels = c("Not active", "Active"))
И поменяем заголовок в легенде:
# то же + аргумент name
ggplot(data = beav,
aes(x = id, y = temp,
group = activ, color = activ)) +
geom_line() +
geom_point() +
scale_color_manual(values = c("red", "blue"),
labels = c("Not active", "Active"),
name = "Activity")
Теперь осталось узнать, как подписывать оси на графике и добавлять заголовок. Для всего этого есть один слой labs
(можно и иначе, есть отдельные слои для заголовков, подзаголовков и прочих подписей):
# title - заголовок
# x - подпись оси x
# y - подпись оси y
ggplot(data = beav, aes(x = id, y = temp, group = activ, color = activ)) +
geom_line() + geom_point() +
scale_color_manual(values = c("red", "blue"),
labels = c("Not active", "Active")) +
labs(title = "Beavers: body temperature",
x = "Observations",
y = "Temperature, C")
В завершение нашего первого знакомства с ggplot2
поменяем тему графика (theme). По умолчанию график строится на сером фоне, но фон можно сделать, например, белым:
# добавляем еще слой с theme
# bw: black-white
ggplot(data = beav, aes(x = id, y = temp, group = activ, color = activ)) +
geom_line() + geom_point() +
scale_color_manual(values = c("red", "blue"),
labels = c("Not active", "Active")) +
labs(title = "Beavers: body temperature",
x = "Observations",
y = "Temperature, C") +
theme_bw()
Или, наоборот, тёмным (правда, здесь это будет не очень удачно смотреться):
ggplot(data = beav, aes(x = id, y = temp, group = activ, color = activ)) +
geom_line() + geom_point() +
scale_color_manual(values = c("red", "blue"),
labels = c("Not active", "Active")) +
labs(title = "Beavers: body temperature",
x = "Observations",
y = "Temperature, C") +
theme_dark()
А теперь перейдем к другим графикам.
Для чего нужны гистограммы, мы уже обсуждали. Гистограммы строятся для визуализации формы распределения количественного показателя. Построим гистограмму для температуры тела бобров:
ggplot(data = beav, aes(x = temp)) +
geom_histogram()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Пока выглядит несильно симпатично. Начинаем исправлять. Прежде всего, изменим шаг гистограммы, то есть ширину столбца. При построении этого графика R выдал предупреждение, что по умолчанию было построено 30 столбцов, что в данном случае может быть некорректно. Выставим шаг (binwidth = 1
) вручную:
ggplot(data = beav, aes(x = temp)) +
geom_histogram(binwidth = 0.1)
Теперь поменяем цвет. При изменении цвета «заполненных» (состоящих не из отдельных линий и точек) графиков нужно помнить, что есть два параметра: color
и fill
. Параметр color
отвечает за цвет границ графика, а за не цвет их заливки. А уже fill
— как раз за заливку.
# желто-зеленая гистограмма
# столбцы которой очерчены черной линией
ggplot(data = beav, aes(x = temp)) +
geom_histogram(binwidth = 0.1,
fill = "yellowgreen",
color = "black")
На гистограммы можно добавлять вспомогательные вертикальные или горизонтальные линии. Например, можно отчертить значение частоты, равной 15:
# слой geom_hline
# yintercept - значение, где прямая пересекает ось y
ggplot(data = beav, aes(x = temp)) +
geom_histogram(binwidth = 0.1,
fill = "yellowgreen",
color = "black") +
geom_hline(yintercept = 15, color = "red")
Или отметить медиану (уже вертикальная линия):
# слой geom_vline
# xintercept - значение, где прямая пересекает ось x
# lty - тип линии
ggplot(data = beav, aes(x = temp)) +
geom_histogram(binwidth = 0.1,
fill = "yellowgreen",
color = "black") +
geom_vline(xintercept = median(beav$temp),
color = "red",
lty = 2)
Гистограммы тоже можно строить отдельно для разных групп наблюдений, оставаясь при этом в пределах одного графика. В нашем случае это не очень наглядно (в группе активных бобров всего 6 наблюдений), но для примера можно построить гистограммы по группам:
ggplot(data = beav, aes(x = temp, group = activ, fill = activ)) +
geom_histogram()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Если требуется построить графики распределения, которые накладываются друг на друга, для большей наглядности вместо гистограмм иногда используют сглаженные графики плотности распределения (kernel density plots).
# alpha = 0.5 - для 50% прозрачности
ggplot(data = beav, aes(x = temp, group = activ, fill = activ)) +
geom_density(alpha = 0.5)
Такие графики выглядят симпатично, однако могут дезинформировать. Из-за того, что такие графики плотности получаются путем сглаживания гистограммы, они могут получаться неточными. Например, не будут отражены некоторые перепады в частотах, сгладятся «пики» распределения. Даже в нашем случае заметны неточности: по гистограмме видно, что в распределении температуры тела активных бобров есть «дырки» (некоторых столбцов нет), а на сглаженном графике плотности мы этого не увидим.
Раз уж зашла речь о дезинформации, обсудим ещё один важный момент. Очень удобно (и честно!), когда на графике по возможности отражено число наблюдений, по которому он строился. Если в случае с точечными графиками это видно и так (много точек или совсем мало), то в случае гистограмм, графиков плотности, ящиков с усами и прочих, число наблюдений определить по графику сложновато. В ggplot
для отметки наблюдений есть специальный параметр rugs
(устоявшегося русскоязычного термина нет). Выглядит это следующим образом:
ggplot(data = beav, aes(x = temp, group = activ, fill = activ)) +
geom_density(alpha = 0.5) + geom_rug()
Под графиком добавляются «палочки» — обозначения наблюдений. И хотя эти засечки (rugs
) не показывают явно общее числе наблюдений (вряд ли кто-то захочет их считать), по ним можно представлять, сколько наблюдений сконцентрировано в той или иной части графика. Зачем это нужно? Представим, что мы ничего не знаем о базе данных по бобрам и видим графики плотностей распределения по группам. Нам может показаться, что, если мы исключим несколько значений температуры тела активных бобров в окрестности 37.5 градусов, распределение температуры тела этих бобров будет похоже на нормальное. Однако, когда мы посмотрим на график с rugs, про нормальность мы думать не будем — увидим, что в группе всего 6 наблюдений, а это очень мало, чтобы судить о форме распределения.
Бонус. Как наложить сглаженный график плотности (без заливки) на гистограмму?
# y = ..density.. : функция для вычисления плотности,
# обособляется точками
ggplot(data = beav, aes(x = temp)) +
geom_histogram(aes(y = ..density..),
fill = "violetred", color = "black") +
geom_density(col = "darkblue")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Про ящики с усами и скрипичные диаграммы мы уже говорили, поэтому давайте просто их построим. Просто ящик с усами:
# слой geom_boxplot
ggplot(data = beav, aes(y = temp)) +
geom_boxplot()
Ящики с усами по группам:
# вариант 1, пустая ось x и группировка в group
ggplot(data = beav, aes(x = "", y = temp, group = activ, fill = activ)) +
geom_boxplot()
# вариант 2, группировка по оси x
ggplot(data = beav, aes(x = activ, y = temp, fill = activ)) +
geom_boxplot()
Скрипичные диаграммы по группам:
# слой geom_violin
ggplot(data = beav, aes(x = "", y = temp, group = activ, fill = activ)) +
geom_violin()
Обычные диаграммы рассеяния мы строили и не раз. Построим теперь диаграмму рассеяния с помощью ggplot2
. Для начала возьмем нашу любимую базу по показателям WGI и Freedom House.
dat <- read.csv("https://raw.githubusercontent.com/allatambov/R-programming-3/master/lectures/lect9-02-02/wgi_fh_new.csv", dec = ",")
dat <- na.omit(dat)
Построим диаграмму рассеяния для индексов Voice & Accountability (va
) и Rule of Law (rl
).
# диаграмма рассеяния
ggplot(data = dat, aes(x = va, y = rl)) +
geom_point() +
labs(title = "WGI indicators",
x = "Voice and Accountability",
y = "Rule of Law")
Можем наложить на эту диаграмму эллипс рассеяния, чтобы было проще судить о направлении и силе связи:
# слой stat_ellipse
ggplot(data = dat, aes(x = va, y = rl)) +
geom_point() +
labs(title = "WGI indicators",
x = "Voice and Accountability",
y = "Rule of Law") + stat_ellipse()
А можно добавить регрессионную прямую, которая будет иллюстрировать, насколько изменяется значение индекса Voice & Accountability при увеличении индекса Rule of Law на единицу:
# lm - от linear model
ggplot(data = dat, aes(x = va, y = rl)) +
geom_point() +
labs(title = "WGI indicators",
x = "Voice and Accountability",
y = "Rule of Law") +
geom_smooth(method = lm)
Если убрать method
в слое geom_smooth()
и оставить настройки по умолчанию, то будет построена сглаженная регрессия (lowess или loess, мы её отчасти обсуждали, см. здесь):
ggplot(data = dat, aes(x = va, y = rl)) +
geom_point() +
labs(title = "WGI indicators",
x = "Voice and Accountability",
y = "Rule of Law") +
geom_smooth()
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'
О том, что маркеры для точек можно менять, мы уже знаем (параметр shape
в geom_point
). Попрактикуемся на семинаре. А пока познакомимся с пузырьковой диаграммой (bubble plot).
Bubble plot позволяет делать график как будто «объёмным» — добавлять дополнительные измерения. На диаграмму рассеяния выше мы можем добавить значение ещё одной переменной, не превращая при этом график в какую-то трёхмерную конструкцию. Каким образом? Сделав размер точек на диаграмме рассеяния зависимым от значений третьей переменной! Более того, можно добавить и четвёртое измерение — закрасить точки на графике разным цветом в зависимости от значений ещё одного показателя.
Давайте сейчас сделаем следующее: построим диаграмму рассеяния для индексов Voice & Accountability и Rule of Law, учитывая при этом значение индекса Freedon House в интересующих нас государствах.
# тот же aes, но теперь еще и в geom_point
# величина точки зависит от fh, и оно задано внутри aesthetics
# цвет пока у всех точек один, поэтому он задан вне aesthetics
ggplot(data = dat, aes(x = va, y = rl)) +
geom_point(aes(size = fh), color = "cornflowerblue") +
labs(title = "WGI indicators",
x = "Voice and Accountability",
y = "Rule of Law")
А теперь финальный аккорд. Давайте добавим в базу данных факторную переменную для значений Freedom House (Free
, Partly Free
, Not Free
), как в домашнем задании, и сделаем нашу пузырьковую диаграмму осмысленно разноцветной.
Добавим переменную:
# из ДЗ 4
dat <- dat %>% mutate(not_free = as.integer(fh >= 5.5),
partly_free = as.integer(fh >= 3 & fh <= 5),
free = as.integer(fh <= 2.5))
colnames(dat)[11] <- "fh_score"
dat$fh_type <- names(dat[12:14])[max.col(dat[12:14])]
dat$fh_type <- factor(dat$fh_type)
Построим диаграмму:
ggplot(data = dat, aes(x = va, y = rl)) +
geom_point(aes(size = fh_score, color = fh_type)) +
labs(title = "WGI indicators", x = "Voice and Accountability",
y = "Rule of Law")
Графики можно строить по группам так, чтобы графики для каждой группы были в отдельной ячейке («фасетке»). Для этого понадобится слой facet_wrap()
:
# в нём через ~ указан показатель, по которому делим на группы
ggplot(data = dat, aes(x = va, y = rl)) +
geom_point() +
labs(title = "WGI indicators",
x = "Voice and Accountability",
y = "Rule of Law") +
facet_wrap(~fh_type)
Бонус: небольшой бонус — библиотека ggflags
, с помощью которой вместо точек на график можно наносить флаги государств. См. описание здесь.
Установим её. Эта библиотека интересна тем, что она устанавливается не из официального «хранилища» (CRAN), а с Github. Установим сначала библиотеку для разработчиков devtools
, а затем с ее помощью поставим ggflags
.
install.packages("devtools")
library(devtools)
install_github("rensa/ggflags") # имя пользователя и библиотека
Выберем несколько строк из нашей базы данных (для небольшого числа точек, конечно, диаграмма рассеяния как-то не очень интересна, но для примера):
set.seed(111)
cnt_sample <- dat[sample(nrow(dat), 5), ]
cnt_sample
## X country cnt_code year va ps ge rq rl
## 116 129 Malta MLT 2016 1.20 1.08 0.95 1.16 1.08
## 141 156 Poland POL 2016 0.84 0.51 0.69 0.95 0.68
## 72 83 Hong Kong SAR, China HKG 2016 0.27 0.84 1.86 2.15 1.70
## 99 110 Libya LBY 2016 -1.37 -2.21 -1.89 -2.27 -1.87
## 73 84 Honduras HND 2016 -0.43 -0.36 -0.73 -0.51 -1.11
## cc fh_score not_free partly_free free fh_type
## 116 0.72 1.0 0 0 1 free
## 141 0.75 1.0 0 0 1 free
## 72 1.58 3.5 0 1 0 partly_free
## 99 -1.57 6.0 1 0 0 not_free
## 73 -0.69 4.0 0 1 0 partly_free
Нам нужны флаги следующих стран: Malta, Poland, Hong Kong, Libya, Honduras.
library(ggflags)
# коды стран для флагов
cnt_sample$codes = c("mt", "pl", "hk", "ly", "hn")
# график
# заодно добавили названия стран:
# label в aes
# слой geom_text(с выравниваем подписей по горизонтали и вертикали)
ggplot(data = cnt_sample,
aes(x = va, y = rl,
country = codes,
label = country)) +
geom_flag() +
geom_text(hjust=0.5, vjust=-0.5)
Disclaimer: я не знаю, насколько часто обновляется эта библиотека и насколько актуальны флаги государств, используемые в ней.