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

Создать выражение из функции для data.table для eval

С учетом data.table dat:

dat <- data.table(x_one=1:10, x_two=1:10, y_one=1:10, y_two=1:10) 

Мне нужна функция, которая создает выражение между двумя одинаковыми строками, учитывая их "корневое" имя, например. x_one - x_two.

myfun <- function(name) {
  one <- paste0(name, '_one')
  two <- paste0(name, '_two')

  parse(text=paste(one, '-', two))
}

Теперь, используя только одно имя корня, работает так, как ожидалось, и приводит к вектору.

dat[, eval(myfun('x')),]

[1] 0 0 0 0 0 0 0 0 0 0

Однако, пытаясь назначить этот вывод, имя с использованием метода list выходит из строя:

dat[, list(x_out = eval(myfun('x'))),]

Error in eval(expr, envir, enclos) : object 'x_one' not found

Я могу "решить" это, добавив with(dat, ...), но вряд ли кажется data.table-ish

dat[, list(x_out = with(dat, eval(myfun('x'))),
           y_out = with(dat, eval(myfun('y')))),]

    x_out y_out
 1:     0     0
 2:     0     0
 3:     0     0
 4:     0     0
 5:     0     0
 6:     0     0
 7:     0     0
 8:     0     0
 9:     0     0
10:     0     0

Каков правильный способ генерации и оценки этих выражений, если я хочу, чтобы результат был таким, как у меня выше?

В случае, если это помогает, вывод sessionInfo() ниже. Я помню, что смог сделать это или что-то близкое к нему, но его было немного, а data.table обновляется с...

R version 2.15.1 (2012-06-22)

Platform: x86_64-pc-linux-gnu (64-bit)

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8    LC_PAPER=C                 LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] graphics  grDevices utils     datasets  stats     grid      methods   base     

other attached packages:
 [1] Cairo_1.5-1      zoo_1.7-7        stringr_0.6.1    doMC_1.2.5       multicore_0.1-7  iterators_1.0.6  foreach_1.4.0   
 [8] data.table_1.8.2 circular_0.4-3   boot_1.3-5       ggplot2_0.9.1    reshape2_1.2.1   plyr_1.7.1      

loaded via a namespace (and not attached):
 [1] codetools_0.2-8    colorspace_1.1-1   dichromat_1.2-4    digest_0.5.2       labeling_0.1       lattice_0.20-6    
 [7] MASS_7.3-20        memoise_0.1        munsell_0.3        proto_0.3-9.2      RColorBrewer_1.0-5 scales_0.2.1      
[13] tools_2.15.1      
4b9b3361

Ответ 1

Одним из решений является размещение list(...) в выводе функции.

Я стараюсь использовать as.quoted, крадуя путь @hadley реализует .() в пакете plyr.

library(data.table)
library(plyr)
dat <- data.table(x_one=1:10, x_two=1:10, y_one=1:10, y_two=1:10) 
myfun <- function(name) {
  one <- paste0(name, '_one')
  two <- paste0(name, '_two')
  out <- paste0(name,'_out')
 as.quoted(paste('list(',out, '=',one, '-', two,')'))[[1]]
}


dat[, eval(myfun('x')),]

#    x_out
# 1:     0
# 2:     0
# 3:     0
# 4:     0
# 5:     0
# 6:     0
# 7:     0
# 8:     0
# 9:     0
#10:     0

Чтобы сделать сразу два столбца, вы можете настроить свой вызов

myfun <- function(name) {
  one <- paste0(name, '_one')
  two <- paste0(name, '_two')
  out <- paste0(name,'_out')
  calls <- paste(paste(out, '=', one, '-',two), collapse = ',')


 as.quoted(paste('list(', calls,')'))[[1]]
}


dat[, eval(myfun(c('x','y'))),]

#   x_out y_out
# 1:     0     0
# 2:     0     0
# 3:     0     0
# 4:     0     0
# 5:     0     0
# 6:     0     0
# 7:     0     0
# 8:     0     0
# 9:     0     0
# 0:     0     0

По причине.....

в этом решении весь вызов 'list(..) оценивается внутри parent.frame, являющегося таблицей данных.

