studio - ¿Cómo se puede trabajar de manera totalmente genérica en data.table en R con nombres de columna en variables?
data.table r tutorial (2)
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:
- Seleccionar / asignar a variables data.table qué nombres se almacenan en un vector de caracteres
- pasar el nombre de la columna en data.table usando la variable en R
- Refiriéndose a columnas data.table por nombres guardados en variables
- pasando nombres de columna a data.table mediante programación
- Meta-programación de Data.table
- ¿Cómo escribir una función que llama a una función que llama a data.table?
- Usar nombres dinámicos de columnas en `data.table`
- nombres dinámicos de columna en data.table, R
- Asigna múltiples columnas usando: = en data.table, por grupo
- Establecer el nombre de la columna en la operación "agrupar por" con data.table
- 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 me encontré con uno que era bastante sencillo de simplificar para su uso 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
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 programació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)]''
)]
}
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 brillante, 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]