library(tidyverse)
# Раскомментируйте и запустите следующие команды, 
# если у вас не ставится tidyverse
#library(dplyr)
#library(tidyr)
#library(ggplot2)

Хорошо упорядоченные данные (tidy data)

Данные могут быть представлены в различной форме. Следующие таблицы содержат одни и те же данные - динамику количества случаев заболевания (cases) и численности населения (population) в разных странах (country). Хотя данные одни и те же, структура таблиц различается:

table1
## # A tibble: 6 x 4
##   country      year  cases population
##   <chr>       <int>  <int>      <int>
## 1 Afghanistan  1999    745   19987071
## 2 Afghanistan  2000   2666   20595360
## 3 Brazil       1999  37737  172006362
## 4 Brazil       2000  80488  174504898
## 5 China        1999 212258 1272915272
## 6 China        2000 213766 1280428583
table2
## # A tibble: 12 x 4
##    country      year type            count
##    <chr>       <int> <chr>           <int>
##  1 Afghanistan  1999 cases             745
##  2 Afghanistan  1999 population   19987071
##  3 Afghanistan  2000 cases            2666
##  4 Afghanistan  2000 population   20595360
##  5 Brazil       1999 cases           37737
##  6 Brazil       1999 population  172006362
##  7 Brazil       2000 cases           80488
##  8 Brazil       2000 population  174504898
##  9 China        1999 cases          212258
## 10 China        1999 population 1272915272
## 11 China        2000 cases          213766
## 12 China        2000 population 1280428583
table3
## # A tibble: 6 x 3
##   country      year rate             
## * <chr>       <int> <chr>            
## 1 Afghanistan  1999 745/19987071     
## 2 Afghanistan  2000 2666/20595360    
## 3 Brazil       1999 37737/172006362  
## 4 Brazil       2000 80488/174504898  
## 5 China        1999 212258/1272915272
## 6 China        2000 213766/1280428583
table4a # cases
## # A tibble: 3 x 3
##   country     `1999` `2000`
## * <chr>        <int>  <int>
## 1 Afghanistan    745   2666
## 2 Brazil       37737  80488
## 3 China       212258 213766
table4b # population
## # A tibble: 3 x 3
##   country         `1999`     `2000`
## * <chr>            <int>      <int>
## 1 Afghanistan   19987071   20595360
## 2 Brazil       172006362  174504898
## 3 China       1272915272 1280428583
table5
## # A tibble: 6 x 4
##   country     century year  rate             
## * <chr>       <chr>   <chr> <chr>            
## 1 Afghanistan 19      99    745/19987071     
## 2 Afghanistan 20      00    2666/20595360    
## 3 Brazil      19      99    37737/172006362  
## 4 Brazil      20      00    80488/174504898  
## 5 China       19      99    212258/1272915272
## 6 China       20      00    213766/1280428583

Вопрос: В каком из представлений легче всего рассчитать в R относительную частоту заболевания - число случаев на 10000 жителей?

Наиболее удобно представление данных, в котором:

  • все переменные расположены по столбцам,
  • все наблюдения - по строкам,
  • в каждой ячейке таблицы хранится одно значение.

Данные в такой форме будем называть хорошо упорядоченными (tidy).

“Хорошо упорядоченные” данные

Преимуществом такой формы представления данных является единообразие: любая операция выполняется со столбцами таблицы, не требуется дополнительных действий, чтобы обратиться к нужным данным. Большинство функций в R ориентированы на векторную обработку значений, то есть они работают именно со столбцами значений.

Вопрос: где находятся переменные в рассмотренных примерах таблиц?

Плохо упорядоченные данные (messy data)

В противоположность “хорошо упорядоченным” данным, все остальные представления данных можно назвать “плохо упорядоченными” (messy). Этот термин означает лишь то, что обработка таких данных функциями для работы с таблицами будет более трудной. Это не значит, что сами данные - плохие или непригодные к анализу. Бывают ситуации, когда “плохая” для компьютерной обработки структура данных оказывается более удобной для человека - например, нам удобнее следить за динамикой показателей в сводных таблицах, где разные периоды времени отражаются отдельными столбцами.

