¿La forma más rápida de agregar filas para los pasos de tiempo que faltan?

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?

preguntado el 03 de mayo de 12 a las 21:05

Básicamente haría lo que describes, solo usando expand.grid y entonces merge con all = TRUE. No estoy seguro de que dividir por Id primero sea necesario, de verdad. -

Aquí hay una complicación adicional, hay numerosas variables de identificación. solo necesito agregar Time y establecer Value a NA y rellena el resto. Entonces se convierte data_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. -

Lo 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. -

Para datos desagrupados, vea también varias buenas respuestas en Cómo agregar cero al valor faltante en r. -

4 Respuestas

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 NAsucede porque nomatch por defecto es NA. Conjunto nomatch a 0 en su lugar, para eliminar las coincidencias. Si en lugar de rellenar con NASi se requiere la fila predominante, simplemente agregue roll=TRUE. Esto puede ser más eficiente que rellenar con NAs y luego llenando NAs 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

@MattDowle Quizás edite su muy buena respuesta para incluir también características más recientes como on como alternativa a setkeyy 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

Puede usar el tidyr para esto.

Utiliza 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 Ids 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

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

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 or haz tu propia pregunta.