2012-08-15 14 views
9

Datos de ejemplo:¿Cómo llenar NA con la mediana?

set.seed(1) 
df <- data.frame(years=sort(rep(2005:2010, 12)), 
       months=1:12, 
       value=c(rnorm(60),NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA)) 

head(df) 
    years months  value 
1 2005  1 -0.6264538 
2 2005  2 0.1836433 
3 2005  3 -0.8356286 
4 2005  4 1.5952808 
5 2005  5 0.3295078 
6 2005  6 -0.8204684 

Dime por favor, cómo puedo reemplazar NA en df valor de $ a mediana de otros meses? "valor" debe contener la mediana del valor de todos los valores previos para el mismo mes. Es decir, si el mes actual es mayo, el "valor" debe contener el valor mediano de todos los valores anteriores del mes de mayo.

+3

+1 porque logró conectar 5 respuestas diferentes en 10 minutos. – Andrie

+0

Edité la pregunta para incluir 'set.seed (1)' – Andrie

Respuesta

8

O con ave

df <- data.frame(years=sort(rep(2005:2010, 12)), 
months=1:12, 
value=c(rnorm(60),NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA)) 
df$value[is.na(df$value)] <- with(df, ave(value, months, 
    FUN = function(x) median(x, na.rm = TRUE)))[is.na(df$value)] 

Puesto que hay tantas respuestas vamos a ver que es más rápido.

plyr2 <- function(df){ 
    medDF <- ddply(df,.(months),summarize,median=median(value,na.rm=TRUE)) 
df$value[is.na(df$value)] <- medDF$median[match(df$months,medDF$months)][is.na(df$value)] 
    df 
} 
library(plyr) 
library(data.table) 
DT <- data.table(df) 
setkey(DT, months) 


benchmark(ave = df$value[is.na(df$value)] <- 
    with(df, ave(value, months, 
       FUN = function(x) median(x, na.rm = TRUE)))[is.na(df$value)], 
      tapply = df$value[61:72] <- 
      with(df, tapply(value, months, median, na.rm=TRUE)), 
      sapply = df[61:72, 3] <- sapply(split(df[1:60, 3], df[1:60, 2]), median), 
      plyr = ddply(df, .(months), transform, 
         value=ifelse(is.na(value), median(value, na.rm=TRUE), value)), 
      plyr2 = plyr2(df), 
      data.table = DT[,value := ifelse(is.na(value), median(value, na.rm=TRUE), value), by=months], 
      order = "elapsed") 
     test replications elapsed relative user.self sys.self user.child sys.child 
3  sapply   100 0.209 1.000000  0.196 0.000   0   0 
1  ave   100 0.260 1.244019  0.244 0.000   0   0 
6 data.table   100 0.271 1.296651  0.264 0.000   0   0 
2  tapply   100 0.271 1.296651  0.256 0.000   0   0 
5  plyr2   100 1.675 8.014354  1.612 0.004   0   0 
4  plyr   100 2.075 9.928230  2.004 0.000   0   0 

Hubiera apostado a que data.table fue el más rápido.

[Matthew Dowle] La tarea que se está programando aquí toma como máximo 0.02 segundos (2.075/100). data.table considera que es insignificante. Intente configurar replications en 1 y aumente el tamaño de los datos, en su lugar. O cronometrar el más rápido de 3 carreras también es una regla práctica común. Una discusión más detallada de estos enlaces:

+0

Gracias. Creo que está funcionando correctamente – Sheridan

+3

+1 muy claramente hecho. 'data.table' realmente brilla una vez que los datos se vuelven grandes y/o la variable de agrupamiento tiene muchos niveles. con un conjunto de datos diferente, todos los tiempos serían significativamente diferentes. – Justin

+0

¿En qué se diferencia 'ave' de' tapply'? ¿Es simplemente 'tapply' con' mean' como sintaxis predeterminada y ligeramente diferente? –

1

Esta es una manera de usar plyr, que no es muy bonito, pero creo que hace lo que quiere:

library("plyr") 

# Make a separate dataframe with month as first column and median as second: 
medDF <- ddply(df,.(months),summarize,median=median(value,na.rm=TRUE)) 

