¿Cómo se puede trabajar de manera totalmente genérica en data.table en R con nombres de columna en variables?

Antes que nada: gracias a @MattDowle; data.table es una de las mejores cosas que me han pasado desde que comencé a usar R

Segundo: conozco muchas soluciones para varios casos de uso de nombres de columnas variables en data.table , que incluyen:

  1. Seleccionar / asignar a variables data.table qué nombres se almacenan en un vector de caracteres
  2. pasar el nombre de la columna en data.table usando la variable en R
  3. Refiriéndose a columnas data.table por nombres guardados en variables
  4. pasando nombres de columna a data.table mediante progtwigción
  5. Meta-progtwigción de Data.table
  6. ¿Cómo escribir una función que llama a una función que llama a data.table?
  7. Usar nombres dynamics de columnas en `data.table`
  8. nombres dynamics de columna en data.table, R
  9. Asigna múltiples columnas usando: = en data.table, por grupo
  10. Establecer el nombre de la columna en la operación “agrupar por” con data.table
  11. R que resume columnas múltiples con data.table

y probablemente más no me he referido.

Pero: incluso si aprendí todos los trucos documentados anteriormente al punto que nunca tuve que buscarlos para recordarme cómo usarlos, todavía encontraría que trabajar con nombres de columna que se pasan como parámetros a una función es extremadamente tarea tediosa.

Lo que estoy buscando es una alternativa aprobada por las mejores prácticas para la siguiente solución / flujo de trabajo. Tenga en cuenta que tengo un grupo de columnas de datos similares y me gustaría realizar una secuencia de operaciones similares en estas columnas o conjuntos de ellas, donde las operaciones son de complejidad arbitrariamente alta, y los grupos de nombres de columna pasados ​​a cada operación especificada en una variable.

Me doy cuenta de que este problema suena artificial, pero me encuentro con sorprendente frecuencia. Los ejemplos suelen ser tan desordenados que es difícil separar las características relevantes para esta pregunta, pero recientemente encontré uno que era bastante sencillo de simplificar para usarlo como MWE aquí:

 library(data.table) library(lubridate) library(zoo) the.table <- data.table(year=1991:1996,var1=floor(runif(6,400,1400))) the.table[,`:=`(var2=var1/floor(runif(6,2,5)), var3=var1/floor(runif(6,2,5)))] # Replicate data across months new.table <- the.table[, list(asofdate=seq(from=ymd((year)*10^4+101), length.out=12, by="1 month")),by=year] # Do a complicated procedure to each variable in some group. var.names <- c("var1","var2","var3") for(varname in var.names) { #As suggested in an answer to Link 3 above #Convert the column name to a 'quote' object quote.convert <- function(x) eval(parse(text=paste0('quote(',x,')'))) #Do this for every column name I'll need varname <- quote.convert(varname) anntot <- quote.convert(paste0(varname,".annual.total")) monthly <- quote.convert(paste0(varname,".monthly")) rolling <- quote.convert(paste0(varname,".rolling")) scaled <- quote.convert(paste0(varname,".scaled")) #Perform the relevant tasks, using eval() #around every variable columnname I may want new.table[,eval(anntot):= the.table[,rep(eval(varname),each=12)]] new.table[,eval(monthly):= the.table[,rep(eval(varname)/12,each=12)]] new.table[,eval(rolling):= rollapply(eval(monthly),mean,width=12, fill=c(head(eval(monthly),1), tail(eval(monthly),1)))] new.table[,eval(scaled):= eval(anntot)/sum(eval(rolling))*eval(rolling), by=year] } 

Por supuesto, el efecto particular sobre los datos y las variables aquí es irrelevante, así que no se concentre en él ni sugiera mejoras para lograr lo que logra en este caso particular. Lo que busco, más bien, es una estrategia genérica para el flujo de trabajo de aplicar repetidamente un procedimiento arbitrariamente complicado de acciones de data.table a una lista de columnas o lista de listas de columnas, especificada en una variable o pasada como argumento a una función, donde el procedimiento debe referirse programáticamente a las columnas nombradas en la variable / argumento, y posiblemente incluya actualizaciones, uniones, agrupaciones, llamadas a los objetos especiales de datos .I , .SD , etc .; PERO uno que es más simple, más elegante, más corto o más fácil de diseñar o implementar o comprender que el anterior u otros que requieren quote frecuente y eval .

En particular, tenga en cuenta que, dado que los procedimientos pueden ser bastante complejos e implican actualizar repetidamente la data.table y luego hacer referencia a las columnas actualizadas, el lapply(.SD,...), ... .SDcols = ... estándar de lapply(.SD,...), ... .SDcols = ... es usualmente no es un sustituto viable. Además, reemplazar cada llamada de eval(a.column.name) con DT[[a.column.name]] no simplifica mucho ni funciona completamente en general, ya que no funciona bien con las otras operaciones data.table , en cuanto a Soy consciente

