web-dev-qa-db-fra.com

Comment remplacer les valeurs NA par des zéros dans une trame de données R?

J'ai un cadre de données et certaines colonnes ont des valeurs NA.

Comment remplacer ces valeurs NA par des zéros?

632
Renato Dinhani

Voir mon commentaire dans la réponse @ gsk3. Un exemple simple:

> 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

Il n'est pas nécessaire d'appliquer apply. =)

EDIT

Vous devriez également jeter un oeil au paquetage norm. Il a beaucoup de fonctionnalités de Nice pour l'analyse des données manquantes. =)

775
aL3xa

Les options hybrides de dplyr sont maintenant environ 30% plus rapides que celles du sous-ensemble Base R. Sur une structure de données de points de données 100M, mutate_all(~replace(., is.na(.), 0)) s'exécute une demi-seconde plus vite que l'option de base R d[is.na(d)] <- 0. Ce que l’on veut éviter en particulier, c’est d’utiliser une ifelse() ou une if_else(). (L'analyse complète de 600 essais a duré plus de 4,5 heures, principalement en raison de l'intégration de ces approches.) Veuillez consulter les analyses de référence ci-dessous pour obtenir les résultats complets.

Si vous êtes confronté à des blocs de données volumineux, _data.table_ est l'option la plus rapide: 40% plus rapide que l'approche standard Base R . Il modifie également les données en place, vous permettant ainsi de travailler avec presque deux fois plus de données à la fois.


Un regroupement d’autres approches utiles pour remplacer le nid

Emplacement:

  • index mutate_at(c(5:10), ~replace(., is.na(.), 0))
  • référence directe mutate_at(vars(var5:var10), ~replace(., is.na(.), 0))
  • correspondance mutate_at(vars(contains("1")), ~replace(., is.na(.), 0))
    • ou à la place de contains(), essayez ends_with(), starts_with()
  • correspondance de motif mutate_at(vars(matches("\\d{2}")), ~replace(., is.na(.), 0))

conditionnellement:
(changez simplement numérique (colonnes) et laissez chaîne (colonnes) seul.)

  • entiers mutate_if(is.integer, ~replace(., is.na(.), 0))
  • double mutate_if(is.numeric, ~replace(., is.na(.), 0))
  • chaînes mutate_if(is.character, ~replace(., is.na(.), 0))

L'analyse complète -

Mis à jour pour dplyr 0.8.0: les fonctions utilisent le format purrr ~ symboles: remplacement des arguments obsolètes funs().

Approches testées:

_# 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.fnafill       <- function(x) { fnafill(df, fill=0)}
DT.setnafill     <- function(x) { setnafill(df, fill=0)}
_

Le code de cette analyse:

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

Résumé des résultats

_> 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
_

Box box des résultats

_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()
_

Boxplot Comparison of Elapsed Time

