27 июля 2014

Знакомьтесь - tidyr



На днях в официальном блоге RStudio проф. Хэдли Уикхэм объявил о выходе своего нового пакета - tidyr, функции которого предназначены для подготовки "опрятных" (англ. tidy) данных. Ниже приведен перевод этого объявления.




"Опрятные" данные - это данные, с которыми легко работать: их легко преобразовать (например. примощи dplyr), визуализировать (при помощи ggplot2 или ggvis) и использовать для построения модели (при помощи сотен R-пакетов). Два наиболее важных свойства таких данных состоят в следующем:
  • Каждый столбец в таблице соответствует одной переменной;
  • Каждая строка соответствует одному наблюдению.
Приведение данных к такому формату значительно облегчает работу, поскольку он обеспечивает  единообразный подход для обращения к переменным (по именам столбцов) и наблюдениям (по номерам строк). Используя "опрятные" данные и соответствующие программные инструменты, вы тратите меньше времени на то, чтобы решить, как подать результаты вычислений одной функции на другую функцию, и в результате у вас появляется больше времени для изучения свойств данных.

Для преобразования плохо упорядоченных данных вы сначала определяете интересующие вас переменные, а затем используете средства tidyr, чтобы сделать из этих переменных самостоятельные столбцы. Пакет tidyr содержит три основные функции для преобразования плохо организованных данных: gather() ("собирать"), separate() ("разделять") и spread() ("распределять").

Функция gather() принимает несколько столбцов и преобразует их в пары "ключ - значение", в результате чего "широкие" таблицы данных превращаются в "длинные". Идентичные по действию функции имеются также в других пакетах: melt() (из пакета reshape2), pivot() (spreadsheets) и  fold() (databases). Вот пример использования gather() на вымышленном наборе данных. В этом гипотетическом экперименте мы дали испытуемым два разных лекарственных препарата и затем измерили у них частоту сердечных сокращений:

library(tidyr)
library(dplyr)
 
messy <- data.frame(
  name = c("Wilbur", "Petunia", "Gregory"),
  a = c(67, 80, 64),
  b = c(56, 90, 50)
)
 
messy
     name  a  b
1  Wilbur 67 56
2 Petunia 80 90
3 Gregory 64 50

Таким образом, у нас есть три переменные (имя испытуемого, название препарата и частота сокращений), однако в приведенной выше таблице messy только переменная "имя пациента" (name) представлена в виде отдельного столбца. Применим функцию gather() для преобразования столбцов a и b в пары "ключ - значение" (т.е. "препарат (drug) - частота сокращений (heartrate)"):

messy %>% gather(drug, heartrate, a:b)
 
     name drug heartrate
1  Wilbur    a        67
2 Petunia    a        80
3 Gregory    a        64
4  Wilbur    b        56
5 Petunia    b        90
6 Gregory    b        50

Иногда две переменные бывают представлены в виде одного столбца. Функция separate() позволяет разделить их (см. также ?extract - эта функция выполняет разделение, используя регулярные выражения). Рассмотрим (вымышленный) пример, приведенный ранее на сайте Stackoverflow. Имеются измерения длительности разговоров по мобильному телефону, сделанных испытуемыми в двух местах - дома и на работе, в два разных периода времени. Формирование этих четырех групп испытуемых было выполнено случайным образом.

set.seed(10)
messy <- data.frame(
  id = 1:4,
  trt = sample(rep(c('control', 'treatment'), each = 2)),
  work.T1 = runif(4),
  home.T1 = runif(4),
  work.T2 = runif(4),
  home.T2 = runif(4)
)

Для преобразования этих плохо упорядоченных данных мы сначала применим gather(), чтобы превратить столбцы work.T1, home.T1, work.T2 и home.T2 в соответствующие пары "значение - ключ" (для экономии места ниже приведены только первые 8 строк итоговой таблицы):

tidier <- messy %>% gather(key, time, -id, -trt)
 
tidier %>% head(8)
  id       trt     key       time
1  1 treatment work.T1 0.08513597
2  2   control work.T1 0.22543662
3  3 treatment work.T1 0.27453052
4  4   control work.T1 0.27230507
5  1 treatment home.T1 0.61582931
6  2   control home.T1 0.42967153
7  3 treatment home.T1 0.65165567
8  4   control home.T1 0.56773775

Дальше мы применим функцию separate(), чтобы разделить key на местоположение (location) и период времени (Time), используя регулярное выражение, которое соответствует знаку-разделителю соответствующих значений (т.е. точке):

tidy <- tidier %>% separate(key, into = c("location", "Time"), sep = "\\.") 
 
tidy %>% head(8)
  id       trt location Time       time
1  1 treatment     work   T1 0.08513597
2  2   control     work   T1 0.22543662
3  3 treatment     work   T1 0.27453052
4  4   control     work   T1 0.27230507
5  1 treatment     home   T1 0.61582931
6  2   control     home   T1 0.42967153
7  3 treatment     home   T1 0.65165567
8  4   control     home   T1 0.56773775

Последний инструмент - функция spread() - принимает два столбца (пара "ключ - значение") и "разносит" их по разным столбцам, превращая "длинную" таблицу в "широкую". В других пакетах имеются аналогичные функции: cast() (reshape2), unpivot() (spreadsheets) и unfold() (databases). Функция spread() используется в случаях, когда переменные образуют строки вместо столбцов. Эта функция будет применяться реже, чем gather() или separate() и поэтому для получения дополнительной информации следует обратиться к справочной документации и приведенным там примерам.

Точно так же, как пакет reshape2 обладал меньшей функциональностью по сравнению с reshape, пакет tidyr предоставляет меньше возможностей, чем reshape2. Он предназначен исключетельно для подготовки "опрятных" данных, а не для переформатирования таблиц в целом. В частности, имеющиеся функции работают только с таблицами данных и не позволяют аггрегировать данные (например, по средним значениям). Это упрощает функции tidyr: каждая из них делает только одно дело, но делает его хорошо. Для более сложных операций следует объединять возможности tidyr и dplyr, используя оператор %>%.

Дополнительую информацию по концепции "опрятных данных" можно найти в соответствующей статье Хэдли Уикхэма. См. также руководство, доступное по команде vignette("tidy-data"), и примеры, доступные по команде demo(package = "tidyr"). Кроме того, обратите внимание на отличные ответы по этой теме на сайте Stackoverflow.


1 комментарий :

Анонимный комментирует...

По очистке и манипуляции с данными есть неплохое руководство: http://cran.r-project.org/doc/contrib/de_Jonge+van_der_Loo-Introduction_to_data_cleaning_with_R.pdf

Отправить комментарий