Jak zastąpić wartości NA zerami w ramce danych R?

Mam data.frame a niektóre kolumny mają NA wartości. Chcę zamienić NA s na zera. Jak to robię?

 537
Author: nbro, 2011-11-17

14 answers

Zobacz mój komentarz w odpowiedzi @gsk3. Prosty przykład:

> 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

Nie ma potrzeby stosowania apply. =)

EDIT

Należy również spojrzeć na norm Pakiet. Ma wiele ciekawych funkcji do analizy brakujących danych. =)

 654
Author: aL3xa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-17 16:16:47

Opcja hybrydowa dplyr/Base R: mutate_all(funs(replace(., is.na(.), 0)))) jest ponad dwa razy szybsza od opcji base r d[is.na(d)] <- 0. (patrz analizy porównawcze poniżej.)

Jeśli zmagasz się z ogromnymi ramami danych, data.tablejest najszybszą opcją ze wszystkich: 30% mniej czasu niż dplyri 3 razy szybciej niż baza R. Modyfikuje również dane na miejscu, skutecznie umożliwiając pracę z prawie dwukrotnie większą ilością danych na raz.


Zgrupowanie inne pomocne metody wymiany tidyverse

lokalnie:

  • indeks mutate_at(c(5:10), funs(replace(., is.na(.), 0)))
  • bezpośrednie odniesienie mutate_at(vars(var5:var10), funs(replace(., is.na(.), 0)))
  • dopasowanie stałe mutate_at(vars(contains("1")), funs(replace(., is.na(.), 0)))
    • lub zamiast contains(), spróbuj ends_with(), starts_with()
  • dopasowanie wzorca mutate_at(vars(matches("\\d{2}")), funs(replace(., is.na(.), 0)))

warunkowo:
(Zmień tylko numeric (kolumny) I leave string (kolumny) sam.)

  • liczby całkowite mutate_if(is.integer, funs(replace(., is.na(.), 0)))
  • mutate_if(is.numeric, funs(replace(., is.na(.), 0)))
  • stringi mutate_if(is.character, funs(replace(., is.na(.), 0)))

Pełna Analiza -

Sprawdzone podejścia:

# 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
library(tidyverse)
dplyr_if_else      <- function(x) { mutate_all(x, funs(if_else(is.na(.), 0, .))) }
dplyr_coalesce     <- function(x) { mutate_all(x, funs(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, funs(ifelse(is.na(.), 0, .))) }
hybrd.rplc_all   <- function(x) { mutate_all(x, funs(replace(., is.na(.), 0))) }
hybrd.rplc_at.idx<- function(x) { mutate_at(x, c(1:10), funs(replace(., is.na(.), 0))) }
hybrd.rplc_at.nse<- function(x) { mutate_at(x, vars(var1:var10), funs(replace(., is.na(.), 0))) }
hybrd.rplc_at.stw<- function(x) { mutate_at(x, vars(starts_with("var")), funs(replace(., is.na(.), 0))) }
hybrd.rplc_at.ctn<- function(x) { mutate_at(x, vars(contains("var")), funs(replace(., is.na(.), 0))) }
hybrd.rplc_at.mtc<- function(x) { mutate_at(x, vars(matches("\\d+")), funs(replace(., is.na(.), 0))) }
hybrd.rplc_if    <- function(x) { mutate_if(x, is.numeric, funs(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) }

Kod do tej analizy:

library(microbenchmark)
# 20% NA filled dataframe of 5 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)), 5e6*10, replace = TRUE),
                            dimnames = list(NULL, paste0("var", 1:10)), 
                            ncol = 10))
# Running 250 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)),
    baseR.sbst.rssgn = baseR.sbst.rssgn(copy(dfN)),
    baseR.replace    = baseR.replace(copy(dfN)),
    dplyr_coalesce   = dplyr_coalesce(copy(dfN)),
    hybrd.rplc_at.nse= hybrd.rplc_at.nse(copy(dfN)),
    hybrd.rplc_at.stw= hybrd.rplc_at.stw(copy(dfN)),
    hybrd.rplc_at.ctn= hybrd.rplc_at.ctn(copy(dfN)),
    hybrd.rplc_at.mtc= hybrd.rplc_at.mtc(copy(dfN)),
    hybrd.rplc_at.idx= hybrd.rplc_at.idx(copy(dfN)),
    hybrd.rplc_if    = hybrd.rplc_if(copy(dfN)),
    tidyr_replace_na = tidyr_replace_na(copy(dfN)),
    baseR.for        = baseR.for(copy(dfN)),
    DT.for.set.nms   = DT.for.set.nms(copy(dfN)),
    DT.for.set.sqln  = DT.for.set.sqln(copy(dfN)),
    times = 250L
)

