tabla - Creación de una función para reemplazar los NA de un data.frame con valores de otro
matrices en r (3)
Aquí hay una versión un poco más concisa / robusta de su enfoque. Podría reemplazar el for-loop con una llamada a lapply
, pero me parece que el bucle es más fácil de leer.
Esta función asume que cualquier columna que no mergeCols
en mergeCols
es un juego justo para que se llenen sus NA. No estoy realmente seguro de que esto ayude, pero me arriesgaré con los votantes.
fillNaDf.ju <- function(naDf, fillDf, mergeCols) {
mergedDf <- merge(fillDf, naDf, by=mergeCols, suffixes=c(".fill",""))
dataCols <- setdiff(names(naDf),mergeCols)
# loop over all columns we didn''t merge by
for(col in dataCols) {
rows <- is.na(mergedDf[,col])
# skip this column if it doesn''t contain any NAs
if(!any(rows)) next
rows <- which(rows)
# replace NAs with values from fillDf
mergedDf[rows,col] <- mergedDf[rows,paste(col,"fill",sep=".")]
}
# don''t return ".fill" columns
mergedDf[,names(naDf)]
}
Regularmente tengo situaciones en las que necesito reemplazar los valores perdidos de un data.frame con valores de algún otro data.frame que se encuentre en un nivel diferente de agregación. Por ejemplo, si tengo un data.frame lleno de datos del condado, podría reemplazar los valores de NA con los valores del estado almacenados en otro data.frame. Después de escribir la misma merge
... ifelse(is.na())
yada yada unas docenas de veces decidí descomponer y escribir una función para hacer esto.
Esto es lo que cociné, junto con un ejemplo de cómo lo uso:
fillNaDf <- function(naDf, fillDf, mergeCols, fillCols){
mergedDf <- merge(naDf, fillDf, by=mergeCols)
for (col in fillCols){
colWithNas <- mergedDf[[paste(col, "x", sep=".")]]
colWithOutNas <- mergedDf[[paste(col, "y", sep=".")]]
k <- which( is.na( colWithNas ) )
colWithNas[k] <- colWithOutNas[k]
mergedDf[col] <- colWithNas
mergedDf[[paste(col, "x", sep=".")]] <- NULL
mergedDf[[paste(col, "y", sep=".")]] <- NULL
}
return(mergedDf)
}
## test case
fillDf <- data.frame(a = c(1,2,1,2), b = c(3,3,4,4) ,f = c(100,200, 300, 400), g = c(11, 12, 13, 14))
naDf <- data.frame( a = sample(c(1,2), 100, rep=TRUE), b = sample(c(3,4), 100, rep=TRUE), f = sample(c(0,NA), 100, rep=TRUE), g = sample(c(0,NA), 200, rep=TRUE) )
fillNaDf(naDf, fillDf, mergeCols=c("a","b"), fillCols=c("f","g") )
Así que, después de correr, tuve la extraña sensación de que alguien probablemente haya resuelto este problema antes que yo y de una manera mucho más elegante. ¿Hay una solución mejor / más fácil / más rápida para este problema? Además, ¿hay alguna forma de eliminar el bucle en medio de mi función? Ese bucle está ahí porque a menudo estoy reemplazando los NA en más de una columna. Y, sí, la función asume que las columnas de las que estamos rellenando reciben el mismo nombre y las columnas que estamos rellenando, y lo mismo se aplica a la combinación.
Cualquier orientación o refactorización sería útil.
EDITAR el 2 de diciembre Me di cuenta de que tenía fallas lógicas en mi ejemplo que solucioné.
Mi preferencia sería sacar el código de la combinación que hace la coincidencia y hacerlo por mí mismo para que pueda mantener el orden del marco de datos original intacto, tanto por filas como por columnas. También utilizo la indexación matricial para evitar cualquier bucle, aunque para hacerlo, creo un nuevo marco de datos con el fillCols revisado y sustituyo las columnas del original por este; Pensé que podría completarlo directamente, pero aparentemente no puede usar el ordenamiento matricial para reemplazar partes de un cuadro de datos, por lo que no me sorprendería que un bucle entre los nombres fuera más rápido en algunas situaciones.
Con indexación matricial:
fillNaDf <- function(naDf, fillDf, mergeCols, fillCols) {
fillB <- do.call(paste, c(fillDf[, mergeCols, drop = FALSE], sep="/r"))
naB <- do.call(paste, c(naDf[, mergeCols, drop = FALSE], sep="/r"))
na.ind <- is.na(naDf[,fillCols])
fill.ind <- cbind(match(naB, fillB)[row(na.ind)[na.ind]], col(na.ind)[na.ind])
naX <- naDf[,fillCols]
fillX <- fillDf[,fillCols]
naX[na.ind] <- fillX[fill.ind]
naDf[,colnames(naX)] <- naX
naDf
}
Con un bucle:
fillNaDf2 <- function(naDf, fillDf, mergeCols, fillCols) {
fillB <- do.call(paste, c(fillDf[, mergeCols, drop = FALSE], sep="/r"))
naB <- do.call(paste, c(naDf[, mergeCols, drop = FALSE], sep="/r"))
m <- match(naB, fillB)
for(col in fillCols) {
fix <- which(is.na(naDf[,col]))
naDf[fix, col] <- fillDf[m[fix],col]
}
naDf
}
Qué gran pregunta.
Aquí hay una solución data.table
:
# Convert data.frames to data.tables (i.e. data.frames with extra powers;)
library(data.table)
fillDT <- data.table(fillDf, key=c("a", "b"))
naDT <- data.table(naDf, key=c("a", "b"))
# Merge data.tables, based on their keys (columns a & b)
outDT <- naDT[fillDT]
# a b f g f.1 g.1
# [1,] 1 3 NA 0 100 11
# [2,] 1 3 NA NA 100 11
# [3,] 1 3 NA 0 100 11
# [4,] 1 3 0 0 100 11
# [5,] 1 3 0 NA 100 11
# First 5 rows of 200 printed.
# In outDT[i, j], on the following two lines
# -- i is a Boolean vector indicating which rows will be operated on
# -- j is an expression saying "(sub)assign from right column (e.g. f.1) to
# left column (e.g. f)
outDT[is.na(f), f:=f.1]
outDT[is.na(g), g:=g.1]
# Just keep the four columns ultimately needed
outDT <- outDT[,list(a,b,g,f)]
# a b g f
# [1,] 1 3 0 0
# [2,] 1 3 11 0
# [3,] 1 3 0 0
# [4,] 1 3 11 0
# [5,] 1 3 11 0
# First 5 rows of 200 printed.