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ę?
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. =)
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.table
jest 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óbujends_with()
,starts_with()
- lub zamiast
-
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]")
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))
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.
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:
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.
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")
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)
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))
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
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)
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.
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!
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
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)
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)
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