¿La forma más rápida de agregar filas para valores faltantes en data.frame?
dataframe plyr (4)
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 apilar los datos originales.
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?
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
#> <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 faltantes
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
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.