Seleccionar / asignar a data.table cuando los nombres de las variables se almacenan en un vector de caracteres


92

¿Cómo se refiere a las variables en a data.tablesi los nombres de las variables se almacenan en un vector de caracteres? Por ejemplo, esto funciona para data.frame:

df <- data.frame(col1 = 1:3)
colname <- "col1"
df[colname] <- 4:6
df
#   col1
# 1    4
# 2    5
# 3    6

¿Cómo puedo realizar esta misma operación para un data.table, con o sin :=notación? Lo obvio de dt[ , list(colname)]no funciona (ni esperaba que lo hiciera).

Respuestas:


133

Dos formas de seleccionar variable (s) mediante programación:

  1. with = FALSE:

     DT = data.table(col1 = 1:3)
     colname = "col1"
     DT[, colname, with = FALSE] 
     #    col1
     # 1:    1
     # 2:    2
     # 3:    3
    
  2. ..prefijo 'dot dot' ( ):

     DT[, ..colname]    
     #    col1
     # 1:    1
     # 2:    2
     # 3:    3
    

Para obtener una descripción más detallada de la ..notación 'punto punto' ( ), consulte Nuevas funciones en 1.10.2 (actualmente no se describe en el texto de ayuda).

Para asignar a variable (s), coloque el LHS de :=entre paréntesis:

DT[, (colname) := 4:6]    
#    col1
# 1:    4
# 2:    5
# 3:    6

Este último se conoce como plonk de columna , porque reemplaza el vector de columna completo por referencia. Si ihubiera un subconjunto , se subasignaría por referencia. El parens around (colname)es una taquigrafía introducida en la versión v1.9.4 en CRAN de octubre de 2014. Aquí está la noticia :

El uso de with = FALSEcon :=ahora está obsoleto en todos los casos, dado que :=durante algún tiempo se ha preferido envolver el LHS de entre paréntesis.

colVar = "col1"
DT[, (colVar) := 1]                             # please change to this
DT[, c("col1", "col2") := 1]                    # no change
DT[, 2:4 := 1]                                  # no change
DT[, c("col1","col2") := list(sum(a), mean(b))]  # no change
DT[, `:=`(...), by = ...]                       # no change

Consulte también la sección Detalles en ?`:=`:

DT[i, (colnamevector) := value]
# [...] The parens are enough to stop the LHS being a symbol

Y para responder más preguntas en el comentario, aquí hay una forma (como de costumbre, hay muchas formas):

DT[, colname := cumsum(get(colname)), with = FALSE]
#    col1
# 1:    4
# 2:    9
# 3:   15 

o bien, puede que le resulte más fácil de leer, escribir y depurar sólo para evaluna paste, similar a la construcción de una sentencia de SQL dinámico para enviar a un servidor:

expr = paste0("DT[,",colname,":=cumsum(",colname,")]")
expr
# [1] "DT[,col1:=cumsum(col1)]"

eval(parse(text=expr))
#    col1
# 1:    4
# 2:   13
# 3:   28

Si lo hace mucho, puede definir una función auxiliar EVAL:

EVAL = function(...)eval(parse(text=paste0(...)),envir=parent.frame(2))

EVAL("DT[,",colname,":=cumsum(",colname,")]")
#    col1
# 1:    4
# 2:   17
# 3:   45

Ahora que data.table1.8.2 optimiza automáticamente la jeficiencia, puede ser preferible utilizar el evalmétodo. El get()in jevita algunas optimizaciones, por ejemplo.

O lo hay set(). Una forma funcional de bajo costo, :=que estaría bien aquí. Ver ?set.

set(DT, j = colname, value = cumsum(DT[[colname]]))
DT
#    col1
# 1:    4
# 2:   21
# 3:   66

1
Gracias por la respuesta Mateo. El with = FALSE definitivamente resuelve parte de mi problema. Sin embargo, en realidad, quiero reemplazar la columna con el cumsum de la columna. ¿Puedo hacer referencia al nombre de la columna por variable en el lado derecho de la asignación de alguna manera?
frankc

En realidad, acabo de grabar el cumsum externamente con un nombre diferente que no existe dentro del dt y que funciona bien.
frankc

1
¡Pero eso sería una línea extra! No muy elegante :) Pero bueno, a veces es útil. En esos casos, es mejor comenzar con el nombre de la variable ., o ..evitar cualquier posible enmascaramiento si DTalguna vez contiene ese símbolo como nombre de columna en el futuro (y seguir la convención con la que los nombres de columna no comienzan .). Hay algunas solicitudes de funciones para que sea más sólido para problemas de alcance como ese, como agregar .()y ..().
Matt Dowle

