operaciones data.table por nombre de columna

2014-01-09 r data.table

Supongamos que tengo un data.table

a <- data.table(id=c(1,1,2,2,3),a=21:25,b=11:15,key="id")

Puedo agregar nuevas columnas como esta:

a[, sa := sum(a), by="id"]
a[, sb := sum(b), by="id"]
> a
   id  a  b sa sb
1:  1 21 11 43 23
2:  1 22 12 43 23
3:  2 23 13 47 27
4:  2 24 14 47 27
5:  3 25 15 25 15

Sin embargo, supongamos que tengo nombres de columna en su lugar:

for (n in c("a","b")) {
  s <- paste0("s",n)
  a[, s := sum(n), by="id", with=FALSE] # ERROR: invalid 'type' (character) of argument
}

¿qué debo hacer?

Answers

Editar 2020-02-15 sobre ..

data.table también admite la sintaxis .. para "buscar un nivel", obviando la necesidad de with=FALSE en la mayoría de los casos, por ejemplo, dt[ , ..n1] y dt[ , ..n2] en los siguientes


echar un vistazo with en ? data.table :

dt <- data.table(id=1:5,a=21:25,b=11:15,key="id")
dt[, n3 := dt[ , n1, with = FALSE ] * dt[ , n2, with = FALSE ], with = FALSE ]

EDITAR:

O simplemente cambia los colnames adelante y atrás:

dt <- data.table(id=1:5,a=21:25,b=11:15,key="id")
dt[ , dt.names["n3"] := 1L, with = FALSE ]

dt.names <- c( n1 = "a", n2 = "b", n3 = "c" )
setnames( dt, dt.names, names(dt.names) )

dt[ , n3 := n1 * n2, by = "id" ]
setnames( dt, names(dt.names), dt.names )

que trabaja junto con by.

Esto es similar a:

¿Cómo generar una combinación lineal de variables y actualizar la tabla usando data.table en una llamada de bucle?

pero desea combinar esto con by= también, por lo que set() no es lo suficientemente flexible. Es un diseño de diseño deliberado y es poco probable que set() cambie en ese sentido.

A veces uso el ayudante EVAL al final de esa respuesta.
https://stackoverflow.com/a/20808573/403310
Algunos se estremecen con ese enfoque, pero solo pienso en ello como construir una declaración SQL dinámica, que es una práctica bastante común. El enfoque EVAL brinda la máxima flexibilidad sin rascarse la cabeza sobre eval() y quote() . Para ver la consulta dinámica que se ha construido (para verificarla) puede agregar una print dentro de su función auxiliar EVAL .

Sin embargo, en este sencillo ejemplo, puede ajustar el LHS de := con corchetes para indicar a data.table que data.table el valor (más claro que with=FALSE ), y el RHS necesita un get() .

for (n in c("a","b")) {
  s <- paste0("s",n)
  a[, (s) := sum(get(n)), by="id"]
}

También puedes hacer esto:

a <- data.table(id=c(1,1,2,2,3),a=21:25,b=11:15,key="id")

a[, c("sa", "sb") := lapply(.SD, sum), by = id]

O un poco más en general:

cols.to.sum = c("a", "b")
a[, paste0("s", cols.to.sum) := lapply(.SD, sum), by = id, .SDcols = cols.to.sum]

Aquí hay un enfoque que hace el manejo de llamadas y evita cualquier sobrecarga con .SD

# a helper function
makeCall <- function(x,fun) bquote(.(fun)(.(x)))
# the columns you wish to sum (apply function to)
cols <- c('a','b')
new.cols <- paste0('s',cols)
# create named list of names
name.cols <- setNames(sapply(cols,as.name), new.cols)
# create the call
my_call <-  as.call(c(as.name(':='), lapply(name.cols, makeCall, fun = as.name('sum'))))
(a[, eval(my_call), by = 'id'])

#    id  a  b sa sb
# 1:  1 21 11 43 23
# 2:  1 22 12 43 23
# 3:  2 23 13 47 27
# 4:  2 24 14 47 27
# 5:  3 25 15 25 15

Related