Соответствующий код в [.data.table равен

if (missing(j)) stop("logical error, j missing")
jsub = substitute(j)
if (is.null(jsub)) return(NULL)
jsubl = as.list.default(jsub)
if (identical(jsubl[[1L]],quote(eval))) {
    jsub = eval(jsubl[[2L]],parent.frame())
    if (is.expression(jsub)) jsub = jsub[[1L]]
}

если (в вашем случае)

j = list(xout = eval(myfun('x'))) 

##then

jsub <- substitute(j) 

является

 #  list(xout = eval(myfun("x")))

и

as.list.default(jsub)
## [[1]]
## list
## 
## $xout
## eval(myfun("x"))

поэтому jsubl[[1L]] есть list, jsubl[[2L]] is eval(myfun("x"))

поэтому data.table не нашел вызов eval и не будет обрабатывать его соответствующим образом.

Это будет работать, заставляя вторую оценку в правильных data.table

# using OP myfun
dat[,list(xout =eval(myfun('x'), dat))]

Точно так же

eval(parse(text = 'x_one'),dat)
# [1]  1  2  3  4  5  6  7  8  9 10

Работает, но

 eval(eval(parse(text = 'x_one')), dat)

Не

Редактировать 10/4/13

Хотя, вероятно, безопаснее (но медленнее) использовать .SD в качестве среды, так как тогда он будет устойчив к i или by, а также, например,

dat[,list(xout =eval(myfun('x'), .SD))]

Отредактируйте от Матфея:

+10 до выше. Я не мог бы лучше объяснить это. Сделав еще один шаг, я иногда создаю весь запрос data.table, а затем eval. Иногда это может быть немного более устойчивым. Я думаю об этом, как SQL; т.е. мы часто строим динамический оператор SQL, который отправляется на SQL-сервер, который должен быть выполнен. При отладке тоже иногда проще смотреть на построенный запрос и запускать его в приглашении браузера. Но иногда такой запрос будет очень длинным, поэтому передача eval в i, j или by может быть более эффективной, не перекомпонуя другие компоненты. Как обычно, многие способы скинуть кошку.

Тонкие причины для рассмотрения eval всего запроса:

  • Одна из причин, по которой быстро группируется, заключается в том, что она сначала проверяет выражение j. Если он list, он удаляет имена, но запоминает их. Затем eval неназванный список для каждой группы, затем восстанавливает имена один раз, в конце в конечном результате. Одной из причин, по которой другие методы могут быть медленными, является повторение одного и того же имени столбца для каждой группы, снова и снова. Более сложный j определяется, хотя (например, если выражение не начинается точно с помощью list), тем сложнее он будет кодировать внутреннюю проверку контрольной логики. В этой области есть много тестов; например, в сочетании с eval и подробными отчетами, если удаление имени не работает. Но, построив "простой" запрос (полный запрос) и eval, который может быть более быстрым и надежным по этой причине.

  • В версии v.1.8.2 теперь выполняется оптимизация j: options(datatable.optimize=Inf). Это проверяет j и модифицирует его для оптимизации mean и lapply(.SD,...) идиомы. Это делает порядок разницы в величине и означает, что пользователю не нужно знать (например, некоторые из точек вики теперь ушли). Мы могли бы сделать больше этого; например, DT[a==10] можно оптимизировать до DT[J(10)] автоматически, если key(DT)[1]=="a" [Обновление Sep 2014 - теперь реализовано в версии 1.9.3]. Но опять же, внутренняя оптимизация усложняется для внутреннего кода, если вместо DT[,mean(a),by=b] it DT[,list(x=eval(expr)),by=b], где expr содержит вызов mean, например. Таким образом, eval весь запрос может играть лучше с datatable.optimize. Поворот многословия в отчетах о том, что он делает и оптимизация может быть отключена, если это необходимо; например, для проверки разницы в скорости, которую он делает.

В соответствии с комментариями добавлен FR # 2183: "Изменить j = list (xout = eval (...)) eval для eval в пределах области DT". Спасибо за выделение. Что тип сложного j Я имею в виду, где eval вложен в выражение. Если j начинается с eval, тем не менее, это намного проще и уже закодировано (как показано выше) и протестировано и должно быть оптимизировано отлично.

Если один из них извлекается из этого, то это: использовать DT[...,verbose=TRUE] или options(datatable.verbose=TRUE), чтобы проверить data.table, все еще эффективно работая при использовании для динамических запросов с участием eval.

Ответ 2

Это не кажется идеальным, но это лучшее, что я смог придумать. Я брошу его туда, чтобы посмотреть, поможет ли он получить лучшие ответы...

vars <- c("x", "y")
res <- do.call(data.table, (lapply(vars, function(X) dat[,eval(myfun(X)),])))
setnames(res, names(res), paste0(vars, "_out"))

## Check the results
head(res, 3)
#    x_out y_out
# 1:     0     0
# 2:     0     0
# 3:     0     0

Часть, которая мне не нравится, состоит в том, что lapply() создаст одну копию вывода в форме списка, а затем data.table() будет (насколько я понимаю) скопировать эти данные в отдельное место, что хуже, чем если бы вы использовали конструкцию list() в [.data.frame().