Respondí antes de darme cuenta de que editaste tu respuesta. Mi primer pensamiento había sido eval (parse ()) pero por alguna razón estaba teniendo problemas para que funcionara, cuando me di cuenta de que debía hacerlo externamente. Esta es una gran respuesta con muchas cosas en las que no pensé. Gracias por data.table en general, es un gran paquete.
frankc

2
Tenga en cuenta que usted podría utilizar la interpolación tipo de cadena cuasi-perl de fn$del paquete gsubfn para mejorar la legibilidad de la solución de EVAL: library(gsubfn); fn$EVAL( "DT[,$colname:=cumsum($colname)]" ).
G. Grothendieck

8

* Esta no es una respuesta en realidad, pero no tengo suficiente credibilidad en la calle para publicar comentarios: /

De todos modos, para cualquiera que esté buscando crear una nueva columna en una tabla de datos con un nombre almacenado en una variable, tengo lo siguiente para trabajar. No tengo ni idea de su rendimiento. ¿Alguna sugerencia de mejora? ¿Es seguro asumir que una nueva columna sin nombre siempre recibirá el nombre V1?

colname <- as.name("users")
# Google Analytics query is run with chosen metric and resulting data is assigned to DT
DT2 <- DT[, sum(eval(colname, .SD)), by = country]
setnames(DT2, "V1", as.character(colname))

Observe que puedo hacer referencia a él bien en la suma () pero parece que no puedo hacer que se asigne en el mismo paso. Por cierto, la razón por la que necesito hacer esto es que colname se basará en la entrada del usuario en una aplicación Shiny.


+1 por solo trabajar: estoy de acuerdo en que esta no debe ser "la manera" de hacer esto, pero después de haber pasado 45 minutos revisando cada publicación de SO sobre este tema, esta es la única solución a la que he podido llegar. trabajo - ¡gracias por tomarse el tiempo para señalarlo!
neuropsych

¡Me alegro de haber podido ayudar! Desafortunadamente, nunca encontré una solución más elegante usando directamente data.tables, aunque esta línea de 3 no es terrible. En mi escenario, me di cuenta de que una alternativa más simple habría sido usar tidyr para hacer que mis datos sean "largos" en lugar de "anchos", ya que según la entrada del usuario, siempre podría filtrar en una sola columna en lugar de seleccionar de un conjunto de columnas.
efh0888

2
No es seguro asumir que V1es el nuevo nombre. Por ejemplo, si lee csv con fready hay una columna sin nombre, tendrá V1nombre (y read.csvdará X). Entonces es posible que su mesa ya tenga un V1. Tal vez solo obtenga el nombre pornames(DT)[length(names(DT))]
dracodoc

2

Para múltiples columnas y una función aplicada a valores de columna.

Al actualizar los valores de una función, el RHS debe ser un objeto de lista, por lo que usar un bucle en .SDcon lapplyhará el truco.

El siguiente ejemplo convierte columnas enteras en columnas numéricas

a1 <- data.table(a=1:5, b=6:10, c1=letters[1:5])
sapply(a1, class)  # show classes of columns
#         a           b          c1 
# "integer"   "integer" "character" 

# column name character vector
nm <- c("a", "b")

# Convert columns a and b to numeric type
a1[, j = (nm) := lapply(.SD, as.numeric ), .SDcols = nm ]

sapply(a1, class)
#         a           b          c1 
# "numeric"   "numeric" "character" 

2

Recupere múltiples columnas de data.table a través de variable o función:

library(data.table)

x <- data.table(this=1:2,that=1:2,whatever=1:2)

# === explicit call
x[, .(that, whatever)]
x[, c('that', 'whatever')]

# === indirect via  variable
# ... direct assignment
mycols <- c('that','whatever')
# ... same as result of a function call
mycols <- grep('a', colnames(x), value=TRUE)

x[, ..mycols]
x[, .SD, .SDcols=mycols]

# === direct 1-liner usage
x[, .SD, .SDcols=c('that','whatever')]
x[, .SD, .SDcols=grep('a', colnames(x), value=TRUE)]

que todos ceden

   that whatever
1:    1        1
2:    2        2

Encuentro el .SDcolscamino más elegante.


1

Podrías intentar esto

colname <- as.name ("COL_NAME")

DT2 <- DT [, lista (COL_SUM = sum (eval (colname, .SD))), por = c (grupo)]


1
Siempre se recomienda agregar una explicación con su código en lugar de solo publicarlo.
MBorg
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.