Traté de hacer esto en el pensamiento de data.table “esto no es tan malo” … pero después de un período embarazoso de tiempo, me di por vencido. Matt dice algo como “hazlo en pedazos y únete”, pero no pude encontrar maneras elegantes de hacer estas piezas, especialmente porque la última depende de los pasos anteriores.

Tengo que decir que esta es una pregunta bastante shiny, y yo también encuentro problemas similares con frecuencia. Me encanta data.table, pero aún me cuesta a veces. No sé si estoy luchando con data.table o la complejidad del problema.

Aquí está el enfoque incompleto que he tomado.

Siendo realistas, puedo imaginar que en un proceso normal tendría almacenadas más variables intermedias que serían útiles para calcular estos valores.

 library(data.table) library(zoo) ## Example yearly data set.seed(27) DT <- data.table(year=1991:1996, var1=floor(runif(6,400,1400))) DT[ , var2 := var1 / floor(runif(6,2,5))] DT[ , var3 := var1 / floor(runif(6,2,5))] setkeyv(DT,colnames(DT)[1]) DT ## Convenience function nonkey <- function(dt){colnames(dt)[!colnames(dt)%in%key(dt)]} ## Annual data expressed monthly NewDT <- DT[, j=list(asofdate=as.IDate(paste(year, 1:12, 1, sep="-"))), by=year] setkeyv(NewDT, colnames(NewDT)[1:2]) ## Create annual data NewDT_Annual <- NewDT[DT] setnames(NewDT_Annual, nonkey(NewDT_Annual), paste0(nonkey(NewDT_Annual), ".annual.total")) ## Compute monthly data NewDT_Monthly <- NewDT[DT[ , .SD / 12, keyby=list(year)]] setnames(NewDT_Monthly, nonkey(NewDT_Monthly), paste0(nonkey(NewDT_Monthly), ".monthly")) ## Compute rolling stats NewDT_roll <- NewDT_Monthly[j = lapply(.SD, rollapply, mean, width=12, fill=c(.SD[1],tail(.SD, 1))), .SDcols=nonkey(NewDT_Monthly)] NewDT_roll <- cbind(NewDT_Monthly[,1:2,with=F], NewDT_roll) setkeyv(NewDT_roll, colnames(NewDT_roll)[1:2]) setnames(NewDT_roll, nonkey(NewDT_roll), gsub(".monthly$",".rolling",nonkey(NewDT_roll))) ## Compute normalized values ## Compute "adjustment" table which is ## total of each variable, by year for rolling ## divided by ## original annual totals ## merge "adjustment values" in with monthly data, and then ## make a modified data.table which is each varaible * annual adjustment factor ## Merge everything NewDT_Combined <- NewDT_Annual[NewDT_roll][NewDT_Monthly] 

Gracias por la pregunta. Su enfoque original es de gran ayuda para resolver la mayoría de los problemas.

Aquí modifiqué ligeramente la función de cita y cambié el enfoque para analizar y evaluar toda la expresión RHS como una cadena en lugar de las variables individuales.

El razonamiento es:

  • Probablemente no desee repetirse declarando todas las variables que necesita usar al comienzo del ciclo.
  • Las cadenas escalarán mejor ya que pueden generarse mediante progtwigción. He agregado un ejemplo a continuación que calcula porcentajes por filas para ilustrar esto.

 library(data.table) library(lubridate) library(zoo) set.seed(1) the.table <- data.table(year=1991:1996,var1=floor(runif(6,400,1400))) the.table[,`:=`(var2=var1/floor(runif(6,2,5)), var3=var1/floor(runif(6,2,5)))] # Replicate data across months new.table <- the.table[, list(asofdate=seq(from=ymd((year)*10^4+101), length.out=12, by="1 month")),by=year] # function to paste, parse & evaluate arguments evalp <- function(..., envir=parent.frame()) {eval(parse(text=paste0(...)), envir=envir)} # Do a complicated procedure to each variable in some group. var.names <- c("var1","var2","var3") for(varname in var.names) { # 1. For LHS, use paste0 to generate new column name as string (from @eddi's comment) # 2. For RHS, use evalp new.table[, paste0(varname, '.annual.total') := evalp( 'the.table[,rep(', varname, ',each=12)]' )] new.table[, paste0(varname, '.monthly') := evalp( 'the.table[,rep(', varname, '/12,each=12)]' )] # Need to add envir=.SD when working within the table new.table[, paste0(varname, '.rolling') := evalp( 'rollapply(',varname, '.monthly,mean,width=12, fill=c(head(', varname, '.monthly,1), tail(', varname, '.monthly,1)))' , envir=.SD )] new.table[,paste0(varname, '.scaled'):= evalp( varname, '.annual.total / sum(', varname, '.rolling) * ', varname, '.rolling' , envir=.SD ) ,by=year ] # Since we're working with strings, more freedom # to work programmatically new.table[, paste0(varname, '.row.percent') := evalp( 'the.table[,rep(', varname, '/ (', paste(var.names, collapse='+'), '), each=12)]' )] } 
Intereting Posts