воскресенье, 26 июля 2015 г.

Dplyr. Data frames (перевод)

Хэдли Уикхэм (Hadley Wickham)  - мега-мужик, и я решил поучаствовать в переводе на русский язык документации по его пакетам. Начало было здесь: Введение в dplyr.


Перевод

https://cran.r-project.org/web/packages/dplyr/vignettes/data_frames.html

data_frame() представляет собой привлекательный способ создания таблиц данных (data frames). Эта функция объединяет лучшие практики создания таблиц данных:
  • Никогда не меняет тип подаваемых на вход данных (т.е. больше никаких stringsAsFactors = FALSE!)
data.frame(x = letters) %>% sapply(class)
##        x 
## "factor"
data_frame(x = letters) %>% sapply(class)
##           x 
## "character"
Это упрощает её использование со списками-колонками (list-columns; вспоминаем, что в R таблица данных тоже является списком - прим. пер.):
data_frame(x = 1:3, y = list(1:5, 1:10, 1:20))
## Source: local data frame [3 x 2]
## 
##   x         y
## 1 1  <int[5]>
## 2 2 <int[10]>
## 3 3 <int[20]>
Списки-колонки чаще всего создаются при помощи do(), но могут быть полезны и при создании вручную.
  • Никогда не исправляет имена переменных:
data.frame(`crazy name` = 1) %>% names()
## [1] "crazy.name"
data_frame(`crazy name` = 1) %>% names()
## [1] "crazy name"
  • Оценивает аргументы “лениво” и по порядку:
data_frame(x = 1:5, y = x ^ 2)
## Source: local data frame [5 x 2]
## 
##   x  y
## 1 1  1
## 2 2  4
## 3 3  9
## 4 4 16
## 5 5 25
  • Добавляет класс tbl_df() для результата, так что если вы случайно напечатаете большую таблицу, то получите только первые несколько строк.
data_frame(x = 1:5) %>% class()
## [1] "tbl_df"     "tbl"        "data.frame"
  • Никогда не использует row.names(), потому что весь смысл “чистых” данных состоит в согласованном хранении переменных, поэтому мы не должны помещать одну переменную в специальный атрибут.
  • Повторяет только векторы единичной длины. Повторение (recycling) векторов другой длины является частым источником багов.

Превращение

В дополнение к data_frame() dplyr предлагает функцию as_data_frame() для приведения списков в формат таблиц данных. Она делает две вещи:
  • Проверяет, что исходный список является пригодным для таблицы данных, т.е. что каждый элемент имеет имя, является одномерным атомарным вектором или списком, и все элементы имеют одинаковую длину.
  • Устанавливает класс и атрибуты списка, чтобы заставить его вести себя как таблицу данных. Эта модификация не требует полной копии исходного списка и поэтому работает очень быстро.
Это гораздо проще, чем as.data.frame(). Тяжело в точности объяснить, что именно делает as.data.frame(), но это действие аналогично do.call(cbind, lapply(x, data.frame)) - т.е. происходит превращение каждого компонента в таблицу данных и объединение их при помощи cbinds(). Следовательно,as_data_frame() работает гораздо быстрее as.data.frame():
l2 <- replicate(26, sample(100), simplify = FALSE)
names(l2) <- letters
microbenchmark::microbenchmark(
  as_data_frame(l2),
  as.data.frame(l2)
)
## Unit: microseconds
##               expr      min        lq      mean   median        uq
##  as_data_frame(l2)  187.599  196.4225  257.9634  218.479  238.9105
##  as.data.frame(l2) 2142.070 2199.6500 2537.3101 2361.941 2575.5450
##       max neval cld
##  2402.573   100  a 
##  4760.567   100   b
Скорость as.data.frame() обычно не является “бутылочным горлышком” при интерактивной работе, но может быть проблемой при комбинировании тысяч беспорядочных источников в одну “чистую” таблицу.

Память

Одной из причин быстроты dplyr является осторожность при создании копий столбцов. Этот раздел описывает, как это работает, и предоставляет вам несколько полезных средств для понимания использования памяти таблицами данных в R.
Первым средством, которое мы используем, является функция dplyr::location(). Она сообщает нам три вещи о таблице данных:
  • где сам объект находится в памяти
  • где расположен каждый столбец
  • где расположен каждый атрибут