# Replace `NA` values in `df$value` with medians from the second data frame 
# match() here ensures that the medians are entered in the correct elements. 
df$value[is.na(df$value)] <- medDF$median[match(df$months,medDF$months)][is.na(df$value)] 
6

desea utilizar la función de prueba is.na:

df$value[is.na(df$value)] <- median(df$value, na.rm=TRUE) 

que dice para todos los valores donde df$value es NA, reemplácelo con el lado derecho. Es necesario la pieza na.rm=TRUE o de lo contrario la función median volverá NA

a hacer este mes a mes, hay muchas opciones, pero creo plyr tiene la sintaxis más simple:

library(plyr) 
ddply(df, 
     .(months), 
     transform, 
     value=ifelse(is.na(value), median(value, na.rm=TRUE), value)) 

también se puede utilizar data.table . esta es una opción especialmente buena si sus datos son grandes:

library(data.table) 
DT <- data.table(df) 
setkey(DT, months) 

DT[,value := ifelse(is.na(value), median(value, na.rm=TRUE), value), by=months] 

¡Hay muchas otras maneras, pero hay dos!

+0

+1 para la explicación. No uso mucho 'plyr', así que solo tengo curiosidad, ¿cuál es la principal diferencia entre' transform' (que usaste) y 'summarize' que usó Sacha? – A5C1D2H2I1M1N2O1R2T1

+1

'transform' es alterar o agregar una columna a un' data.frame' existente. como en él devolverá todo el marco de datos proporcionado más cualquier nueva fila que haya agregado. 'summarise' devuelve un" resumen "como promedio por mes o algo así y solo devuelve las filas especificadas. – Justin

+0

Bueno, no sabía nada de 'transformar'. Pensé que debería haber una manera de hacerlo en una línea con 'plyr'. –

3

Siguiendo con base de R, también puede intentar lo siguiente:

medians = sapply(split(df[1:60, 3], df[1:60, 2]), median) 
df[61:72, 3] = medians 
+0

Esto solo funciona si exactamente solo las filas 61 - 72 contienen 'NA ', que probablemente no sea el caso en el conjunto de datos completo de OP. –

+0

@SachaEpskamp, ​​y por lo tanto un voto negativo? Lo siento, pero no veo qué más esperas. ¿Su solución proporciona una mediana móvil durante más de un año de datos faltantes? Si es así, una vez más, no soy un usuario habitual de 'plyr', así que actualice su respuesta con un ejemplo trabajado. – A5C1D2H2I1M1N2O1R2T1

+1

Lo siento, no era necesario, pero no puedo arreglarlo. Paso demasiado tiempo en Reddit, votando cosas, se vuelve automático :) En cuanto a 'plyr', la respuesta de Justins es mucho mejor. –

4

Ésta es la solución más robusta que puedo pensar Asegura que los años se ordenan correctamente y calculará correctamente la mediana para todos los meses anteriores en los casos en que tenga varios años con valores perdidos.

# first, reshape your data so it is years by months: 
library(reshape2) 
tmp <- dcast(years ~ months, data=df) # convert data to years x months 
tmp <- tmp[order(tmp$years),]   # order years 
# now calculate the running median on each month 
library(caTools) 
# function to replace NA with rolling median 
tmpfun <- function(x) { 
    ifelse(is.na(x), runquantile(x, k=length(x), probs=0.5, align="right"), x) 
} 
# apply tmpfun to each column and convert back to data.frame 
tmpmed <- as.data.frame(lapply(tmp, tmpfun)) 
# reshape back to long and convert 'months' back to integer 
res <- melt(tmpmed, "years", variable.name="months") 
res$months <- as.integer(gsub("^X","",res$months)) 
+0

Buen trabajo para asegurarse de que los datos estén organizados antes de intentar hacer cualquier otra cosa. – A5C1D2H2I1M1N2O1R2T1

+0

+1 Para una solución robusta. – Andrie

1

Hay otra manera de hacerlo con dplyr.

Si desea reemplazar todas las columnas con su medio, hacer:

library(dplyr) 
df %>% 
    mutate_all(~ifelse(is.na(.), median(., na.rm = TRUE), .)) 

Si desea sustituir un subconjunto de columnas (como el "valor" en el ejemplo de la OP), hacer:

df %>% 
    mutate_at(vars(value), ~ifelse(is.na(.), median(., na.rm = TRUE), .))