Подтвердить что ты не робот

Функции записи (процедуры) для объектов 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?

4b9b3361

Ответ 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 (только по ссылке, только для глагола, функции побочных эффектов).