2011-12-01 14 views
17

Tengo regularmente situaciones en las que necesito reemplazar valores faltantes de un data.frame con valores de algún otro data.frame que está en un nivel diferente de agregación . Entonces, por ejemplo, si tengo un data.frame lleno de datos del condado, podría reemplazar los valores de NA con los valores de estado almacenados en otro data.frame. Después de escribir el mismo merge ... ifelse(is.na()) yada yada unas pocas docenas de veces, decidí analizar y escribir una función para hacer esto.Crear una función para reemplazar NA de un data.frame con valores de otro

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 que consiguiera este espectáculo de correr tuve esta extraña sensación de que alguien probablemente ha resuelto este problema antes de mí y en una una forma mucho más elegante. ¿Hay una solución mejor/más fácil/más rápida para este problema? Además, ¿hay alguna manera de eliminar el bucle en el medio de mi función? Ese ciclo está ahí porque a menudo estoy reemplazando NA en más de una columna. Y, sí, la función asume que las columnas que estamos rellenando de tienen el mismo nombre y las columnas que estamos rellenando en, y lo mismo se aplica a la fusión.

Cualquier orientación o refactorización sería útil.

EDITAR el 2 dic me di cuenta que tenía defectos lógicos en mi ejemplo, que me fijo.

Respuesta

14

¡Qué gran pregunta.

he aquí 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. 
+0

fresca. Algunos comentarios pueden ayudarme a darle sentido. ¡Parece conciso! :) –

+0

OK - Lo comenté un poco. Si está interesado en obtener más información, la sección 'Ejemplos' de '? Data.table' es ejemplar, y vale la pena los ~ 20 minutos que tarda en procesarse. Especialmente si usted es un tipo de Big Data, y parece que podría ser, realmente podría valer la pena la inversión de tiempo inicial. –

+0

Gracias Josh.Eso es realmente útil. –

5

Aquí hay una versión un poco más concisa/robusta de su enfoque. Puede reemplazar el for-loop con una llamada al lapply, pero encuentro que el bucle es más fácil de leer.

Esta función asume que cualquier columna no en mergeCols es un juego justo para llenar sus AN. Realmente no estoy 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)] 
} 
3

Mi preferencia sería para sacar el código de combinación que hace el juego y hacerlo yo mismo para que pudiera mantener el orden de la trama de datos original intacto, tanto por filas y por columnas. También uso la indexación matricial para evitar cualquier bucle, aunque para hacerlo creo un nuevo marco de datos con los fillCols revisados ​​y reemplazo las columnas del original con él; Pensé que podría completarlo directamente, pero aparentemente no se puede usar el orden de la matriz para reemplazar partes de un data.frame, por lo que no me sorprendería si un bucle sobre los nombres fuera más rápido en algunas situaciones.

Con la indexación de la matriz:

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 
} 
Cuestiones relacionadas