Иногда данные приходится специально делать “плохо упорядоченными”, объединяя несколько переменных в одном столбце, чтобы обеспечить их автоматизированную обработку.

Например, если требуется визуализировать динамику населения и количества случаев заболеваний, то для “плохо упорядоченного” набора данных это можно сделать одной командой, а для “хорошо упорядоченного” потребуется несколько.

table2
## # A tibble: 12 x 4
##    country      year type            count
##    <chr>       <int> <chr>           <int>
##  1 Afghanistan  1999 cases             745
##  2 Afghanistan  1999 population   19987071
##  3 Afghanistan  2000 cases            2666
##  4 Afghanistan  2000 population   20595360
##  5 Brazil       1999 cases           37737
##  6 Brazil       1999 population  172006362
##  7 Brazil       2000 cases           80488
##  8 Brazil       2000 population  174504898
##  9 China        1999 cases          212258
## 10 China        1999 population 1272915272
## 11 China        2000 cases          213766
## 12 China        2000 population 1280428583
ggplot(data = table2) +
  geom_line(mapping = aes(color = country, 
                          x = year,
                          y = count)) +
  facet_wrap(~ type, scales = 'free_y') +
  scale_y_continuous(labels = scales::comma) +
  scale_x_continuous(breaks = 1999:2000)

table1
## # A tibble: 6 x 4
##   country      year  cases population
##   <chr>       <int>  <int>      <int>
## 1 Afghanistan  1999    745   19987071
## 2 Afghanistan  2000   2666   20595360
## 3 Brazil       1999  37737  172006362
## 4 Brazil       2000  80488  174504898
## 5 China        1999 212258 1272915272
## 6 China        2000 213766 1280428583
# Динамика случаев заболевания
p1 <- ggplot(data = table1) +
  geom_line(mapping = aes(color = country, 
                          x = year,
                          y = cases)) +
  scale_y_continuous(labels = scales::comma) +
  scale_x_continuous(breaks = 1999:2000) +
  scale_color_discrete(guide = FALSE)
  
# Динамика населения
p2 <- ggplot(data = table1) +
  geom_line(mapping = aes(color = country, 
                          x = year,
                          y = population)) +
  scale_y_continuous(labels = scales::comma) +
  scale_x_continuous(breaks = 1999:2000)

# Собираем два графика на одну визуализацию
gridExtra::grid.arrange(p1, p2, ncol = 2)

Как сделать данные “хорошо упорядоченными”?

В этом блокноте мы рассмотрим функции пакета tidyr, позволяющие привести данные к “хорошо упорядоченному” формату:

  • gather() - свертка столбцов в строки;
  • spread() - развертка строк в столбцы;
  • separate() - разделение значений одного столбца на несколько;
  • unite() - объединение значений нескольких столбцов в один.

Более подробно познакомиться с проблемой реструктурирования данных в следующих источниках:

Свертка столбцов в строки с помощью функции gather()

Одной из проблем набора данных может быть то, что в названиях столбцов содержатся не названия переменных, а значения переменных. Чтобы привести данные к удобному формату, нужно собрать (gather) данные столбцов в две новые переменные.

Работа функции gather()

Функция gather() имеет следующие параметры:

  • data - набор данных;
  • перечень столбцов, которые представляют собой значения переменных, а не их названия;
  • key - название новой переменной, в которой будут храниться названия старых столбцов;
  • value - название новой переменной, в которой будут храниться значения ячеек старых столбцов.

Пример gather()

В наборе данных table4a названия столбцов 1999 и 2000 являются значениями переменной year. С помощью функции gather() данные столбцов будут свернуты в две переменные: year с названиями столбцов 1999 и 2000 и cases с значениями ячеек столбцов 1999 и 2000.