Podsumowanie wyników

> perf_results
Unit: milliseconds
              expr       min        lq      mean    median        uq      max neval
     hybrid.ifelse 5250.5259 5620.8650 5809.1808 5759.3997 5947.7942 6732.791   250
     dplyr_if_else 3209.7406 3518.0314 3653.0317 3620.2955 3746.0293 4390.888   250
  baseR.sbst.rssgn 1611.9227 1878.7401 1964.6385 1942.8873 2031.5681 2485.843   250
     baseR.replace 1559.1494 1874.7377 1946.2971 1920.8077 2002.4825 2516.525   250
    dplyr_coalesce  949.7511 1231.5150 1279.3015 1288.3425 1345.8662 1624.186   250
 hybrd.rplc_at.nse  735.9949  871.1693 1016.5910 1064.5761 1104.9590 1361.868   250
 hybrd.rplc_at.stw  704.4045  887.4796 1017.9110 1063.8001 1106.7748 1338.557   250
 hybrd.rplc_at.ctn  723.9838  878.6088 1017.9983 1063.0406 1110.0857 1296.024   250
 hybrd.rplc_at.mtc  686.2045  885.8028 1013.8293 1061.2727 1105.7117 1269.949   250
 hybrd.rplc_at.idx  696.3159  880.7800 1003.6186 1038.8271 1083.1932 1309.635   250
     hybrd.rplc_if  705.9907  889.7381 1000.0113 1036.3963 1083.3728 1338.190   250
  tidyr_replace_na  680.4478  973.1395  978.2678 1003.9797 1051.2624 1294.376   250
         baseR.for  670.7897  965.6312  983.5775 1001.5229 1052.5946 1206.023   250
    DT.for.set.nms  496.8031  569.7471  695.4339  623.1086  861.1918 1067.640   250
   DT.for.set.sqln  500.9945  567.2522  671.4158  623.1454  764.9744 1033.463   250

Boxplot wyników (w skali logu)

# adjust the margins to prepare for better boxplot printing
par(mar=c(8,5,1,1) + 0.1) 
# generate boxplot
boxplot(opN, las = 2, xlab = "", ylab = "log(time)[milliseconds]")

Boxplot porównanie upływającego czasu

Kolorowy rozrzut prób (na logu skala)

qplot(y=time/10^9, data=opN, colour=expr) + 
    labs(y = "log10 Scaled Elapsed Time per Trial (secs)", x = "Trial Number") +
    scale_y_log10(breaks=c(1, 2, 4))

Rozrzut wszystkich czasów prób

Notka o innych wysokich wykonawcach

Kiedy zbiory danych stają się większe, Tidyr " S replace_na historycznie wyciągnął się z przodu. Z bieżącym zbiorem 50m punktów danych do przejechania, działa prawie dokładnie tak samo dobrze jak baza R dla pętli. Jestem ciekaw, co się dzieje w przypadku ramek danych o różnych rozmiarach.

Dodatkowe przykłady dla mutate i summarize _at i _all warianty funkcji można znaleźć tutaj: https://rdrr.io/cran/dplyr/man/summarise_all.html Dodatkowo znalazłem pomocne Demonstracje i kolekcje przykładów tutaj: https://blog.exploratory.io/dplyr-0-5-is-awesome-heres-why-be095fd4eb8a

Uznanie i uznanie

Ze szczególnymi podziękowaniami dla:

  • Tyler Rinker i Akrun do demonstracji microbenchmark.
  • alexis_laz do pracy nad pomaga mi zrozumieć użycie local() i (z pomocą pacjenta Franka również) rolę, jaką cichy przymus odgrywa w przyspieszeniu wielu z tych podejść.
  • ArthurYip dla poke, aby dodać nowszą funkcję coalesce() i zaktualizować analizę.
  • Gregor za to, że udało mu się rozgryźć funkcje data.table na tyle dobrze, aby w końcu włączyć je do składu.
  • baza R dla pętli: alexis_laz
  • data.tabela dla pętli: Matt_Dowle

