Хэдли Уикхэм (Hadley Wickham) - мега-мужик, и я решил поучаствовать в переводе на русский язык документации по его пакетам. Начало было здесь: Введение в dplyr.
Data frames
2015-06-15
Перевод
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()
создает новые данные, но они обычно как минимум на порядок меньше исходных.
data.table развивает эту идею на один шаг дальше, чем dplyr, и предоставляет функции для модификации таблицы данных на месте. Это позволяет избежать необходимости копировать указатели на существующие столбцы и атрибуты и опеспечивает ускорение, когда у вас много столбцов. dplyr не делает этого с таблицами данных (хотя и может), потому что я считаю, что безопаснее сохранять данные неизменяемыми: все методы dplyr для таблицы данных возвращают новую таблицу данных, даже когда они объединяют так много данных, насколько это возможно.
Комментариев нет:
Отправить комментарий