Семантика ссылок
translated by Andrey Ogurtsov
2015-06-24
i
и by
. Она предназначена для тех, кто уже знаком с синтаксисом data.table, его общим видом, способом выбора строк в i
, выбором и вычислением столбцов, выполнением агрегирования по группам. Если вы не знакомы с этими концепциями, пожалуйста, прочтите сперва виньетку “Введение в data.table”.Данные
Мы будем использовать набор данныхflights
, так же как в виньетке “Введение в data.table”.flights <- fread("https://raw.githubusercontent.com/wiki/arunsrinivasan/flights/NYCflights14/flights14.csv")
flights
# year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight
# 1: 2014 1 1 914 14 1238 13 0 AA N338AA 1
# 2: 2014 1 1 1157 -3 1523 13 0 AA N335AA 3
# 3: 2014 1 1 1902 2 2224 9 0 AA N327AA 21
# 4: 2014 1 1 722 -8 1014 -26 0 AA N3EHAA 29
# 5: 2014 1 1 1347 2 1706 1 0 AA N319AA 117
# ---
# 253312: 2014 10 31 1459 1 1747 -30 0 UA N23708 1744
# 253313: 2014 10 31 854 -5 1147 -14 0 UA N33132 1758
# 253314: 2014 10 31 1102 -8 1311 16 0 MQ N827MQ 3591
# 253315: 2014 10 31 1106 -4 1325 15 0 MQ N511MQ 3592
# 253316: 2014 10 31 824 -5 1045 1 0 MQ N813MQ 3599
# origin dest air_time distance hour min
# 1: JFK LAX 359 2475 9 14
# 2: JFK LAX 363 2475 11 57
# 3: JFK LAX 351 2475 19 2
# 4: LGA PBI 157 1035 7 22
# 5: JFK LAX 350 2475 13 47
# ---
# 253312: LGA IAH 201 1416 14 59
# 253313: EWR IAH 189 1400 8 54
# 253314: LGA RDU 83 431 11 2
# 253315: LGA DTW 75 502 11 6
# 253316: LGA SDF 110 659 8 24
dim(flights)
# [1] 253316 17
Введение
В этой виньетке мы:- сперва коротко обсудим семантику ссылок и рассмотрим две разные формы использования оператора
:=
- затем увидим, как мы можем добавлять/обновлять/удалять столбцы по ссылке в
j
с использованием:=
, и как это комбинировать сi
иby
- и, наконец, мы увидим, как использовать оператор
:=
ради его побочного эффекта, и как мы можем его избежать при помощиcopy()
.
1. Семантика ссылок
Результатом всех операций, которые мы видели в предыдущей виньетке, был новый набор данных. Мы увидим, как добавлять новые столбцы, обновлять или удалять существующие столбцы в исходных данных.a) Бэкграунд
Прежде чем заняться семантикой ссылок, рассмотрим следующую таблицу data.frame:DF = data.frame(ID = c("b","b","b","a","a","c"), a = 1:6, b = 7:12, c=13:18)
DF
# ID a b c
# 1 b 1 7 13
# 2 b 2 8 14
# 3 b 3 9 15
# 4 a 4 10 16
# 5 a 5 11 17
# 6 c 6 12 18
Когда вы выполняем:DF$c <- 18:13 # (1) -- replace entire column
# or
DF$c[DF$ID == "b"] <- 15:13 # (2) -- subassign in column 'c'
и (1), и (2) приводят к созданию глубокой копии всей таблицы data.frame в R
версии < 3.1
. Данные копируются больше одного раза. Для увеличения производительности путем избегания этих ненужных копий data.table использует доступный, но неиспользуемый в R оператор :=
.Большое увеличение производительности было сделано в
R v3.1
, в результате чего в случае (1) создается поверхностная, а не глубокая копия. Тем не менее, для (2) по-прежнему создается глубокая копия всего столбца даже в R v3.1+
. Это означает, что чем больше столбцов участвуют в частичном присвоении в одном запросе, тем более глубокие копии создает R.Поверхностная копия против глубокой копии
Поверхностная копия является всего лишь копией вектора-указателя столбцов (в соответствии со столбцами в data.frame или data.table). Настоящие данные физически не копируются в памяти.С другой стороны, глубокая копия создает новую копию всех данных в новой области памяти.
С использованием оператора
:=
в data.table никакие копии не создаются ни в случае (1), ни в случае (2) независимо от используемой версии R. Причина этого в том, что оператор :=
на месте обновляет столбцы data.table (по ссылке).
b) Оператор :=
Может быть использован в j
двумя способами:- Форма
LHS := RHS
DT[, c("colA", "colB", ...) := list(valA, valB, ...)]
# when you have only one column to assign to you
# can drop the quotes and list(), for convenience
DT[, colA := valA]
- Функциональная форма
DT[, `:=`(colA = valA, # valA is assigned to colA
colB = valB, # valB is assigned to colB
...
)]
Обратите внимаени, что приведенныый выше код объясняет, как можно использовать :=
. Это не рабочие примеры. Мы начнем использовать этот оператор с таблицей data.table flights
в следующем разделе.- Форма (a) удобна для программирования и особенно полезна, когда вы не знаете заранее столбцы для присваивания значений.
- С другой стороны, форма (b) удобна, когда вы хотите записать комментарии на будущее.
- Результат возвращается скрыто.
- Поскольку оператор
:=
доступен вj
, мы можем комбинировать его с операциямиi
иby
, подобно операциям агрегирования, которые мы видели в предыдущей виньетке.
:=
, показанных выше, мы не присваиваем результат переменной, потому что не нуждаемся в этом. Исходная таблица data.table изменяется по ссылке. Давайте рассмотрим примеры, чтобы понять, что под этим подразумевается.В оставшейся части виньетки мы будем работать с набором данных
flights
.2. Добавление/обновление/удаление столбцов по ссылке
a) Добавление столбцов по ссылке
- Как мы можем добавить столбцы скорость и общая задержка каждого рейса в таблицу data.table flights
?
flights[, `:=`(speed = distance / (air_time/60), # speed in km/hr
delay = arr_delay + dep_delay)] # delay in minutes
head(flights)
# year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014 1 1 914 14 1238 13 0 AA N338AA 1 JFK
# 2: 2014 1 1 1157 -3 1523 13 0 AA N335AA 3 JFK
# 3: 2014 1 1 1902 2 2224 9 0 AA N327AA 21 JFK
# 4: 2014 1 1 722 -8 1014 -26 0 AA N3EHAA 29 LGA
# 5: 2014 1 1 1347 2 1706 1 0 AA N319AA 117 JFK
# 6: 2014 1 1 1824 4 2145 0 0 AA N3DEAA 119 EWR
# dest air_time distance hour min speed delay
# 1: LAX 359 2475 9 14 413.6490 27
# 2: LAX 363 2475 11 57 409.0909 10
# 3: LAX 351 2475 19 2 423.0769 11
# 4: PBI 157 1035 7 22 395.5414 -34
# 5: LAX 350 2475 13 47 424.2857 3
# 6: LAX 339 2454 18 24 434.3363 4
## alternatively, using the 'LHS := RHS' form
# flights[, c("speed", "delay") := list(distance/(air_time/60), arr_delay + dep_delay)]
Обратите внимание
- Мы не присвоили разультат переменной
flights
. - Таблица
flights
теперь содержит два новых столбца. Это то, что мы подразумеваем под добавлением по ссылке. - Мы использовали функциональную форму, так что мы можем добавлять комментарии сбоку для объяснения, что делают эти вычисления. Вы также можете видеть форму
LHS := RHS
(закомментированную).
b) Обновление некоторых строк в столбцах по ссылке - частичное присваивание по ссылке
Давайте взглянем на все значенияhours
, доступные в таблице data.table flights
:# get all 'hours' in flights
flights[, sort(unique(hour))]
# [1] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
Мы видим, что имеется 25 уникальных значений - есть и 0, и 24. Давайте заменим 24 на 0.
– Заменить строки, где hour == 24
, на 0
# subassign by reference
flights[hour == 24L, hour := 0L]
- Мы можем использовать
i
вместе с:=
вj
тем же способом, как мы видели в виньетке “Введение в data.table”. - Столбец
hour
заменяется0
только для индексов строк, для которых условиеhour == 24L
, определенное вi
, возвращаетTRUE
. :=
возвращает результат скрыто. Иногда бывает нужно увидеть результат после присваивания. Мы можем добиться этого, добавив пустой оператор[]
в конце запроса, как показано ниже:
flights[hour == 24L, hour := 0L][]
# year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight
# 1: 2014 1 1 914 14 1238 13 0 AA N338AA 1
# 2: 2014 1 1 1157 -3 1523 13 0 AA N335AA 3
# 3: 2014 1 1 1902 2 2224 9 0 AA N327AA 21
# 4: 2014 1 1 722 -8 1014 -26 0 AA N3EHAA 29
# 5: 2014 1 1 1347 2 1706 1 0 AA N319AA 117
# ---
# 253312: 2014 10 31 1459 1 1747 -30 0 UA N23708 1744
# 253313: 2014 10 31 854 -5 1147 -14 0 UA N33132 1758
# 253314: 2014 10 31 1102 -8 1311 16 0 MQ N827MQ 3591
# 253315: 2014 10 31 1106 -4 1325 15 0 MQ N511MQ 3592
# 253316: 2014 10 31 824 -5 1045 1 0 MQ N813MQ 3599
# origin dest air_time distance hour min speed delay
# 1: JFK LAX 359 2475 9 14 413.6490 27
# 2: JFK LAX 363 2475 11 57 409.0909 10
# 3: JFK LAX 351 2475 19 2 423.0769 11
# 4: LGA PBI 157 1035 7 22 395.5414 -34
# 5: JFK LAX 350 2475 13 47 424.2857 3
# ---
# 253312: LGA IAH 201 1416 14 59 422.6866 -29
# 253313: EWR IAH 189 1400 8 54 444.4444 -19
# 253314: LGA RDU 83 431 11 2 311.5663 8
# 253315: LGA DTW 75 502 11 6 401.6000 11
# 253316: LGA SDF 110 659 8 24 359.4545 -4
Давайте взглянем на все значения hours
для проверки.# check again for '24'
flights[, sort(unique(hour))]
# [1] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
c) Удаление столбца по ссылке
– Удаление столбца delay
flights[, c("delay") := NULL]
head(flights)
# year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014 1 1 914 14 1238 13 0 AA N338AA 1 JFK
# 2: 2014 1 1 1157 -3 1523 13 0 AA N335AA 3 JFK
# 3: 2014 1 1 1902 2 2224 9 0 AA N327AA 21 JFK
# 4: 2014 1 1 722 -8 1014 -26 0 AA N3EHAA 29 LGA
# 5: 2014 1 1 1347 2 1706 1 0 AA N319AA 117 JFK
# 6: 2014 1 1 1824 4 2145 0 0 AA N3DEAA 119 EWR
# dest air_time distance hour min speed
# 1: LAX 359 2475 9 14 413.6490
# 2: LAX 363 2475 11 57 409.0909
# 3: LAX 351 2475 19 2 423.0769
# 4: PBI 157 1035 7 22 395.5414
# 5: LAX 350 2475 13 47 424.2857
# 6: LAX 339 2454 18 24 434.3363
## or using the functional form
# flights[, `:=`(delay = NULL)]
- Присвоение значения
NULL
столбцу удаляет его. И это происходит мгновенно. - Мы можем такое передавать имена столбцов вместо их имен в
LHS
, хотя хорошая практика программирования заключается в использовании имен. - Когда нужно удалить всего один столбец, мы можем опустить
c()
и двойные кавычки и использовать для удобства просто имя столбца. Эквивалент кода выше:
flights[, delay := NULL]
d) :=
вместе с группировкой при помощи by
В разделе 2b мы уже видели, как использовать :=
совместно с i
. Давайте посмотрим, как мы можем использовать :=
в сочетании с by
.
- Как мы можем добавить новый столбец, содержащий максимальную скорость для каждой пары origin, dest
?
flights[, max_speed := max(speed), by=.(origin, dest)]
head(flights)
# year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014 1 1 914 14 1238 13 0 AA N338AA 1 JFK
# 2: 2014 1 1 1157 -3 1523 13 0 AA N335AA 3 JFK
# 3: 2014 1 1 1902 2 2224 9 0 AA N327AA 21 JFK
# 4: 2014 1 1 722 -8 1014 -26 0 AA N3EHAA 29 LGA
# 5: 2014 1 1 1347 2 1706 1 0 AA N319AA 117 JFK
# 6: 2014 1 1 1824 4 2145 0 0 AA N3DEAA 119 EWR
# dest air_time distance hour min speed max_speed
# 1: LAX 359 2475 9 14 413.6490 526.5957
# 2: LAX 363 2475 11 57 409.0909 526.5957
# 3: LAX 351 2475 19 2 423.0769 526.5957
# 4: PBI 157 1035 7 22 395.5414 517.5000
# 5: LAX 350 2475 13 47 424.2857 526.5957
# 6: LAX 339 2454 18 24 434.3363 518.4507
- Мы добавляем новый столбец
max_speed
по ссылке, используя оператор:=
. - Мы задаем столбцы для группировки, как было показано в виньетке “Введение в data.table”. Для каждой группы было вычислено выражение
max(speed)
, которое возвращает единственное значение. Это выражение повторяется, чтобы соответствовать длине группы. Еще раз: никакие копии не создаются. Таблица data.tableflights
изменяется на месте. - Мы также можем задать
by
как символьный вектор, как мы видели в виньетке “Введение в data.table”, например,by = c("origin", "dest")
.
e) Множественные столбцы и :=
- Как мы может добавить еще две колонки, рассчитав max()
для dep_delay
и arr_delay
для каждого месяца, используя .SD
?
in_cols = c("dep_delay", "arr_delay")
out_cols = c("max_dep_delay", "max_arr_delay")
flights[, c(out_cols) := lapply(.SD, max), by = month, .SDcols = in_cols]
head(flights)
# year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014 1 1 914 14 1238 13 0 AA N338AA 1 JFK
# 2: 2014 1 1 1157 -3 1523 13 0 AA N335AA 3 JFK
# 3: 2014 1 1 1902 2 2224 9 0 AA N327AA 21 JFK
# 4: 2014 1 1 722 -8 1014 -26 0 AA N3EHAA 29 LGA
# 5: 2014 1 1 1347 2 1706 1 0 AA N319AA 117 JFK
# 6: 2014 1 1 1824 4 2145 0 0 AA N3DEAA 119 EWR
# dest air_time distance hour min speed max_speed max_dep_delay max_arr_delay
# 1: LAX 359 2475 9 14 413.6490 526.5957 973 996
# 2: LAX 363 2475 11 57 409.0909 526.5957 973 996
# 3: LAX 351 2475 19 2 423.0769 526.5957 973 996
# 4: PBI 157 1035 7 22 395.5414 517.5000 973 996
# 5: LAX 350 2475 13 47 424.2857 526.5957 973 996
# 6: LAX 339 2454 18 24 434.3363 518.4507 973 996
- Мы используем форму
LHS := RHS
. Сохраняем имена исходных и результирующих столбцов в отдельных переменных и передаем их вSDcols
иLHS
(для лучшей читаемости). - Отметим, что поскольку мы допускаем присваивание по ссылке без заключения имени столбца в кавычки для случая отдельного столбца, как объясняется в разделе 2c, мы не можем записать
out_cols := lapply(.SD, max)
. Это приведет к добавлению единственного столбца с именемout_col
. Вместо этого мы должны использоватьc(out_cols)
или просто(out_cols)
. Использования(
вокруг имени переменной достаточно, чтобы различать эти два случая. - Форма
LHS := RHS
позволяет нам работать с несколькими столбцами. В RHS для расчетаmax
для столбцов, заданных в.SDcols
, мы используем базовую функциюlapply()
вместе с.SD
тем же способом, как мы видели ранее в виньетке “Введение в data.table”. Возвращается список из двух элементов, содержащих максимальные значенияdep_delay
иarr_delay
для каждой группы.
speed
, max_speed
, max_dep_delay
и max_arr_delay
.# RHS gets automatically recycled to length of LHS
flights[, c("speed", "max_speed", "max_dep_delay", "max_arr_delay") := NULL]
head(flights)
# year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014 1 1 914 14 1238 13 0 AA N338AA 1 JFK
# 2: 2014 1 1 1157 -3 1523 13 0 AA N335AA 3 JFK
# 3: 2014 1 1 1902 2 2224 9 0 AA N327AA 21 JFK
# 4: 2014 1 1 722 -8 1014 -26 0 AA N3EHAA 29 LGA
# 5: 2014 1 1 1347 2 1706 1 0 AA N319AA 117 JFK
# 6: 2014 1 1 1824 4 2145 0 0 AA N3DEAA 119 EWR
# dest air_time distance hour min
# 1: LAX 359 2475 9 14
# 2: LAX 363 2475 11 57
# 3: LAX 351 2475 19 2
# 4: PBI 157 1035 7 22
# 5: LAX 350 2475 13 47
# 6: LAX 339 2454 18 24
3) :=
и copy()
:=
изменяет исходный объект по ссылке. Помимо возможностей, которые мы уже обсудили, иногда мы можем захотеть использовать возможность обновления по ссылке ради его побочного эффекта. А в других случаях может быть нежелательно изменять исходный объект, и тогда мы можем использовать функцию copy()
, как мы сейчас увидим.
a) Использование оператора :=
ради его побочного эффекта
Скажем, мы хотели бы создать функцию, которая будет возвращать максимальную скорость для каждого месяца. Но, в то же время, мы хотели бы добавить столбец speed
в таблицу flight
. Мы можем написать следующую простую функцию:foo <- function(DT) {
DT[, speed := distance / (air_time/60)]
DT[, .(max_speed = max(speed)), by=month]
}
ans = foo(flights)
head(flights)
# year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014 1 1 914 14 1238 13 0 AA N338AA 1 JFK
# 2: 2014 1 1 1157 -3 1523 13 0 AA N335AA 3 JFK
# 3: 2014 1 1 1902 2 2224 9 0 AA N327AA 21 JFK
# 4: 2014 1 1 722 -8 1014 -26 0 AA N3EHAA 29 LGA
# 5: 2014 1 1 1347 2 1706 1 0 AA N319AA 117 JFK
# 6: 2014 1 1 1824 4 2145 0 0 AA N3DEAA 119 EWR
# dest air_time distance hour min speed
# 1: LAX 359 2475 9 14 413.6490
# 2: LAX 363 2475 11 57 409.0909
# 3: LAX 351 2475 19 2 423.0769
# 4: PBI 157 1035 7 22 395.5414
# 5: LAX 350 2475 13 47 424.2857
# 6: LAX 339 2454 18 24 434.3363
head(ans)
# month max_speed
# 1: 1 535.6425
# 2: 2 535.6425
# 3: 3 549.0756
# 4: 4 585.6000
# 5: 5 544.2857
# 6: 6 608.5714
- Обратите внимание, что новый столбец
speed
был добавлен в таблицу data.tableflights
, поскольку:=
выполняет операции по ссылке. ПосколькуDT
(аргумент функции) иflights
ссылаются на один и тот же объект в памяти, изменениеDT
также отражается на flights. ans
содержит максимальную скорость для каждого месяца.
b) Функция copy()
В предыдущем разделе мы использовали оператор :=
ради его побочного эффекта. Но, разумеется, это не всегда может быть желательно. Иногда мы хотели бы передать объект data.table
функции и использовать оператор :=
, но не хотели бы обновлять исходный объект. Мы можем достичь этого, используя функцию copy()
.Функция
copy()
создает глубокую копию исходного объекта, и поэтому любые последующие операции обновления по ссылке, выполняемые на скопированном объекте, не влияют на исходный объект.Есть два конкретных случая, когда функция
copy()
имеет важное значение:- В отличие от ситуации, которую мы видели в предыдущем пункте, мы можем не хотеть, чтобы таблица data.table, передаваемая функции, изменялась по ссылке. В качестве примера давайте рассмотрим задачу из предыдущего раздела, за исключением того, что не хотим изменять
flights
по ссылке.
speed
, созданный в предыдущем разделе.flights[, speed := NULL]
Теперь мы можем выполнить задачу следующим образом:foo <- function(DT) {
DT <- copy(DT) ## deep copy
DT[, speed := distance / (air_time/60)] ## doesn't affect 'flights'
DT[, .(max_speed = max(speed)), by=month]
}
ans <- foo(flights)
head(flights)
# year month day dep_time dep_delay arr_time arr_delay cancelled carrier tailnum flight origin
# 1: 2014 1 1 914 14 1238 13 0 AA N338AA 1 JFK
# 2: 2014 1 1 1157 -3 1523 13 0 AA N335AA 3 JFK
# 3: 2014 1 1 1902 2 2224 9 0 AA N327AA 21 JFK
# 4: 2014 1 1 722 -8 1014 -26 0 AA N3EHAA 29 LGA
# 5: 2014 1 1 1347 2 1706 1 0 AA N319AA 117 JFK
# 6: 2014 1 1 1824 4 2145 0 0 AA N3DEAA 119 EWR
# dest air_time distance hour min
# 1: LAX 359 2475 9 14
# 2: LAX 363 2475 11 57
# 3: LAX 351 2475 19 2
# 4: PBI 157 1035 7 22
# 5: LAX 350 2475 13 47
# 6: LAX 339 2454 18 24
head(ans)
# month max_speed
# 1: 1 535.6425
# 2: 2 535.6425
# 3: 3 549.0756
# 4: 4 585.6000
# 5: 5 544.2857
# 6: 6 608.5714
- Использование функции
copy()
не обновляет по ссылке таблицу data.tableflights
. Эта таблица не содержит столбцаspeed
. ans
содержит максимальную скорость, соответствующую каждому месяцу.
v1.9.8
. Мы коснемся этой темы еще раз в виньетке “data.table design”.- Когда мы сохраняем имена столбцов в переменной, например,
DT_n = names(DT)
, а затем добавляем/обновляем/удаляем столбцы по ссылке, это также изменитDT_n
, если мы не выполнимcopy(names(DT))
.
DT = data.table(x=1, y=2)
DT_n = names(DT)
DT_n
# [1] "x" "y"
## add a new column by reference
DT[, z := 3]
## DT_n also gets updated
DT_n
# [1] "x" "y" "z"
## use `copy()`
DT_n = copy(names(DT))
DT[, w := 4]
## DT_n doesn't get updated
DT_n
# [1] "x" "y" "z"
Резюме
- Оператор :=
- Используется для добавления/обновления/удаления столбцов по ссылке.
- Мы также увидели, как использовать
:=
вместе сi
иby
таким же образом, как мы видели в виньетке “Введение в data.table”. Еще мы можем использоватьkeyby
, цепочечные операции, передавать выражения вby
. Синтаксис согласован. - Мы можем использовать оператор
:=
ради его побочного эффекта, или использоватьcopy()
, чтобы не изменять исходный объект при обновлении по ссылке.
j
, как это комбинировать с by
, а также немного возможностей i
. Давайте обратим наше внимание на i
в следующей виньетке “Keys and fast binary search based subset” для выполнения молниеносного создания поднаборов при помощи установки ключей (keying) в data.tables.
Комментариев нет:
Отправить комментарий