У меня есть фрейм данных, и некоторые столбцы имеют значения NA
.
Как заменить эти значения NA
на нули?
Смотрите мой комментарий в ответе @gsk3. Простой пример:
> 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
Нет необходимости применять apply
. =)
ИЗМЕНИТЬ
Вы также должны взглянуть на пакет norm
. У этого есть много хороших особенностей для отсутствующего анализа данных. =)
Опция гибридного dplyr/Base R: mutate_all(funs(replace(., is.na(.), 0)))
более чем в два раза быстрее, чем базовая d[is.na(d)] <- 0
R d[is.na(d)] <- 0
, (см. анализ производительности ниже.)
Если вы data.table
с массивными data.table
, data.table
- самый быстрый вариант из всех: на 30% меньше времени, чем у dplyr, и в 3 раза быстрее, чем у Base R. Он также изменяет данные на месте, эффективно позволяя работать с почти вдвое большим количеством данных одновременно.
Locationally:
mutate_at(c(5:10), funs(replace(., is.na(.), 0)))
mutate_at(vars(var5:var10), funs(replace(., is.na(.), 0)))
mutate_at(vars(contains("1")), funs(replace(., is.na(.), 0)))
contains()
, попробуйте ends_with()
, starts_with()
mutate_at(vars(matches("\\d{2}")), funs(replace(., is.na(.), 0)))
Условно:
(измените только число (столбцы) и оставьте строку (столбцы) в покое.)
mutate_if(is.integer, funs(replace(., is.na(.), 0)))
mutate_if(is.numeric, funs(replace(., is.na(.), 0)))
mutate_if(is.character, funs(replace(., is.na(.), 0)))
# 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) }
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
)
> 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
# 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]")
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))
Когда наборы данных становятся больше, Tidyr ' replace_na
исторически вырвался вперед. Благодаря текущему набору 50M точек данных, он работает почти так же хорошо, как и Base R For Loop. Мне любопытно посмотреть, что происходит для разных размеров данных.
Дополнительные примеры для mutate
и summarize
_at
и _all
функциональные варианты можно найти здесь: https://rdrr.io/cran/dplyr/man/summarise_all.html Кроме того, я обнаружил, полезные демонстрации и сборники примеров здесь: https://blog.exploratory.io/dplyr-0-5-is-awesome-heres-why-be095fd4eb8a
С особой благодарностью:
local()
и (с помощью Frank тоже помощи пациентам), какую роль играет тихое принуждение в ускорении многих из этих подходов.coalesce()
и обновить анализ.data.table
функциях data.table
, чтобы наконец включить их в состав.(Конечно, пожалуйста, подойдите и отдайте им голоса, если вы найдете такие подходы полезными.)
Примечание по использованию чисел: если у вас есть чистый набор целочисленных данных, все ваши функции будут работать быстрее. Пожалуйста, смотрите работу alexiz_laz для получения дополнительной информации. IRL, я не могу вспомнить, чтобы встретил набор данных, содержащий более 10-15% целых чисел, поэтому я запускаю эти тесты на полностью числовых фреймах данных.
df1[j][is.na(df1[j])] = 0
неверно, должно быть df1[[j]][is.na(df1[[j]])] = 0
Для одного вектора:
x <- c(1,2,NA,4,5)
x[is.na(x)] <- 0
Для data.frame выведите функцию из вышесказанного, затем apply
в столбцы.
Просьба представить воспроизводимый пример в следующий раз, как описано здесь:
is.na
является универсальной функцией и имеет методы для объектов класса data.frame
. так что этот также будет работать с data.frame
s!
пример dplyr:
library(dplyr)
df1 <- df1 %>%
mutate(myCol1 = if_else(is.na(myCol1), 0, myCol1))
Примечание: это работает для каждого выбранного столбца, если нам нужно сделать это для всех столбцов, смотрите ответ @reidjax с использованием mutate_each.
Если мы пытаемся заменить NA
при экспорте, например, при записи в csv, тогда мы можем использовать:
write.csv(data, "data.csv", na = "0")
Я знаю, что вопрос уже ответил, но сделать это таким образом может быть более полезным для некоторых:
Определите эту функцию:
na.zero <- function (x) {
x[is.na(x)] <- 0
return(x)
}
Теперь, когда вам нужно преобразовать NA в вектор в ноль, вы можете сделать:
na.zero(some.vector)
Более общий подход к использованию replace()
в матрице или векторе для замены NA
на 0
Например:
> x <- c(1,2,NA,NA,1,1)
> x1 <- replace(x,is.na(x),0)
> x1
[1] 1 2 0 0 1 1
Это также альтернатива использованию ifelse()
в dplyr
df = data.frame(col = c(1,2,NA,NA,1,1))
df <- df %>%
mutate(col = replace(col,is.na(col),0))
levels(A$x) <- append(levels(A$x), "notAnswered") A$x <- replace(A$x,which(is.na(A$x)),"notAnswered")
замещающих значений levels(A$x) <- append(levels(A$x), "notAnswered") A$x <- replace(A$x,which(is.na(A$x)),"notAnswered")
which
здесь не нужен, вы можете использовать x1 <- replace(x,is.na(x),1)
.
С dplyr
0.5.0 вы можете использовать функцию coalesce
, которую можно легко интегрировать в конвейер %>%
, выполнив coalesce(vec, 0)
. Это заменяет все NA в vec
на 0:
Скажем, у нас есть кадр данных с 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
Другой пример с использованием пакета imputeTS:
library(imputeTS)
na.replace(yourDataframe, 0)
Если вы хотите заменить NA в факторных переменных, это может быть полезно:
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")
Он преобразует фактор-вектор в числовой вектор и добавляет еще один искусственный уровень числового фактора, который затем преобразуется обратно в фактор-вектор с одним дополнительным "уровнем NA" по вашему выбору.
Прокомментировал бы пост @ianmunoz, но у меня недостаточно репутации. Вы можете комбинировать dplyr
mutate_each
и replace
, чтобы позаботиться о замене NA
на 0
. Использование dataframe из ответа @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
Здесь мы используем стандартную оценку (SE), поэтому нам нужен символ подчеркивания "funs_
". Мы также используем lazyeval
interp
/~
, а .
ссылается на "все, с чем мы работаем", т.е. Кадр данных. Теперь есть нули!
Вы можете использовать replace()
Например:
> 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
NA
в вашем векторе. Это хорошо для небольших векторов, как в вашем примере.
x1 <- replace(x,is.na(x),1)
будет работать без явного перечисления значений индекса.
Эта простая функция, извлеченная из Datacamp, может помочь:
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
}
Тогда
replace_missings(df, replacement = 0)
Другая опция dplyr
pipe совместимая с tidyr
методом replace_na
, которая работает для нескольких столбцов:
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)
Вы можете легко ограничить, например, числовые столбцы:
d$str <- c("string", NA)
myList <- myList[sapply(d, is.numeric)]
df <- d %>% replace_na(myList)
Специальная функция (nafill
/setnafill
) для этой цели входит в пакет data.table
, уже может быть протестирована при установке из ветки
devtools::install_github("Rdatatable/data.table@nafill")
library(data.table)
ans_df = nafill(df, fill=0)
setnafill(df, fill=0) # this one updates in-place
Также можно использовать tidyr::replace_na
.
library(tidyr)
df <- df %>% mutate_all(funs(replace_na(.,0)))
Как я вижу, вопрос открыт...
Я хотел бы сказать, как я это делаю. Да, @aL3xa показал наилучшее решение для data.frame. Могу только добавить, что я сделал бы:
> A <- data.table(a=c(1,1,2,2,3,3,4,4), b=c(NA,NA,3:8))
> A
a b
1: 1 NA
2: 1 NA
3: 2 3
4: 2 4
5: 3 5
6: 3 6
7: 4 7
8: 4 8
> ## replace NA
> setDF(A) ## now we work not "as reference"
> A[is.na(A)] <- 0
> setDT(A) ## I want to continue work with data.table
Я использую функции setDF и setDT, потому что это очень быстро. Из документации: "Таблица данных ввода" модифицируется ссылкой на "data.frame" и возвращается (невидимо).... "
Второе решение в терминах data.table:
> A[,apply(.SD,2,function(x) { x[is.na(x)]<- 0; x })] -> A
> A
a b
[1,] 1 0
[2,] 1 0
[3,] 2 3
[4,] 2 4
[5,] 3 5
[6,] 3 6
[7,] 4 7
[8,] 4 8
Если вам нужны только некоторые столбцы, вы можете использовать .SDcols в data.table или просто получить несколько столбцов в data.frame. Как:
> ## for data.frame
> A <- data.table(a=1:8,b=c(1:6,NA,NA),c=c(NA,NA,3:8))
a b c
1: 1 1 NA
2: 2 2 NA
3: 3 3 3
4: 4 4 4
5: 5 5 5
6: 6 6 6
7: 7 NA 7
8: 8 NA 8
> setDF(A)
> A[,c("b","c")][ is.na(A[,c("b","c")]) ] <- 0
> setDT(A)
a b c
1: 1 1 0
2: 2 2 0
3: 3 3 3
4: 4 4 4
5: 5 5 5
6: 6 6 6
7: 7 0 7
8: 8 0 8
или
> A[,apply(.SD,2, function(x) {x[is.na(x)] <- 0; x}), .SDcols=c("b","c")]
Что взять, зависит от разных факторов.
С наилучшими пожеланиями и благодарностью за все!
Извините за мой английский (это не мой язык).