¿La forma más rápida de agregar filas para los pasos de tiempo que faltan?
Frecuentes
Visto 14,006 equipos
36
Tengo una columna en mis conjuntos de datos donde los períodos de tiempo (Time
) son números enteros que van desde ab. A veces pueden faltar períodos de tiempo para un grupo determinado. Me gustaría completar esas filas con NA
. A continuación se muestran datos de ejemplo para 1 (de varios 1000) grupo(s).
structure(list(Id = c(1, 1, 1, 1), Time = c(1, 2, 4, 5), Value = c(0.568780482159894,
-0.7207749516298, 1.24258192959273, 0.682123081696789)), .Names = c("Id",
"Time", "Value"), row.names = c(NA, 4L), class = "data.frame")
Id Time Value
1 1 1 0.5687805
2 1 2 -0.7207750
3 1 4 1.2425819
4 1 5 0.6821231
Como puede ver, falta el Tiempo 3. A menudo, uno o más pueden faltar. Puedo resolver esto por mi cuenta, pero me temo que no lo estaría haciendo de la manera más eficiente. Mi enfoque sería crear una función que:
Generar una secuencia de periodos de tiempo a partir de min(Time)
a max(Time)
Entonces haz un setdiff
agarrar perdido Time
valores.
Convierte ese vector en un data.frame
Extraiga variables de identificador único (Id
y otros no enumerados anteriormente), y agréguelo a este data.frame.
Combinar los dos.
Retorno de la función.
Entonces, todo el proceso se ejecutaría de la siguiente manera:
# Split the data into individual data.frames by Id.
temp_list <- dlply(original_data, .(Id))
# pad each data.frame
tlist2 <- llply(temp_list, my_pad_function)
# collapse the list back to a data.frame
filled_in_data <- ldply(tlist2)
Mejor manera de lograr esto?
4 Respuestas
38
Siguiendo los comentarios con Ben Barnes y comenzando con su mydf3
:
DT = as.data.table(mydf3)
setkey(DT,Id,Time)
DT[CJ(unique(Id),seq(min(Time),max(Time)))]
Id Time Value Id2
[1,] 1 1 -0.262482283 2
[2,] 1 2 -1.423935165 2
[3,] 1 3 0.500523295 1
[4,] 1 4 -1.912687398 1
[5,] 1 5 -1.459766444 2
[6,] 1 6 -0.691736451 1
[7,] 1 7 NA NA
[8,] 1 8 0.001041489 2
[9,] 1 9 0.495820559 2
[10,] 1 10 -0.673167744 1
First 10 rows of 12800 printed.
setkey(DT,Id,Id2,Time)
DT[CJ(unique(Id),unique(Id2),seq(min(Time),max(Time)))]
Id Id2 Time Value
[1,] 1 1 1 NA
[2,] 1 1 2 NA
[3,] 1 1 3 0.5005233
[4,] 1 1 4 -1.9126874
[5,] 1 1 5 NA
[6,] 1 1 6 -0.6917365
[7,] 1 1 7 NA
[8,] 1 1 8 NA
[9,] 1 1 9 NA
[10,] 1 1 10 -0.6731677
First 10 rows of 25600 printed.
CJ
significa Cross Join, ver ?CJ
. El acolchado con NA
sucede porque nomatch
por defecto es NA
. Conjunto nomatch
a 0
en su lugar, para eliminar las coincidencias. Si en lugar de rellenar con NA
Si se requiere la fila predominante, simplemente agregue roll=TRUE
. Esto puede ser más eficiente que rellenar con NA
s y luego llenando NA
s después. Ver la descripción de roll
in ?data.table
.
setkey(DT,Id,Time)
DT[CJ(unique(Id),seq(min(Time),max(Time))),roll=TRUE]
Id Time Value Id2
[1,] 1 1 -0.262482283 2
[2,] 1 2 -1.423935165 2
[3,] 1 3 0.500523295 1
[4,] 1 4 -1.912687398 1
[5,] 1 5 -1.459766444 2
[6,] 1 6 -0.691736451 1
[7,] 1 7 -0.691736451 1
[8,] 1 8 0.001041489 2
[9,] 1 9 0.495820559 2
[10,] 1 10 -0.673167744 1
First 10 rows of 12800 printed.
setkey(DT,Id,Id2,Time)
DT[CJ(unique(Id),unique(Id2),seq(min(Time),max(Time))),roll=TRUE]
Id Id2 Time Value
[1,] 1 1 1 NA
[2,] 1 1 2 NA
[3,] 1 1 3 0.5005233
[4,] 1 1 4 -1.9126874
[5,] 1 1 5 -1.9126874
[6,] 1 1 6 -0.6917365
[7,] 1 1 7 -0.6917365
[8,] 1 1 8 -0.6917365
[9,] 1 1 9 -0.6917365
[10,] 1 1 10 -0.6731677
First 10 rows of 25600 printed.
En lugar de establecer claves, puede utilizar on
. CJ
también toma un unique
argumento. Un pequeño ejemplo con dos 'Id':
d <- data.table(Id = rep(1:2, 4:3), Time = c(1, 2, 4, 5, 2, 3, 4), val = 1:7)
d[CJ(Id, Time = seq(min(Time), max(Time)), unique = TRUE), on = .(Id, Time)]
# Id Time val
# 1: 1 1 1
# 2: 1 2 2
# 3: 1 3 NA
# 4: 1 4 3
# 5: 1 5 4
# 6: 2 1 NA
# 7: 2 2 5
# 8: 2 3 6
# 9: 2 4 7
# 10: 2 5 NA
En este caso particular, donde uno de los vectores en CJ
fue generado con seq
, el resultado debe nombrarse explícitamente para que coincida con los nombres especificados en on
. Cuando se utilizan variables desnudas en CJ
aunque (como 'Id' aquí), se nombran automáticamente, como en data.table()
(Desde data.table 1.12.2
).
contestado el 13 de mayo de 19 a las 19:05
Nunca entendí completamente el uso de roll
; ¿Cómo puede ayudarme a obtener los NA? - rbatt
@sirallen Ver por ejemplo Manera eficiente de llenar series temporales por grupo y Llenar fechas faltantes por grupo - Henrik
@MattDowle Quizás edite su muy buena respuesta para incluir también características más recientes como on
como alternativa a setkey
y el uso de unique
argumento en CJ
. Salud - Henrik
@MattDowle Hice una edición. Espero que se vea bien. Salud. - Henrik
@Henrik Se ve muy bien. Gracias. - matt dowle
8
Puedes usar tidyr
para esto.
Utilice la herramienta tidyr::complete
para llenar filas para Time
, y por defecto los valores se rellenan con NA
.
Crear datos
Extendí los datos de muestra para mostrar que funciona para múltiples Id
s e incluso cuando dentro de un Id
la gama completa de Time
no es presente.
library(dplyr)
library(tidyr)
df <- tibble(
Id = c(1, 1, 1, 1, 2, 2, 2),
Time = c(1, 2, 4, 5, 2, 3, 5),
Value = c(0.56, -0.72, 1.24, 0.68, 1.46, 0.74, 0.99)
)
df
#> # A tibble: 7 x 3
#> Id Time Value
#> <dbl> <dbl> <dbl>
#> 1 1 1 0.56
#> 2 1 2 -0.72
#> 3 1 4 1.24
#> 4 1 5 0.68
#> 5 2 2 1.46
#> 6 2 3 0.74
#> 7 2 5 0.99
Completa las filas que faltan
df %>% complete(nesting(Id), Time = seq(min(Time), max(Time), 1L))
#> # A tibble: 10 x 3
#> Id Time Value
#> <dbl> <dbl> <dbl>
#> 1 1 1 0.56
#> 2 1 2 -0.72
#> 3 1 3 NA
#> 4 1 4 1.24
#> 5 1 5 0.68
#> 6 2 1 NA
#> 7 2 2 1.46
#> 8 2 3 0.74
#> 9 2 4 NA
#> 10 2 5 0.99
contestado el 30 de mayo de 17 a las 22:05
Encontré que esta solución en particular es la más fácil de usar para responder la misma pregunta anterior, especialmente cuando se usa tidyverse para la manipulación de datos. - Danielle
5
Consulte la respuesta de Matthew Dowle (por ahora, con suerte arriba).
Aquí hay algo que usa el data.table
paquete, y puede ayudar cuando hay más de una variable ID. También puede ser más rápido que merge
, dependiendo de cómo quieras tus resultados. Me interesaría la evaluación comparativa y/o las mejoras sugeridas.
Primero, cree algunos datos más exigentes con dos variables de ID
library(data.table)
set.seed(1)
mydf3<-data.frame(Id=sample(1:100,10000,replace=TRUE),
Value=rnorm(10000))
mydf3<-mydf3[order(mydf3$Id),]
mydf3$Time<-unlist(by(mydf3,mydf3$Id,
function(x)sample(1:(nrow(x)+3),nrow(x)),simplify=TRUE))
mydf3$Id2<-sample(1:2,nrow(mydf3),replace=TRUE)
Crear una función (Esto ha sido EDITADO - ver historial)
padFun<-function(data,idvars,timevar){
# Coerce ID variables to character
data[,idvars]<-lapply(data[,idvars,drop=FALSE],as.character)
# Create global ID variable of all individual ID vars pasted together
globalID<-Reduce(function(...)paste(...,sep="SOMETHINGWACKY"),
data[,idvars,drop=FALSE])
# Create data.frame of all possible combinations of globalIDs and times
allTimes<-expand.grid(globalID=unique(globalID),
allTime=min(data[,timevar]):max(data[,timevar]),
stringsAsFactors=FALSE)
# Get the original ID variables back
allTimes2<-data.frame(allTimes$allTime,do.call(rbind,
strsplit(allTimes$globalID,"SOMETHINGWACKY")),stringsAsFactors=FALSE)
# Convert combinations data.frame to data.table with idvars and timevar as key
allTimesDT<-data.table(allTimes2)
setnames(allTimesDT,1:ncol(allTimesDT),c(timevar,idvars))
setkeyv(allTimesDT,c(idvars,timevar))
# Convert data to data.table with same variables as key
dataDT<-data.table(data,key=c(idvars,timevar))
# Join the two data.tables to create padding
res<-dataDT[allTimesDT]
return(res)
}
Usa la función
(padded2<-padFun(data=mydf3,idvars=c("Id"),timevar="Time"))
# Id Time Value Id2
# [1,] 1 1 -0.262482283 2
# [2,] 1 2 -1.423935165 2
# [3,] 1 3 0.500523295 1
# [4,] 1 4 -1.912687398 1
# [5,] 1 5 -1.459766444 2
# [6,] 1 6 -0.691736451 1
# [7,] 1 7 NA NA
# [8,] 1 8 0.001041489 2
# [9,] 1 9 0.495820559 2
# [10,] 1 10 -0.673167744 1
# First 10 rows of 12800 printed.
(padded<-padFun(data=mydf3,idvars=c("Id","Id2"),timevar="Time"))
# Id Id2 Time Value
# [1,] 1 1 1 NA
# [2,] 1 1 2 NA
# [3,] 1 1 3 0.5005233
# [4,] 1 1 4 -1.9126874
# [5,] 1 1 5 NA
# [6,] 1 1 6 -0.6917365
# [7,] 1 1 7 NA
# [8,] 1 1 8 NA
# [9,] 1 1 9 NA
# [10,] 1 1 10 -0.6731677
# First 10 rows of 25600 printed.
La función editada divide el ID global en sus partes componentes en el marco de datos combinado, antes de fusionarse con los datos originales. Esto debería (creo) ser mejor.
contestado el 06 de mayo de 12 a las 22:05
Paquete correcto, pero demasiado complejo. ¿De alguna manera extrañaste roll=TRUE
cual es especificamente para esto? Mantenga los datos irregulares en la tabla, luego únalos a la serie de tiempo regular. Consulte la tercera sección de la viñeta "Intro to data.table" y el ejemplo en ?data.table
usar roll=TRUE
. Es una de las principales características del paquete. - matt dowle
@MatthewDowle, usted conoce sus propias funciones mucho mejor que yo, y cualquier sugerencia de mejora es muy bienvenida. Dado que el OP quería "rellenar" las variables que no son de ID ni de tiempo con NA
por cada falta Time
, pensé usando roll=TRUE
sería el enfoque incorrecto, ya que esto "rellenaría" con el valor anterior, ¿verdad? Parece que la mayor parte de la complejidad de la función tiene que ver con la consideración de múltiples variables de ID, no con la parte de unión, que se puede lograr muy bien con data.table
. - benbarnes
0
Mi enfoque general es usar freqTable <- as.data.frame(table(idvar1, idvar2, idvarN))
luego extraiga las filas donde Freq==0
, rellene según sea necesario y luego vuelva a apilar sobre los datos originales.
contestado el 03 de mayo de 12 a las 22:05
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas r dataframe plyr data.table or haz tu propia pregunta.
Básicamente haría lo que describes, solo usando
expand.grid
y luegomerge
aall = TRUE
. No estoy seguro de que dividir por Id primero sea necesario, de verdad. - joranAquí hay una complicación adicional, hay numerosas variables de identificación. solo necesito agregar
Time
y establecerValue
aNA
y rellena el resto. Entonces se conviertedata_to_merge <- data.frame(id=unique(data$id),...)
(que es una línea realmente larga y no portátil si cambia la estructura de los datos). Ojalá pudiera fusionar el tiempo que falta, agregar NA y obtener todo el resto de los datos originales de manera eficiente. - MaiasauraLo tengo funcionando ahora, pero aún podría usar una solución general ya que esto estaría en un paquete y no sé qué podría enviar un usuario como datos originales. - Maiasaura
Para datos desagrupados, vea también varias buenas respuestas en Cómo agregar cero al valor faltante en r. - Henrik