¿La forma más rápida de agregar filas para valores faltantes en data.frame?

Tengo una columna en mis conjuntos de datos donde los períodos de tiempo ( Time ) son enteros que van desde ab. A veces puede haber períodos de tiempo faltantes para cualquier grupo dado. Me gustaría completar esas filas con NA . A continuación se muestran los 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 estar perdidos. Puedo resolver esto por mi cuenta, pero me temo que no estaría haciendo esto de la manera más eficiente. Mi enfoque sería crear una función que:

Genera una secuencia de períodos de tiempo desde min(Time) hasta max(Time)

Luego haz un setdiff para obtener los valores de Time faltantes.

Convierte ese vector a data.frame

Extraiga las variables de identificador únicas ( Id y otras que no figuran en la lista anterior) y agréguelas a este data.frame.

Fusiona los dos.

Regreso de la función.

Entonces todo el proceso se ejecutará 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 forma de lograr esto?

Siguiendo con los comentarios de 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, ?CJ ves ?CJ . El relleno con NA ocurre porque nomatch por defecto es NA . Establezca nomatch a 0 lugar para eliminar la ausencia de coincidencias. Si en lugar de rellenar con NA s se requiere la fila que prevalece, simplemente agregue roll=TRUE . Esto puede ser más eficiente que rellenar con NA y luego llenar NA s después. Vea 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. 

Por favor, vea la respuesta de Matthew Dowle (por ahora, con suerte arriba).

Aquí hay algo que usa el paquete data.table , y puede ser útil cuando hay más de una variable de ID. También puede ser más rápido que merge , dependiendo de cómo desee sus resultados. Me interesaría la evaluación comparativa y / o las mejoras sugeridas.

Primero, cree 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 globalID en sus partes componentes en la combinación data.frame, antes de fusionarse con los datos originales. Esto debería (creo) ser mejor.

Puedes usar tidyr para esto.

Utilice tidyr::complete para completar las filas de Time , y de forma predeterminada los valores se completan con NA .

Crear datos

Extendí los datos de muestra para mostrar que funciona para múltiples Id e incluso cuando dentro de un Id no está presente el rango completo de Time .

 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 #>    #> 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 faltantes

 df %>% complete(nesting(Id), Time = seq(min(Time), max(Time), 1L)) #> # A tibble: 10 x 3 #> Id Time Value #>    #> 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 

Mi enfoque general es usar freqTable <- as.data.frame(table(idvar1, idvar2, idvarN)) luego sacar las filas donde Freq==0 , rellenar según sea necesario y luego volver a astackr los datos originales.