2012-08-15 2 views
9

dati Esempio:Come riempire 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 

Mi dica per favore, come posso sostituire NA in df $ valore mediano di altri mesi? "valore" deve contenere la mediana del valore di tutti i valori precedenti per lo stesso mese. Cioè, se il mese corrente è maggio, "valore" deve contenere il valore mediano per tutti i precedenti valori del mese di maggio.

+3

+1 perché sei riuscito a collegare 5 risposte diverse in 10 minuti. – Andrie

+0

Ho modificato la domanda per includere 'set.seed (1)' – Andrie

risposta

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)] 

Poiché ci sono così tante risposte vediamo che è più veloce.

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 

Avrei scommesso che data.table era il più veloce.

[Matthew Dowle] L'attività in fase di registrazione impiega al massimo 0,02 secondi (2,075/100). data.table lo considera insignificante. Prova a impostare replications su 1 e aumenta invece la dimensione dei dati. O anche la più veloce delle 3 serie è una regola comune. discussione più dettagliata in questi link:

+0

Grazie. Penso che funzioni correttamente – Sheridan

+3

+1 molto chiaramente. 'data.table' brilla davvero quando i dati diventano grandi e/o la variabile di raggruppamento ha molti livelli. con un set di dati diverso, tutte le tue tempistiche sarebbero significativamente diverse. – Justin

+0

Com'è 'ave' veramente diverso da' tapply'? È semplicemente 'tapply' con' mean' come sintassi di default e leggermente diversa? –

1

questo è un modo utilizzando plyr, non è molto bella, ma penso che fa ciò che si vuole:

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

si desidera utilizzare la funzione di test is.na:

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

che dice per tutti i valori in cui df$value è NA, sostituirlo con il lato destro. È necessario il pezzo na.rm=TRUE altrimenti la funzione di median tornerà NA

per fare questo mese per mese, ci sono molte scelte, ma penso che plyr ha la sintassi più semplice:

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

è anche possibile utilizzare data.table . questa è una scelta particolarmente buona se i tuoi dati sono grandi:

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

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

Ci sono molti altri modi, ma ce ne sono due!

+0

+1 per la spiegazione. Non uso molto 'plyr', quindi sono solo curioso, qual è la differenza principale tra' transform' (che hai usato) e 'riepiloga 'che Sacha ha usato? – A5C1D2H2I1M1N2O1R2T1

+1

'transform' è modificare o aggiungere una colonna a un' data.frame' esistente. come in esso restituirà l'intero frame dati fornito più eventuali nuove righe aggiunte. 'sumar' restituisce un" riepilogo "come media al mese o qualcosa e restituisce solo le righe specificate. – Justin

+0

Bello, non sapeva di 'trasformare'. Ho pensato che ci dovrebbe essere un modo per farlo in una riga con 'plyr'. –

3

Attaccando con base R, si può anche provare la seguente:

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

Funziona solo se solo le righe 61 - 72 contengono 'NA', che probabilmente non è il caso nel set di dati completo dell'OP. –

+0

@SachaEpskamp, ​​e quindi un downvote? Scusa, ma non vedo cos'altro ti aspetti. La tua soluzione fornisce una media mobile per oltre un anno di dati mancanti? Se è così, di nuovo, non sono un normale utente 'plyr', quindi per favore aggiorna la risposta con un esempio funzionante. – A5C1D2H2I1M1N2O1R2T1

+1

Siamo spiacenti, non era davvero necessario, ma non è possibile risolverlo. Trascorro troppo tempo con il downdiving di Reddit e diventa automatico :) Per quanto riguarda 'plyr', la risposta di Justin è molto meglio. –

4

Ecco la soluzione più robusta I può pensare. Assicura che gli anni siano ordinati correttamente e calcolerà correttamente la mediana per tutti i mesi precedenti nei casi in cui si hanno più anni con valori mancanti.

# 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

Bel lavoro per assicurarsi che i dati siano organizzati prima di provare a fare qualsiasi altra cosa. – A5C1D2H2I1M1N2O1R2T1

+0

+1 per soluzione robusta. – Andrie

1

C'è un altro modo per farlo con dplyr.

Se si desidera sostituire tutte le colonne con la loro mediana, do:

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

Se si desidera sostituire un sottoinsieme di colonne (come ad esempio "valore" nell'esempio di OP), do:

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