table4a
## # A tibble: 3 x 3
##   country     `1999` `2000`
## * <chr>        <int>  <int>
## 1 Afghanistan    745   2666
## 2 Brazil       37737  80488
## 3 China       212258 213766
gather(table4a, `1999`,`2000`, key = "year", value = "cases")
## # A tibble: 6 x 3
##   country     year   cases
##   <chr>       <chr>  <int>
## 1 Afghanistan 1999     745
## 2 Brazil      1999   37737
## 3 China       1999  212258
## 4 Afghanistan 2000    2666
## 5 Brazil      2000   80488
## 6 China       2000  213766

Упражнения gather()

Произведите свертку столбцов следующего набора данных:

sales <- tibble(
  goods = c("Milk", "Cheese", "Butter", "Sour cream"),
  moscow = c(350,430,360,570),
  `st-petersburg` = c(210,270,150,250),
  novosibirsk = c(120,150,210,140),
  total = c(680,850,720,960)
)
sales
## # A tibble: 4 x 5
##   goods      moscow `st-petersburg` novosibirsk total
##   <chr>       <dbl>           <dbl>       <dbl> <dbl>
## 1 Milk          350             210         120   680
## 2 Cheese        430             270         150   850
## 3 Butter        360             150         210   720
## 4 Sour cream    570             250         140   960
# Напишите свой код здесь

Развертка строк в столбцы с помощью функции spread()

Иногда наблюдения “разбросаны” по нескольким строкам. В этом случае функция spread() позволяет собрать данные, относящиеся к одному наблюдению в одну строку.

Работа функции spread()

Функция spread() имеет следующие параметры:

  • data - набор данных;
  • key - название столбца, в котором содержатся названия переменных, для которых будут созданы столбцы;
  • value - название столбца, в котором содержатся значения переменных.

Пример spread()

В наборе данных table2 столбце type содержатся названия переменных cases и population, а в столбце count - значения переменных. С помощью функции spread() создадим переменные cases и population и перенесем в них значения из столбца count:

table2
## # A tibble: 12 x 4
##    country      year type            count
##    <chr>       <int> <chr>           <int>
##  1 Afghanistan  1999 cases             745
##  2 Afghanistan  1999 population   19987071
##  3 Afghanistan  2000 cases            2666
##  4 Afghanistan  2000 population   20595360
##  5 Brazil       1999 cases           37737
##  6 Brazil       1999 population  172006362
##  7 Brazil       2000 cases           80488
##  8 Brazil       2000 population  174504898
##  9 China        1999 cases          212258
## 10 China        1999 population 1272915272
## 11 China        2000 cases          213766
## 12 China        2000 population 1280428583
spread(table2, key = type, value = count)
## # A tibble: 6 x 4
##   country      year  cases population
##   <chr>       <int>  <int>      <int>
## 1 Afghanistan  1999    745   19987071
## 2 Afghanistan  2000   2666   20595360
## 3 Brazil       1999  37737  172006362
## 4 Brazil       2000  80488  174504898
## 5 China        1999 212258 1272915272
## 6 China        2000 213766 1280428583

Упражнения spread()

Произведите развертку строк следующего набора данных:

grades <- tribble(
~name, ~subject, ~grade,
#-----------------|--------|------
"Ivan Ivanov", "Logistics", 4,
"Ivan Ivanov", "Research seminar", 5,
"Ivan Ivanov", "Logistics", 10,
"Peter Petrov", "Logistics", 8,
"Peter Petrov", "Research seminar", 8
)
grades
## # A tibble: 5 x 3
##   name         subject          grade
##   <chr>        <chr>            <dbl>
## 1 Ivan Ivanov  Logistics            4
## 2 Ivan Ivanov  Research seminar     5
## 3 Ivan Ivanov  Logistics           10
## 4 Peter Petrov Logistics            8
## 5 Peter Petrov Research seminar     8
# Напишите свой код здесь

Разделение значений столбца с помощью функции separate()

Иногда значения нескольких переменных могут быть записаны в одном столбце через разделитель. В этом случае для обработки значений этих переменных необходимо разнести их по столбцам. Справиться с этой проблемой позволяет функция separate().

Работа функции separate()