location(iris)
## <06AD7450>
## Variables:
##  * Sepal.Length: <07891458>
##  * Sepal.Width:  <07891928>
##  * Petal.Length: <07891DF8>
##  * Petal.Width:  <078922C8>
##  * Species:      <07892798>
## Attributes:
##  * names:        <06AD7418>
##  * row.names:    <07895398>
##  * class:        <07655450>
Полезно знать адрес памяти, поскольку, если адрес изменился, то вы знаете, что R создал копию. Копии - это плохо, поскольку копирование вектора требует времени. Это обычно не является “бутылочным горлышком”, если вы имеете несколько тысяч значений, но если их миллионы или десятки миллионов, то требуется значительное количество времени. Ненужные копии также плохи тем, что расходуют память.
R старается избегать создания копий, когда это возможно. Например, если вы присвоите iris другой переменной, она по-прежнему будет ссылаться на тот же адрес:
iris2 <- iris
location(iris2)
## <06AD7450>
## Variables:
##  * Sepal.Length: <07891458>
##  * Sepal.Width:  <07891928>
##  * Petal.Length: <07891DF8>
##  * Petal.Width:  <078922C8>
##  * Species:      <07892798>
## Attributes:
##  * names:        <06AD7418>
##  * row.names:    <078FFF98>
##  * class:        <07655450>
Вместо тщательного сравнения длинных ячеек памяти мы можем использовать функцию dplyr::changes()для описания изменений между двумя версиями таблицы данных. Она покажет нам, что iris и iris2 идентичны - они ссылаются на одно и то же место в памяти.
changes(iris2, iris)
## <identical>
Как вы думаете, что случится, если вы измените отдельный столбец в iris2? R 3.1.0 умеет изменять только один столбец, оставляя остальные ссылающимися на существующее расположение:
iris2$Sepal.Length <- iris2$Sepal.Length * 2
changes(iris, iris2)
## Changed variables:
##              old      new     
## Sepal.Length 07891458 0612A0C8
## 
## Changed attributes:
##              old      new     
## row.names    03D40EB8 03D5F180
(Этого не было до R 3.1.0: R создавал полную копию целой таблицы данных.)
dplyr также умен:
iris3 <- mutate(iris, Sepal.Length = Sepal.Length * 2)
changes(iris3, iris)
## Changed variables:
##              old      new     
## Sepal.Length 03737270 07891458
## 
## Changed attributes:
##              old      new     
## class        06304EC0 07655450
## names        06582508 06AD7418
## row.names    07687198 03CF1628
Он достаточно умен для создания только одного столца: остальные столбцы продолжают ссылаться на их старые расположения. Вы могли заметить, что атрибуты по-прежнему были скопированы. Это слабо влияет на производительность, посольку атрибуты обычно являются короткими векторами и их копирования делает внутренний код dplyr значительно проще.
dplyr никогда не делает копии, до тех пор, пока:
  • tbl_df() и group_by() не создадут копию столбца
  • select() никогда не копируют столбцы, даже когда вы их переименовываете
  • mutate() никогда не копируют столбцы, даже когда вы изменяете существующие столбцы
  • arrange() должна создавать копию, потому что вы меняете порядок каждого столбца. Это дорогостоящая операция для больших данных, но вы можете избегать её, используя порядковый аргумент “оконных функций” (https://cran.r-project.org/web/packages/dplyr/vignettes/window-functions.html).
  • summarise() создает новые данные, но они обычно как минимум на порядок меньше исходных.
Это означает, что dplyr позволяет вам работать с таблицами данных с очень маленькими накладными расходами памяти.
data.table развивает эту идею на один шаг дальше, чем dplyr, и предоставляет функции для модификации таблицы данных на месте. Это позволяет избежать необходимости копировать указатели на существующие столбцы и атрибуты и опеспечивает ускорение, когда у вас много столбцов. dplyr не делает этого с таблицами данных (хотя и может), потому что я считаю, что безопаснее сохранять данные неизменяемыми: все методы dplyr для таблицы данных возвращают новую таблицу данных, даже когда они объединяют так много данных, насколько это возможно.

Комментариев нет:

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