Come posso sostituire i valori NA con zeri in un frame di dati R?


Risposte:


880

Vedi il mio commento nella risposta @ gsk3. Un semplice esempio:

> m <- matrix(sample(c(NA, 1:10), 100, replace = TRUE), 10)
> d <- as.data.frame(m)
   V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
1   4  3 NA  3  7  6  6 10  6   5
2   9  8  9  5 10 NA  2  1  7   2
3   1  1  6  3  6 NA  1  4  1   6
4  NA  4 NA  7 10  2 NA  4  1   8
5   1  2  4 NA  2  6  2  6  7   4
6  NA  3 NA NA 10  2  1 10  8   4
7   4  4  9 10  9  8  9  4 10  NA
8   5  8  3  2  1  4  5  9  4   7
9   3  9 10  1  9  9 10  5  3   3
10  4  2  2  5 NA  9  7  2  5   5

> d[is.na(d)] <- 0

> d
   V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
1   4  3  0  3  7  6  6 10  6   5
2   9  8  9  5 10  0  2  1  7   2
3   1  1  6  3  6  0  1  4  1   6
4   0  4  0  7 10  2  0  4  1   8
5   1  2  4  0  2  6  2  6  7   4
6   0  3  0  0 10  2  1 10  8   4
7   4  4  9 10  9  8  9  4 10   0
8   5  8  3  2  1  4  5  9  4   7
9   3  9 10  1  9  9 10  5  3   3
10  4  2  2  5  0  9  7  2  5   5

Non è necessario applicare apply. =)

MODIFICARE

Dovresti anche dare un'occhiata al normpacchetto. Ha molte funzioni interessanti per l'analisi dei dati mancanti. =)


2
Ho già provato questo codice ieri prima di pubblicarlo e non ha funzionato. Perché questo ho pubblicato la domanda. Ma ho provato a conoscerlo e ha funzionato perfettamente. Penso che stavo facendo qualcosa di sbagliato.
Renato Dinhani,

12
@ RenatoDinhaniConceição: se hai già provato qualcosa, è utile condividere tali informazioni quando fai la domanda; aiuta a restringere dove potrebbe essere il problema.
Aaron ha lasciato Stack Overflow il

2
d [is.na (d)] <- 0 non ha senso per me. Sembra al contrario? In che modo R elabora questa affermazione?
user798719

13
@ user798719 - "<-" è l'operatore di assegnazione di R, e può essere letto come: fare qualcosa sul lato destro e quindi assegnarlo alla posizione / nome a sinistra. In questo caso, non stiamo davvero "facendo" nulla, ma solo facendo zero. Il lato sinistro sta dicendo: guarda l'oggetto d, all'interno dell'oggetto d (le parentesi quadre), trova tutti gli elementi che restituiscono VERO (is.na (d) restituisce una logica per ogni elemento). Una volta trovati, sostituiscili ("assegnali") con il valore 0. Ciò lascia tutti i non-NA come erano e sostituisce solo quelli con mancanza.
Twitch_City

3
E ... se hai un frame di dati e vuoi applicare la sostituzione solo a specifici vettori nurmeric (lasciando dire ... stringhe con NA):df[19:28][is.na(df[19:28])] <- 0
jtdoud,

299

Le opzioni ibride dplyr sono ora circa il 30% più veloci rispetto alla riassegnazione del sottoinsieme Base R. Su un datapoint da 100M il dataframe mutate_all(~replace(., is.na(.), 0))viene eseguito mezzo secondo più veloce d[is.na(d)] <- 0dell'opzione R di base . Quello che si vuole evitare nello specifico è usare un ifelse()o un if_else(). (L'analisi completa di 600 prove è durata per oltre 4,5 ore principalmente a causa dell'inclusione di questi approcci.) Per i risultati completi, consultare le analisi di riferimento riportate di seguito.

Se stai lottando con enormi frame di dati, data.tableè l'opzione più veloce di tutte: il 40% più veloce dell'approccio Base R standard . Modifica inoltre i dati in atto, consentendoti di lavorare con quasi il doppio di tutti i dati contemporaneamente.