Функция separate() имеет следующие параметры:

  • data - набор данных;
  • col - название столбца, содержащего значения, которые нужно разделить;
  • into - вектор названий столбцов, в которые будут помещены разделенные значения;
  • sep - разделитель значений, по умолчанию - любой встречающийся символ, не являющийся буквой или цифрой;
  • convert - если параметр равен TRUE, то разделенные значения будут конвертированы в наиболее подходящие типы. Параметр особенно полезен, когда разделяемые значения - числовые.

Пример separate()

В наборе данных table3 в столбце rate записаны через разделитель “/” значения переменных cases и population. Разнесем их в соответствующие столбцы.

table3
## # A tibble: 6 x 3
##   country      year rate             
## * <chr>       <int> <chr>            
## 1 Afghanistan  1999 745/19987071     
## 2 Afghanistan  2000 2666/20595360    
## 3 Brazil       1999 37737/172006362  
## 4 Brazil       2000 80488/174504898  
## 5 China        1999 212258/1272915272
## 6 China        2000 213766/1280428583
separate(table3, rate, into = c("cases", "population"))
## # A tibble: 6 x 4
##   country      year cases  population
##   <chr>       <int> <chr>  <chr>     
## 1 Afghanistan  1999 745    19987071  
## 2 Afghanistan  2000 2666   20595360  
## 3 Brazil       1999 37737  172006362 
## 4 Brazil       2000 80488  174504898 
## 5 China        1999 212258 1272915272
## 6 China        2000 213766 1280428583
separate(table3, rate, into = c("cases", "population"), convert = TRUE)
## # A tibble: 6 x 4
##   country      year  cases population
##   <chr>       <int>  <int>      <int>
## 1 Afghanistan  1999    745   19987071
## 2 Afghanistan  2000   2666   20595360
## 3 Brazil       1999  37737  172006362
## 4 Brazil       2000  80488  174504898
## 5 China        1999 212258 1272915272
## 6 China        2000 213766 1280428583

Упражнения separate()

Разделите значения в следующего набора данных:

addresses <- tibble(address = c(
  "123022, г. Москва, Б. Трехсвятительский пер., д.3",
  "105187, г. Москва, ул. Кирпичная, д.33",
  "100100, г. Москва, ул. Мясницкая, д.13, стр. 4"
)
)

# Напишите свой код здесь

Объединение значений столбцов с помощью функции unite()

Функция unite() позволяет соединить значения нескольких столбцов в один.

Работа функции unite()

Функция имеет следующие параметры:

  • data - набор данных;
  • col - название нового столбца, в котором будут соединенные значения;
  • перечень столбцов, из которых будут соединяться значения;
  • sep - разделитель значений в новом столбце, по умолчанию "_".

Пример unite()

В наборе данных table5 первые и последние две цифры года разнесены по столбцам century и year. Соединим их в столбце new.

table5
## # A tibble: 6 x 4
##   country     century year  rate             
## * <chr>       <chr>   <chr> <chr>            
## 1 Afghanistan 19      99    745/19987071     
## 2 Afghanistan 20      00    2666/20595360    
## 3 Brazil      19      99    37737/172006362  
## 4 Brazil      20      00    80488/174504898  
## 5 China       19      99    212258/1272915272
## 6 China       20      00    213766/1280428583
unite(table5, new, century, year, sep = "")
## # A tibble: 6 x 3
##   country     new   rate             
##   <chr>       <chr> <chr>            
## 1 Afghanistan 1999  745/19987071     
## 2 Afghanistan 2000  2666/20595360    
## 3 Brazil      1999  37737/172006362  
## 4 Brazil      2000  80488/174504898  
## 5 China       1999  212258/1272915272
## 6 China       2000  213766/1280428583

Упражнения unite()

Используя функцию unite(), создайте столбец, в котором для каждой даты проставлено соответствие времени года и год. Например, для даты “01-01-2017” - “зима 2017”.

dates <- tibble(date = c("02-01-2015", "05-04-2015", "27-09-2017", "18-07-2016","09-12-2014"))

# Напишите свой код здесь