Diagramme de dispersion des essais (avec l'axe des ordonnées sur une échelle de notation)

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

Scatterplot of All Trial Times

Une note sur les autres performants

Lorsque les ensembles de données sont plus volumineux, Tidyr 's _replace_na_ s'est historiquement retiré au premier plan. Avec la collection actuelle de 50 millions de points de données à parcourir, la performance est presque identique à celle d'un Base R pour une boucle. Je suis curieux de voir ce qui se passe pour des cadres de données de différentes tailles.

Des exemples supplémentaires pour les variantes de fonction mutate et summarize __at_ et __all_ peuvent être trouvés ici: https://rdrr.io/cran/dplyr/man /summarise_all.html De plus, j'ai trouvé des démonstrations utiles et une collection d'exemples ici: https://blog.exploratory.io/dplyr-0-5-is-awesome-heres-why-be095fd4eb8a =

Attributions et Appréciations

Avec un merci spécial à:

  • Tyler Rinker et Akrun pour la démonstration du micro-repère.
  • alexis_laz pour m'avoir aidé à comprendre l'utilisation de local(), et (avec l'aide patiente de Frank également) le rôle que la coercition silencieuse joue dans l'accélération de nombre de ces approches.
  • ArthurYip pour l'aiguille pour ajouter la fonction coalesce() plus récente dans et mettre à jour l'analyse.
  • Gregor for the Nudge pour comprendre le fonctionnement de _data.table_ suffisamment bien pour enfin les inclure dans la gamme.
  • Base R Pour la boucle: alexis_laz )
  • data.table Pour les boucles: Matt_Dowle

(Bien sûr, s'il vous plaît, approchez-vous et donnez-leur des votes positifs, si vous trouvez ces approches utiles.)

Note sur mon utilisation de Numerics: Si vous avez un jeu de données entier pur, toutes vos fonctions va courir plus vite. S'il vous plaît voir travail de alexiz_laz pour plus d'informations. IRL, je ne me souviens pas avoir rencontré un ensemble de données contenant plus de 10 à 15% d'entiers. Je teste donc ces tests sur des cadres de données entièrement numériques.

Matériel utilisé CPU 3,9 GHz avec 24 Go de RAM

236
leerssej

Pour un seul vecteur:

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

Pour un data.frame, créez une fonction à partir de ce qui précède, puis apply dans les colonnes.

Veuillez fournir un exemple reproductible la prochaine fois, comme indiqué ici:

Comment faire un bon exemple reproductible de R?

114
Ari B. Friedman

exemple dplyr:

library(dplyr)

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

Remarque: Ceci fonctionne par colonne sélectionnée, si nous devons le faire pour toutes les colonnes, voir @ reidjax à l'aide de mutate_each .

68
ianmunoz

Si nous essayons de remplacer NAs lors de l'exportation, par exemple lors de l'écriture sur csv, nous pouvons utiliser:

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

Je sais que la question a déjà reçu une réponse, mais le faire de cette façon pourrait être plus utile pour certains:

Définir cette fonction:

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

Maintenant, chaque fois que vous avez besoin de convertir les NA dans un vecteur en zéro, vous pouvez faire:

na.zero(some.vector)
45
krishan404

Avec dplyr 0.5.0, vous pouvez utiliser la fonction coalesce qui peut être facilement intégrée dans le pipeline %>% en faisant coalesce(vec, 0). Ceci remplace toutes les NA dans vec par 0:

Supposons que nous ayons une trame de données avec 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
21
Psidom

Approche plus générale consistant à utiliser replace() dans une matrice ou un vecteur pour remplacer NA à 0

Par exemple:

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

C'est également une alternative à l'utilisation de ifelse() dans dplyr

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

Un autre exemple utilisant le paquet imputeTS:

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

Il est également possible d'utiliser tidyr::replace_na.

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

Si vous souhaitez remplacer les NA dans les variables factorielles, cela peut être 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") 

Il transforme un vecteur facteur en un vecteur numérique et ajoute un autre niveau de facteur numérique artificiel, qui est ensuite reconverti en un vecteur facteur avec un "niveau NA" supplémentaire de votre choix.

8
user6075957

Aurait commenté le post de @ ianmunoz mais je n'ai pas assez de réputation. Vous pouvez combiner mutate_each de dplyr et replace pour prendre en charge le remplacement de NA par 0. En utilisant le dataframe de la réponse de @ 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

Nous utilisons ici l'évaluation standard (SE), raison pour laquelle nous avons besoin du trait de soulignement sous "funs_.". Nous utilisons également les noms lazyeval's interp/~ et le . fait référence à "tout ce avec quoi nous travaillons", c’est-à-dire au bloc de données. Maintenant, il y a des zéros!

7
reidjax

Vous pouvez utiliser replace()

Par exemple:

> 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
4
Zahra

Une autre option compatible avec le canal dplyr avec tidyrmethod replace_na qui fonctionne pour plusieurs colonnes:

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)

Vous pouvez facilement limiter, par exemple, à colonnes numériques:

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

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

df <- d %>% replace_na(myList)
4
Antti

Cette simple fonction extraite de Datacamp pourrait aider:

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
}

Ensuite

replace_missings(df, replacement = 0)
3
Fábio

La fonction dédiée (nafill/setnafill) à cette fin est en version récente data.table

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
3
jangorecki

Un moyen facile de l'écrire est avec if_na de hablar:

library(dplyr)
library(hablar)

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

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

qui retourne:

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