(oczywiście, proszę sięgnąć do nich i dać im upvotes, zbyt, jeśli uważasz, że te podejścia przydatne.)

uwaga na moje użycie cyfr: jeśli masz czysty zbiór danych integer, wszystkie funkcje będą działać szybciej. Zobacz praca alexiz_laz aby uzyskać więcej informacji. IRL, nie przypominam sobie napotkania zestawu danych zawierającego więcej niż 10-15% liczb całkowitych, więc uruchamiam te testy na pełnym numeryczne ramki danych.

 139
Author: leerssej,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-15 19:31:15

Dla pojedynczego wektora:

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

Dla danych.frame, wykonaj funkcję z powyższego, a następnie apply ją do kolumn.

Następnym razem proszę podać powtarzalny przykład, jak opisano tutaj:

Jak zrobić świetny przykład R?

 104
Author: Ari B. Friedman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:46

Dplyr przykład:

library(dplyr)

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

Uwaga: to działa dla wybranej kolumny, jeśli musimy to zrobić dla wszystkich kolumn, zobacz odpowiedź @reidjaxza pomocą mutate_each.

 61
Author: ianmunoz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-26 10:57:52

Jeśli próbujemy zastąpić NA s podczas eksportu, na przykład podczas zapisu do csv, możemy użyć:

  write.csv(data, "data.csv", na = "0")
 44
Author: mrsoltys,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-26 10:59:37

Wiem, że na pytanie już odpowiedziano, ale robienie tego w ten sposób może być bardziej przydatne dla niektórych:

Zdefiniuj tę funkcję:

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

Teraz, gdy musisz przekonwertować NA 's w wektorze na' s, możesz to zrobić:

na.zero(some.vector)
 40
Author: krishan404,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-24 13:49:59

Bardziej ogólne podejście do wykorzystania replace() w macierzy lub wektorze do zastąpienia NA na 0

Na przykład:

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

Jest to również alternatywa dla użycia ifelse() w dplyr

df = data.frame(col = c(1,2,NA,NA,1,1))
df <- df %>%
   mutate(col = replace(col,is.na(col),0))
 19
Author: Charleslmh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-08 09:29:47

Z dplyr 0.5.0, można użyć funkcji coalesce, którą można łatwo zintegrować z %>% pipeline wykonując coalesce(vec, 0). To zastępuje wszystkie NAs w vec z 0:

Powiedzmy, że mamy ramkę danych z NA s:

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
 17
Author: Psidom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-16 21:34:34

Kolejny przykład użycia pakietu imputeTS :

library(imputeTS)
na.replace(yourDataframe, 0)
 8
Author: stats0007,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-10 18:21:37

Jeśli chcesz zastąpić NAs zmiennymi czynnika, może to być przydatne:

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

Przekształca wektor czynnika w wektor liczbowy i dodaje kolejny sztuczny poziom czynnika liczbowego, który następnie jest przekształcany z powrotem do wektora czynnika z jednym dodatkowym" NA-levelem " do wyboru.

 8
Author: user6075957,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-06 00:10:41

Skomentowałbym post @ianmunoz, ale nie mam wystarczającej reputacji. Możesz połączyć dplyr's mutate_each i replace, aby zadbać o NA do 0 zamiennika. Korzystanie z dataframe z odpowiedzi @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

Używamy standardowej oceny (SE), dlatego potrzebujemy podkreślenia na "funs_."Używamy również lazyeval' s interp/~ a . odwołuje się do "wszystkiego, z czym pracujemy", czyli ramki danych. Teraz są zera!

 6
Author: reidjax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-26 20:32:03

Możesz użyć replace()

Na przykład:

> 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
Author: Zahra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-08 01:44:10

Kolejna opcja dplyr kompatybilna z metodą tidyr replace_na, która działa dla kilku kolumn:

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)

Można łatwo ograniczyć do np. kolumn numerycznych:

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

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

df <- d %>% replace_na(myList)
 3
Author: Antti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-10 11:25:07

Ta prosta funkcja wyodrębniona z Datacamp może pomóc:

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
}

Then

replace_missings(df, replacement = 0)
 3
Author: Fábio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-11 19:11:55