Un raggruppamento di altri utili approcci di sostituzione ordinata

Locationally:

  • indice mutate_at(c(5:10), ~replace(., is.na(.), 0))
  • riferimento diretto mutate_at(vars(var5:var10), ~replace(., is.na(.), 0))
  • partita fissa mutate_at(vars(contains("1")), ~replace(., is.na(.), 0))
    • o al posto di contains(), prova ends_with(),starts_with()
  • corrispondenza del modello mutate_at(vars(matches("\\d{2}")), ~replace(., is.na(.), 0))

Condizionalmente:
(cambia solo un singolo tipo e lascia solo altri tipi.)

  • interi mutate_if(is.integer, ~replace(., is.na(.), 0))
  • numeri mutate_if(is.numeric, ~replace(., is.na(.), 0))
  • stringhe mutate_if(is.character, ~replace(., is.na(.), 0))

L'analisi completa -

Aggiornato per dplyr 0.8.0: le funzioni usano ~simboli in formato purrr : sostituzione di funs()argomenti obsoleti .

Approcci testati:

# Base R: 
baseR.sbst.rssgn   <- function(x) { x[is.na(x)] <- 0; x }
baseR.replace      <- function(x) { replace(x, is.na(x), 0) }
baseR.for          <- function(x) { for(j in 1:ncol(x))
    x[[j]][is.na(x[[j]])] = 0 }

# tidyverse
## dplyr
dplyr_if_else      <- function(x) { mutate_all(x, ~if_else(is.na(.), 0, .)) }
dplyr_coalesce     <- function(x) { mutate_all(x, ~coalesce(., 0)) }

## tidyr
tidyr_replace_na   <- function(x) { replace_na(x, as.list(setNames(rep(0, 10), as.list(c(paste0("var", 1:10)))))) }

