Как заменить значения NA нулями в R-кадре данных?

У меня есть фрейм данных, и некоторые столбцы имеют значения NA.

Как заменить эти значения NA на нули?

Ответ 1

Смотрите мой комментарий в ответе @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. У этого есть много хороших особенностей для отсутствующего анализа данных. =)

Ответ 2

Гибридизированные опции dplyr теперь примерно на 30% быстрее, чем переназначение подгруппы Base R. На 100- mutate_all(~replace(., is.na(.), 0)) информационном mutate_all(~replace(., is.na(.), 0)) работает на полсекунды быстрее, чем базовая d[is.na(d)] <- 0 R d[is.na(d)] <- 0. То, чего вы хотите избежать, - это использование ifelse() или if_else(). (Полный 600-кратный пробный анализ занял более 4,5 часов, в основном из-за включения этих подходов.) См. Ниже результаты сравнительного анализа для получения полных результатов.

Если вы data.table с массивными data.table, data.table - самый быстрый вариант из всех: на 40% быстрее, чем стандартный подход Base R. Он также изменяет данные на месте, эффективно позволяя работать с почти вдвое большим количеством данных одновременно.


Кластеризация других полезных подходов замены Tidyverse

Locationally:

  • index mutate_at(c(5:10), ~replace(., is.na(.), 0))
  • прямая ссылка mutate_at(vars(var5:var10), ~replace(., is.na(.), 0))
  • исправлено совпадение mutate_at(vars(contains("1")), ~replace(., is.na(.), 0))
    • или вместо contains(), попробуйте ends_with(), starts_with()
  • сопоставление с образцом mutate_at(vars(matches("\\d{2}")), ~replace(., is.na(.), 0))

Условно:
(измените только число (столбцы) и оставьте строку (столбцы) в покое.)

  • целые числа mutate_if(is.integer, ~replace(., is.na(.), 0))
  • удваивает mutate_if(is.numeric, ~replace(., is.na(.), 0))
  • строки mutate_if(is.character, ~replace(., is.na(.), 0))

Полный анализ -

Обновлено для dplyr 0.8.0: функции используют символы purrr в формате ~ символы: замена устаревших аргументов funs funs().

Подходы проверены:

# 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)}

Код для этого анализа:

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
)

Сводка результатов

> 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

Boxplot of Results

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

Цветовая диаграмма рассеяния испытаний (с осью Y в логарифмическом масштабе)

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

Примечание о других высоких исполнителей

Когда наборы данных становятся больше, 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

Атрибуты и благодарности

С особой благодарностью:

  • Тайлер Ринкер и Акрун за демонстрацию микробенчмарка.
  • alexis_laz за то, что он помог мне понять использование local() и (с помощью Frank тоже помощи пациентам) роль тихого принуждения в ускорении многих из этих подходов.
  • ArthurYip для poke, чтобы добавить более новую функцию coalesce() и обновить анализ.
  • Грегор, чтобы подтолкнуть достаточно хорошо data.table функциях data.table, чтобы наконец включить их в состав.
  • База R для цикла: alexis_laz
  • data.table для циклов: Matt_Dowle

(Конечно, пожалуйста, подойдите и отдайте им голоса, если вы найдете такие подходы полезными.)

Замечание по использованию чисел: если у вас есть чистый набор целочисленных данных, все ваши функции будут работать быстрее. Пожалуйста, смотрите alexiz_laz работу для получения дополнительной информации. IRL, я не могу вспомнить, чтобы встретил набор данных, содержащий более 10-15% целых чисел, поэтому я запускаю эти тесты на полностью числовых фреймах данных.

Используемое оборудование Процессор 3,9 ГГц с 24 ГБ ОЗУ

Ответ 3

Для одного вектора:

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

Для data.frame выведите функцию из вышесказанного, затем apply в столбцы.

Просьба представить воспроизводимый пример в следующий раз, как описано здесь:

Как сделать отличный воспроизводимый пример R?

Ответ 4

пример dplyr:

library(dplyr)

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

Примечание: это работает для каждого выбранного столбца, если нам нужно сделать это для всех столбцов, смотрите ответ @reidjax с использованием mutate_each.

Ответ 5

Если мы пытаемся заменить NA при экспорте, например, при записи в csv, тогда мы можем использовать:

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

Ответ 6

Я знаю, что вопрос уже ответил, но сделать это таким образом может быть более полезным для некоторых:

Определите эту функцию:

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

Теперь, когда вам нужно преобразовать NA в вектор в ноль, вы можете сделать:

na.zero(some.vector)

Ответ 7

С 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

Ответ 8

Более общий подход к использованию 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))

Ответ 9

Если вы хотите заменить 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" по вашему выбору.

Ответ 10

Другой пример с использованием пакета imputeTS:

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

Ответ 11

Прокомментировал бы пост @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/~, а . ссылается на "все, с чем мы работаем", т.е. Кадр данных. Теперь есть нули!

Ответ 12

Также можно использовать tidyr::replace_na.

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

Ответ 13

Вы можете использовать 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

Ответ 14

Другая опция 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)

Ответ 15

Эта простая функция, извлеченная из 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)

Ответ 16

Специальная функция (nafill/setnafill) для этой цели есть в недавней версии 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

Ответ 17

Как я вижу, вопрос открыт...

Я хотел бы сказать, как я это делаю. Да, @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")]

Что взять, зависит от разных факторов.

С наилучшими пожеланиями и благодарностью за все!

Извините за мой английский (это не мой язык).

Ответ 18

Простой способ написать это с помощью if_na из hablar:

library(dplyr)
library(hablar)

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

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

который возвращает:

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