Библиотека 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)

Построим первый график. До этого занятия мы не обсуждали линейные графики (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`.

Ящики с усами (box plots) и скрипичные диаграммы (violin plots)

Про ящики с усами и скрипичные диаграммы мы уже говорили, поэтому давайте просто их построим. Просто ящик с усами:

# слой 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()

Диаграммы рассеяния (scatter plots) и пузырьковые диаграммы (bubble plots)

Обычные диаграммы рассеяния мы строили и не раз. Построим теперь диаграмму рассеяния с помощью 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: я не знаю, насколько часто обновляется эта библиотека и насколько актуальны флаги государств, используемые в ней.