Функции записи (процедуры) для объектов data.table
В книге "Программное обеспечение для анализа данных: программирование с помощью R" Джон Чамберс подчеркивает, что функции, как правило, не должны быть написаны для их побочного эффекта; скорее, что функция должна возвращать значение без изменения каких-либо переменных в своей вызывающей среде. И наоборот, запись хорошего script с использованием объектов data.table должна специально избегать использования назначения объекта с помощью <-
, обычно используемого для хранения результата функции.
Во-первых, это технический вопрос. Представьте себе функцию R, называемую proc1
, которая принимает объект data.table
x
в качестве аргумента (в дополнение к, возможно, другим параметрам). proc1
возвращает NULL, но изменяет x
с помощью :=
. Из того, что я понимаю, proc1
вызов proc1(x=x1)
делает копию x1
только из-за того, как работает promises. Однако, как показано ниже, исходный объект x1
все еще изменяется proc1
. Почему/как это?
> require(data.table)
> x1 <- CJ(1:2, 2:3)
> x1
V1 V2
1: 1 2
2: 1 3
3: 2 2
4: 2 3
> proc1 <- function(x){
+ x[,y:= V1*V2]
+ NULL
+ }
> proc1(x1)
NULL
> x1
V1 V2 y
1: 1 2 2
2: 1 3 3
3: 2 2 4
4: 2 3 6
>
Кроме того, кажется, что использование proc1(x=x1)
не является медленным, чем выполнение процедуры непосредственно на x, что указывает на то, что мое смутное понимание promises неверно и что они работают в режиме передачи по ссылке
> x1 <- CJ(1:2000, 1:500)
> x1[, paste0("V",3:300) := rnorm(1:nrow(x1))]
> proc1 <- function(x){
+ x[,y:= V1*V2]
+ NULL
+ }
> system.time(proc1(x1))
user system elapsed
0.00 0.02 0.02
> x1 <- CJ(1:2000, 1:500)
> system.time(x1[,y:= V1*V2])
user system elapsed
0.03 0.00 0.03
Итак, учитывая, что передача аргумента data.table функции не добавляет времени, это позволяет писать процедуры для объектов data.table, включающих как скорость data.table, так и обобщаемость функции. Однако, учитывая, что Джон Чамберс сказал, что функции не должны иметь побочных эффектов, действительно ли "нормально" писать этот тип процедурного программирования в R? Почему он утверждал, что побочные эффекты "плохие"? Если я проигнорирую его совет, какие ошибки я должен знать? Что я могу сделать, чтобы написать "хорошие" методы data.table?
Ответы
Ответ 1
Да, добавление, изменение, удаление столбцов в data.table
выполняется с помощью reference
. В некотором смысле, это хорошо, потому что data.table
обычно содержит много данных, и было бы очень много времени и времени, чтобы переназначить все это каждый раз, когда будет сделано изменение. С другой стороны, это плохо, потому что оно идет против подхода функционального программирования no-side-effect
, который R пытается продвигать с помощью pass-by-value
по умолчанию. При отсутствии побочных эффектов программирование мало беспокоится о том, когда вы вызываете функцию: вы можете быть уверены, что ваши входы или ваша среда не будут затронуты, и вы можете просто сфокусироваться на выходе функции. Это просто, поэтому удобно.
Конечно, это нормально игнорировать совет Джона Чемберса, если вы знаете, что делаете. О написании "хороших" процедур data.tables, вот пара правил, которые я бы рассмотрел, если бы я был вами, как способ ограничить сложность и количество побочных эффектов:
- функция не должна изменять более одной таблицы, т.е. изменять эту таблицу как единственный побочный эффект,
- если функция изменяет таблицу, а затем делает эту таблицу результатом функции. Конечно, вы не захотите повторно назначить его: просто запустите
do.something.to(table)
, а не table <- do.something.to(table)
. Если вместо этого функция имела другой ( "реальный" ) вывод, то при вызове result <- do.something.to(table)
легко представить, как вы можете сосредоточить свое внимание на выходе и забыть, что вызов функции имел побочный эффект на вашей таблице.
В то время как функции "один выход/без побочных эффектов" являются нормой в R, приведенные выше правила допускают "один выход или побочный эффект". Если вы согласны с тем, что побочный эффект - это как-то форма вывода, тогда вы согласитесь, что я не слишком сильно изгибаю правила, слабо придерживаясь одного функционального стиля программирования R-выхода. Предоставление функций множественным побочным эффектам будет немного более растянутым; не то, что вы не можете этого сделать, но я постараюсь избежать его, если это возможно.
Ответ 2
Документация может быть улучшена (предложения очень приветствуются), но вот что есть на данный момент. Возможно, он должен сказать "даже в пределах функций"?
В ?":="
:
data.tables не копируются-в-изменении с помощью: =, setkey или любой другой функции *. См. Копию.
DT изменяется по ссылке и возвращается новое значение. Если вам нужна копия, сначала сделайте копию (используя DT2 = copy (DT)). Напомним, что этот пакет предназначен для больших данных (смешанных типов столбцов с несколькими столбцами), где обновления по ссылке могут быть на много порядков быстрее, чем копирование всей таблицы.
и в ?copy
(но я понимаю, что это запутано с помощью setkey):
Ввод изменяется ссылкой и возвращается (невидимо), поэтому он может использоваться в составных заявлениях; например, setkey (DT, a) [J ( "foo" )]. если ты требуется копия, сначала сделайте копию (используя DT2 = copy (DT)). copy() может также иногда может быть полезно до: = используется для переназначения в столбе Справка. См. Копию. Обратите внимание, что setattr также находится в бите пакета. И то и другое пакеты просто выставляют R внутреннюю функцию setAttrib на уровне C, но отличаются в возвращаемом значении. бит:: setattr возвращает NULL (невидимо) в напомните, что функция используется для ее побочного эффекта. data.table:: setattr возвращает измененный объект (невидимо) для использования в составные утверждения.
где последние два предложения о bit::setattr
относятся к точке флоделя 2, интересно.
Также см. следующие вопросы:
Понимание того, когда data.table является ссылкой (вместо копии) другой таблицы данных
Передать по ссылке: оператор: = в пакете data.table
data.table 1.8.1.: "DT1 = DT2" - это не то же самое, что DT1 = копия (DT2)?
Мне очень нравится эта часть вашего вопроса:
что позволяет писать процедуры для объектов data.table, включая как скорость данных. таблицу, так и обобщаемость функция.
Да, это определенно одно из намерений. Посмотрите, как работает база данных: множество разных пользователей/программ меняются по ссылке (вставка/обновление/удаление) одной или нескольких (больших) таблиц в базе данных. Это прекрасно работает на земле базы данных, и это больше похоже на то, как думает data.table. Следовательно, видео svSocket на главной странице и желание для insert
и delete
(только по ссылке, только для глагола, функции побочных эффектов).