## hybrid 
hybrd.ifelse     <- function(x) { mutate_all(x, ~ifelse(is.na(.), 0, .)) }
hybrd.replace_na <- function(x) { mutate_all(x, ~replace_na(., 0)) }
hybrd.replace    <- function(x) { mutate_all(x, ~replace(., is.na(.), 0)) }
hybrd.rplc_at.idx<- function(x) { mutate_at(x, c(1:10), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.nse<- function(x) { mutate_at(x, vars(var1:var10), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.stw<- function(x) { mutate_at(x, vars(starts_with("var")), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.ctn<- function(x) { mutate_at(x, vars(contains("var")), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.mtc<- function(x) { mutate_at(x, vars(matches("\\d+")), ~replace(., is.na(.), 0)) }
hybrd.rplc_if    <- function(x) { mutate_if(x, is.numeric, ~replace(., is.na(.), 0)) }

# data.table   
library(data.table)
DT.for.set.nms   <- function(x) { for (j in names(x))
    set(x,which(is.na(x[[j]])),j,0) }
DT.for.set.sqln  <- function(x) { for (j in seq_len(ncol(x)))
    set(x,which(is.na(x[[j]])),j,0) }
DT.nafill        <- function(x) { nafill(df, fill=0)}
DT.setnafill     <- function(x) { setnafill(df, fill=0)}

Il codice per questa analisi:

library(microbenchmark)
# 20% NA filled dataframe of 10 Million rows and 10 columns
set.seed(42) # to recreate the exact dataframe
dfN <- as.data.frame(matrix(sample(c(NA, as.numeric(1:4)), 1e7*10, replace = TRUE),
                            dimnames = list(NULL, paste0("var", 1:10)), 
                            ncol = 10))
# Running 600 trials with each replacement method 
# (the functions are excecuted locally - so that the original dataframe remains unmodified in all cases)
perf_results <- microbenchmark(
    hybrid.ifelse    = hybrid.ifelse(copy(dfN)),
    dplyr_if_else    = dplyr_if_else(copy(dfN)),
    hybrd.replace_na = hybrd.replace_na(copy(dfN)),
    baseR.sbst.rssgn = baseR.sbst.rssgn(copy(dfN)),
    baseR.replace    = baseR.replace(copy(dfN)),
    dplyr_coalesce   = dplyr_coalesce(copy(dfN)),
    tidyr_replace_na = tidyr_replace_na(copy(dfN)),
    hybrd.replace    = hybrd.replace(copy(dfN)),
    hybrd.rplc_at.ctn= hybrd.rplc_at.ctn(copy(dfN)),
    hybrd.rplc_at.nse= hybrd.rplc_at.nse(copy(dfN)),
    baseR.for        = baseR.for(copy(dfN)),
    hybrd.rplc_at.idx= hybrd.rplc_at.idx(copy(dfN)),
    DT.for.set.nms   = DT.for.set.nms(copy(dfN)),
    DT.for.set.sqln  = DT.for.set.sqln(copy(dfN)),
    times = 600L
)

Riepilogo dei risultati

> print(perf_results)
Unit: milliseconds
              expr       min        lq     mean   median       uq      max neval
      hybrd.ifelse 6171.0439 6339.7046 6425.221 6407.397 6496.992 7052.851   600
     dplyr_if_else 3737.4954 3877.0983 3953.857 3946.024 4023.301 4539.428   600
  hybrd.replace_na 1497.8653 1706.1119 1748.464 1745.282 1789.804 2127.166   600
  baseR.sbst.rssgn 1480.5098 1686.1581 1730.006 1728.477 1772.951 2010.215   600
     baseR.replace 1457.4016 1681.5583 1725.481 1722.069 1766.916 2089.627   600
    dplyr_coalesce 1227.6150 1483.3520 1524.245 1519.454 1561.488 1996.859   600
  tidyr_replace_na 1248.3292 1473.1707 1521.889 1520.108 1570.382 1995.768   600
     hybrd.replace  913.1865 1197.3133 1233.336 1238.747 1276.141 1438.646   600
 hybrd.rplc_at.ctn  916.9339 1192.9885 1224.733 1227.628 1268.644 1466.085   600
 hybrd.rplc_at.nse  919.0270 1191.0541 1228.749 1228.635 1275.103 2882.040   600
         baseR.for  869.3169 1180.8311 1216.958 1224.407 1264.737 1459.726   600
 hybrd.rplc_at.idx  839.8915 1189.7465 1223.326 1228.329 1266.375 1565.794   600
    DT.for.set.nms  761.6086  915.8166 1015.457 1001.772 1106.315 1363.044   600
   DT.for.set.sqln  787.3535  918.8733 1017.812 1002.042 1122.474 1321.860   600

Diagramma di risultati

ggplot(perf_results, aes(x=expr, y=time/10^9)) +
    geom_boxplot() +
    xlab('Expression') +
    ylab('Elapsed Time (Seconds)') +
    scale_y_continuous(breaks = seq(0,7,1)) +
    coord_flip()

Confronto di Boxplot del tempo trascorso

Scatterplot di prove con codice colore (con asse y su scala logaritmica)

qplot(y=time/10^9, data=perf_results, colour=expr) + 
    labs(y = "log10 Scaled Elapsed Time per Trial (secs)", x = "Trial Number") +
    coord_cartesian(ylim = c(0.75, 7.5)) +
    scale_y_log10(breaks=c(0.75, 0.875, 1, 1.25, 1.5, 1.75, seq(2, 7.5)))

Grafico a dispersione di tutti i tempi di prova

Una nota sugli altri performer

Quando il set di dati più grandi, Tidyr 's' replace_naerano storicamente tirato fuori di fronte. Con l'attuale raccolta di 100 milioni di punti dati da eseguire, esegue quasi esattamente così come una Base R For Loop. Sono curioso di vedere cosa succede per frame di dati di dimensioni diverse.

Ulteriori esempi per l' mutatee summarize _ate _allvarianti di funzione possono essere trovate qui: https://rdrr.io/cran/dplyr/man/summarise_all.html Inoltre, ho trovato dimostrazioni utile e collezioni di esempi qui: https: //blog.exploratory. IO / dplyr-0-5-is-awesome-Heres-perché-be095fd4eb8a

Attribuzioni e apprezzamenti

Con ringraziamenti speciali a:

  • Tyler Rinker e Akrun per aver dimostrato microbenchmark.
  • alexis_laz per aver lavorato su come aiutarmi a capire l'uso local()e (anche con l'aiuto paziente di Frank) del ruolo che la coercizione silenziosa gioca nel velocizzare molti di questi approcci.
  • ArthurYip per il poke per aggiungere la nuova coalesce()funzione e aggiornare l'analisi.
  • Gregor per la spinta a capire le data.tablefunzioni abbastanza bene da includerle finalmente nella formazione.
  • Base R Per loop: alexis_laz
  • data.table Per i loop: Matt_Dowle
  • Romano per aver spiegato cosa is.numeric()prova davvero.

(Naturalmente, per favore, avvicinati e dai loro voti anche se ritieni che questi approcci siano utili.)

Nota sul mio uso di Numerics: se si dispone di un set di dati intero puro, tutte le funzioni verranno eseguite più rapidamente. Perulteriori informazioni,consultare il lavoro di alexiz_laz . IRL, non ricordo di aver incontrato un set di dati contenente più del 10-15% di numeri interi, quindi sto eseguendo questi test su frame di dati completamente numerici.

Hardware utilizzato CPU da 3,9 GHz con 24 GB di RAM


2
@Frank - Grazie per aver trovato quella discrepanza. I riferimenti vengono ripuliti e i risultati sono stati rieseguiti su un'unica macchina e ripubblicati.
leerssej

Ok grazie. Inoltre, penso che df1[j][is.na(df1[j])] = 0sia sbagliato, dovrebbe esseredf1[[j]][is.na(df1[[j]])] = 0
Frank

Oh ora vedo che l'hai scritto due volte, diversamente in ogni benchmark. Ad ogni modo, forLp_Sbstnon sembra un modo in cui qualcuno dovrebbe considerare di avvicinarsi a questoforLp_smplfSbst
Frank

1
@UweBlock - ottima domanda: mi ha permesso di eseguire l'operazione di assegnazione a sinistra del sottoinsieme con tutte le funzioni che funzionano esattamente sullo stesso frame di dati. Dato che dovevo avvolgere il locale attorno a quella funzione, poi in nome della scienza [Un lavoro, avevi un lavoro!] L'ho avvolto attorno a tutti in modo che il campo da gioco fosse inequivocabilmente a livello. Per maggiori informazioni - vedi qui: stackoverflow.com/questions/41604711/… Avevo ridotto la precedente risposta piuttosto lungimirante - ma quella parte della discussione sarebbe buona da aggiungere. Grazie!
leerssej,

1
@ArthurYip - Ho aggiunto l' coalesce()opzione e rieseguito tutte le volte. Grazie per l'aggiornamento.
Leerssej,

128

Per un singolo vettore:

x <- c(1,2,NA,4,5)
x[is.na(x)] <- 0

Per un data.frame, crea una funzione da quanto sopra, quindi applyverso le colonne.

Si prega di fornire un esempio riproducibile la prossima volta come dettagliato qui:

Come fare un ottimo esempio riproducibile di R?


18
is.naè una funzione generica e ha metodi per oggetti di data.frameclasse. quindi questo funzionerà anche su data.frames!
aL3xa,

3
Quando ho corso methods(is.na)per la prima volta, ero come whaaa?!? . Adoro quando succedono cose del genere! =)
aL3xa

9
Supponiamo di avere un frame di dati chiamato df invece di un singolo vettore e desideri semplicemente sostituire le osservazioni mancanti in una singola colonna denominata X3. Puoi farlo con questa riga: df $ X3 [is.na (df $ X3)] <- 0
Mark Miller

8
Supponiamo di voler sostituire NA con 0 nelle colonne 4-6 di un frame di dati chiamato my.df. Puoi usare: my.df [, 4: 6] [is.na (my.df [, 4: 6])] <- 0
Mark Miller

come mai passi 'x' a is.na (x) c'è un modo per dire quali routine di libreria in R sono vettorializzate?
uh_big_mike_boi

73

Esempio dplyr:

library(dplyr)

df1 <- df1 %>%
    mutate(myCol1 = if_else(is.na(myCol1), 0, myCol1))

Nota: questo funziona per colonna selezionata, se abbiamo bisogno di fare questo per tutta la colonna, vedi @reidjax risposta 's utilizzando mutate_each .


57

Se stiamo provando a sostituire NAs durante l'esportazione, ad esempio quando scriviamo in CSV, allora possiamo usare:

  write.csv(data, "data.csv", na = "0")

47

So che la domanda ha già una risposta, ma farlo in questo modo potrebbe essere più utile per alcuni:

Definisci questa funzione:

na.zero <- function (x) {
    x[is.na(x)] <- 0
    return(x)
}

Ora, ogni volta che devi convertire NA in un vettore in zero, puoi fare:

na.zero(some.vector)

22

Con dplyr0.5.0, puoi usare una coalescefunzione che può essere facilmente integrata nella %>%pipeline coalesce(vec, 0). Questo sostituisce tutti i NA veccon 0:

Supponiamo di avere un frame di dati con NAs:

library(dplyr)
df <- data.frame(v = c(1, 2, 3, NA, 5, 6, 8))

df
#    v
# 1  1
# 2  2
# 3  3
# 4 NA
# 5  5
# 6  6
# 7  8

df %>% mutate(v = coalesce(v, 0))
#   v
# 1 1
# 2 2
# 3 3
# 4 0
# 5 5
# 6 6
# 7 8

Ho testato la coesione e funziona allo stesso modo di sostituire. il comando di coalescenza è finora il più semplice!
Arthur Yip,

sarebbe utile se si presentasse come applicarlo su tutte le colonne della colonna 2+ colonne.
jangorecki,

21

Approccio più generale di utilizzare replace()in matrice o vettore di sostituire NAa0

Per esempio:

> x <- c(1,2,NA,NA,1,1)
> x1 <- replace(x,is.na(x),0)
> x1
[1] 1 2 0 0 1 1

Questa è anche un'alternativa all'utilizzo di ifelse()indplyr

df = data.frame(col = c(1,2,NA,NA,1,1))
df <- df %>%
   mutate(col = replace(col,is.na(col),0))

1
La mia colonna è stata un fattore importante, quindi ho dovuto aggiungere il mio valore di sostituzionelevels(A$x) <- append(levels(A$x), "notAnswered") A$x <- replace(A$x,which(is.na(A$x)),"notAnswered")
Climbs_lika_Spyder

1
whichnon è necessario qui, è possibile utilizzare x1 <- replace(x,is.na(x),1).
lmo,

Ho provato molti modi proposti in questo thread per sostituire NAa 0in una sola colonna specifica in un ampio frame di dati e questa funzione replace()lavorato più efficacemente, anche il più semplice.
Duc,

19

È anche possibile usare tidyr::replace_na.

    library(tidyr)
    df <- df %>% mutate_all(funs(replace_na(.,0)))

9

Un altro esempio che utilizza il pacchetto imputeTS :

library(imputeTS)
na.replace(yourDataframe, 0)

9

Se vuoi sostituire i NA nelle variabili fattore, questo potrebbe essere utile:

n <- length(levels(data.vector))+1

data.vector <- as.numeric(data.vector)
data.vector[is.na(data.vector)] <- n
data.vector <- as.factor(data.vector)
levels(data.vector) <- c("level1","level2",...,"leveln", "NAlevel") 

Trasforma un vettore-fattore in un vettore numerico e aggiunge un altro livello di fattore numerico artificiale, che viene poi trasformato in un vettore-fattore con un "livello NA" aggiuntivo a scelta.


8

Avrei commentato il post di @ ianmunoz ma non ho abbastanza reputazione. Puoi combinare dplyr's mutate_eachereplace di prendersi cura della NAa 0sostituzione. Utilizzo del dataframe dalla risposta di @ aL3xa ...

> m <- matrix(sample(c(NA, 1:10), 100, replace = TRUE), 10)
> d <- as.data.frame(m)
> d

    V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
1   4  8  1  9  6  9 NA  8  9   8
2   8  3  6  8  2  1 NA NA  6   3
3   6  6  3 NA  2 NA NA  5  7   7
4  10  6  1  1  7  9  1 10  3  10
5  10  6  7 10 10  3  2  5  4   6
6   2  4  1  5  7 NA NA  8  4   4
7   7  2  3  1  4 10 NA  8  7   7
8   9  5  8 10  5  3  5  8  3   2
9   9  1  8  7  6  5 NA NA  6   7
10  6 10  8  7  1  1  2  2  5   7

> d %>% mutate_each( funs_( interp( ~replace(., is.na(.),0) ) ) )

    V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
1   4  8  1  9  6  9  0  8  9   8
2   8  3  6  8  2  1  0  0  6   3
3   6  6  3  0  2  0  0  5  7   7
4  10  6  1  1  7  9  1 10  3  10
5  10  6  7 10 10  3  2  5  4   6
6   2  4  1  5  7  0  0  8  4   4
7   7  2  3  1  4 10  0  8  7   7
8   9  5  8 10  5  3  5  8  3   2
9   9  1  8  7  6  5  0  0  6   7
10  6 10  8  7  1  1  2  2  5   7

Stiamo utilizzando la valutazione standard (SE) qui, motivo per cui abbiamo bisogno del carattere di sottolineatura su " funs_." Usiamo anche lazyeval" interp/" ~i .riferimenti "tutto ciò con cui stiamo lavorando", ovvero il frame di dati. Ora ci sono zeri!


4

Puoi usare replace()

Per esempio:

> x <- c(-1,0,1,0,NA,0,1,1)
> x1 <- replace(x,5,1)
> x1
[1] -1  0  1  0  1  0  1  1

> x1 <- replace(x,5,mean(x,na.rm=T))
> x1
[1] -1.00  0.00  1.00  0.00  0.29  0.00 1.00  1.00

6
Vero, ma pratico solo quando conosci l'indice di NAs nel tuo vettore. Va bene per i piccoli vettori come nel tuo esempio.
dardisco,

4
@dardisco x1 <- replace(x,is.na(x),1)funzionerà senza elencare esplicitamente i valori dell'indice.
lmo

4

Un'altra dplyropzione compatibile con pipe con tidyrmetodo replace_nache funziona per diverse colonne:

require(dplyr)
require(tidyr)

m <- matrix(sample(c(NA, 1:10), 100, replace = TRUE), 10)
d <- as.data.frame(m)

myList <- setNames(lapply(vector("list", ncol(d)), function(x) x <- 0), names(d))

df <- d %>% replace_na(myList)

Puoi facilmente limitare ad es. Colonne numeriche:

d$str <- c("string", NA)

myList <- myList[sapply(d, is.numeric)]

df <- d %>% replace_na(myList)

4

La funzione dedicata ( nafill/ setnafill) a tale scopo è nella data.tableversione recente

install.packages("data.table", repos="https://Rdatatable.gitlab.io/data.table")
library(data.table)
ans_df = nafill(df, fill=0)
setnafill(df, fill=0) # this one updates in-place

Per coloro che effettuano il downvoting, si prega di fornire anche feedback, quindi la mia risposta può essere migliorata.
jangorecki,

3

Questa semplice funzione estratta da Datacamp potrebbe aiutare:

replace_missings <- function(x, replacement) {
  is_miss <- is.na(x)
  x[is_miss] <- replacement

  message(sum(is_miss), " missings replaced by the value ", replacement)
  x
}

Poi

replace_missings(df, replacement = 0)

3

Un modo semplice per scrivere è con if_nada hablar:

library(dplyr)
library(hablar)

df <- tibble(a = c(1, 2, 3, NA, 5, 6, 8))

df %>% 
  mutate(a = if_na(a, 0))

che ritorna:

      a
  <dbl>
1     1
2     2
3     3
4     0
5     5
6     6
7     8

2

Per sostituire tutti i NA in un dataframe è possibile utilizzare:

df %>% replace(is.na(.), 0)


questa non è una nuova soluzione
jogo

1

se si desidera assegnare un nuovo nome dopo aver modificato le NA in una colonna specifica in questo caso colonna V3, utilizzare è possibile fare anche così

my.data.frame$the.new.column.name <- ifelse(is.na(my.data.frame$V3),0,1)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.