style - crear una expresión de una función para data.table a eval
render table shiny (2)
Esto no se siente ideal, pero es lo mejor que he podido encontrar. Lo lanzaré para ver si ayuda a obtener mejores respuestas ...
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
La parte que no me gusta es que lapply()
creará una copia de la salida, en forma de lista, y luego data.table()
tendrá (por lo que tengo entendido) que copiar esos datos en una ubicación separada, que es peor que si hubiera usado la construcción list()
dentro de [.data.frame()
.
Dados los data.table
la data.table
:
dat <- data.table(x_one=1:10, x_two=1:10, y_one=1:10, y_two=1:10)
Me gustaría una función que crea una expresión entre dos filas similares dado su nombre "raíz", por ejemplo, x_one - x_two
.
myfun <- function(name) {
one <- paste0(name, ''_one'')
two <- paste0(name, ''_two'')
parse(text=paste(one, ''-'', two))
}
Ahora, usar solo un nombre de raíz funciona como se espera y da como resultado un vector.
dat[, eval(myfun(''x'')),]
[1] 0 0 0 0 0 0 0 0 0 0
Sin embargo, al intentar asignar a esa salida un nombre usando la técnica de list
falla:
dat[, list(x_out = eval(myfun(''x''))),]
Error in eval(expr, envir, enclos) : object ''x_one'' not found
Puedo "resolver" esto agregando un with(dat, ...)
pero eso parece poco 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
¿Cuál es la forma correcta de generar y evaluar estas expresiones si quiero una salida como la que tengo arriba?
En caso de que ayude, la salida de sessionInfo()
está abajo. Recuerdo haber podido hacer esto, o algo parecido, pero ha pasado un tiempo y data.table
se actualiza desde ...
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
Una solución es poner la list(...)
dentro de la salida de la función.
as.quoted
a usar como. as.quoted
, robando la forma en que implementa @hadley .()
En el paquete 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
Para hacer dos columnas a la vez puedes ajustar tu llamada.
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
En cuanto a la razón .....
en esta solución, toda la llamada a '' list(..)
se evalúa dentro de parent.frame como data.table.
El código relevante dentro de [.data.table
es
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]]
}
si (en tu caso)
j = list(xout = eval(myfun(''x'')))
##then
jsub <- substitute(j)
es
# list(xout = eval(myfun("x")))
y
as.list.default(jsub)
## [[1]]
## list
##
## $xout
## eval(myfun("x"))
así que jsubl[[1L]]
es la list
, jsubl[[2L]]
es eval(myfun("x"))
por data.table
tanto, data.table
no ha encontrado una llamada a eval
y no la tratará de manera adecuada.
Esto funcionará, forzando la segunda evaluación dentro de los datos correctos.
# using OP myfun
dat[,list(xout =eval(myfun(''x''), dat))]
De la misma manera
eval(parse(text = ''x_one''),dat)
# [1] 1 2 3 4 5 6 7 8 9 10
Trabaja pero
eval(eval(parse(text = ''x_one'')), dat)
No
Editar 10/4/13
Aunque probablemente sea más seguro (pero más lento) usar .SD
como entorno, ya que será más robusto para i
o by
ejemplo.
dat[,list(xout =eval(myfun(''x''), .SD))]
Editar desde Mateo:
+10 a arriba. Yo no podría haberlo explicado mejor. Yendo un paso más allá, lo que a veces hago es construir la consulta completa de data.table y luego eval
eso. Puede ser un poco más robusto de esa manera, a veces. Pienso en ello como SQL; es decir, a menudo construimos una declaración dinámica de SQL que se envía al servidor SQL para ser ejecutada. Cuando también está depurando, a veces también es más fácil mirar la consulta construida y ejecutarla en el indicador del navegador. Pero, a veces, una consulta de este tipo sería muy larga, por lo que pasar eval
a i
, j
o by
puede ser más eficiente al no volver a calcular los otros componentes. Como de costumbre, hay muchas maneras de pelar al gato.
Las razones sutiles para considerar evaluar la consulta completa incluyen:
Una razón por la que la agrupación es rápida es que inspecciona primero la expresión
j
. Si es unalist
, quita los nombres, pero los recuerda. A continuación,eval
una lista sin nombre para cada grupo y luego restablece los nombres una vez, al final del resultado final. Una razón por la que otros métodos pueden ser lentos es la recreación del mismo vector de nombre de columna para cada grupo, una y otra vez. Sin embargo, cuanto más complejo se definej
(por ejemplo, si la expresión no comienza precisamente con lalist
), más difícil será codificar internamente la lógica de inspección. Hay muchas pruebas en esta área; por ejemplo, en combinación coneval
, e informes de verbosidad si la omisión del nombre no funciona. Sin embargo, construir una consulta "simple" (la consulta completa) yeval
puede ser más rápido y más sólido por esta razón.Con v1.8.2 ahora hay optimización de
j
:options(datatable.optimize=Inf)
. Esto inspeccionaj
y lo modifica para optimizar lamean
y ellapply(.SD,...)
, hasta ahora. Esto hace que los órdenes de magnitud sean diferentes y significa que hay menos para que el usuario los sepa (por ejemplo, algunos de los puntos de wiki ya han desaparecido). Podríamos hacer más de esto; por ejemplo,DT[a==10]
podría optimizarse aDT[J(10)]
automáticamente si lakey(DT)[1]=="a"
[Actualización de septiembre de 2014: ahora implementada en v1.9.3]. Pero, nuevamente, las optimizaciones internas son más difíciles de codificar internamente si en lugar deDT[,mean(a),by=b]
esDT[,list(x=eval(expr)),by=b]
dondeexpr
contenía una llamada paramean
, por ejemplo. Por lo tanto,eval
la consulta completa puede funcionar mejor condatatable.optimize
. Activar verbosidad en los informes lo que está haciendo y la optimización se puede desactivar si es necesario; Por ejemplo, para probar la diferencia de velocidad que hace.
Según los comentarios, FR # 2183 se ha agregado: "Cambiar j = list (xout = eval (...)) eval a eval dentro del alcance de DT". Gracias por destacar. Ese es el tipo de complejo que quiero decir donde la eval
está anidada en la expresión. Sin embargo, si j
comienza con eval
, eso es mucho más simple y ya está codificado (como se muestra arriba) y probado, y debería optimizarse correctamente.
Si hay data.table
se data.table
de esto, entonces es: use DT[...,verbose=TRUE]
u options(datatable.verbose=TRUE)
para verificar data.table
aún funciona de manera eficiente cuando se usa para consultas dinámicas que involucran eval
.