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

547

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

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

  • 13
    небольшая модификация stackoverflow.com/questions/7279089/… (которую я нашел, выполнив поиск «[r] заменить NA на ноль») ...
  • 12
    d [is.na (d)] <- 0
Теги:
dataframe
na

17 ответов

741
Лучший ответ

Смотрите мой комментарий в ответе @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
    Я уже пробовал этот код вчера, прежде чем выложить его и не работал. Потому что это я разместил вопрос. Но я старался знать и работал отлично. Я думаю, что делал что-то не так.
  • 1
    Может быть, объект был не того класса ... кто знает ... О_о
Показать ещё 8 комментариев
173

Опция гибридного 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. Он также изменяет данные на месте, эффективно позволяя работать с почти вдвое большим количеством данных одновременно.


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

Locationally:

  • index 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

Boxplot of Results (в логарифмическом масштабе)

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

Изображение 4278

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

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

Изображение 4279

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

Когда наборы данных становятся больше, 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% целых чисел, поэтому я запускаю эти тесты на полностью числовых фреймах данных.

  • 1
    @Frank - Спасибо, что нашли это несоответствие. Все ссылки очищены, и результаты были полностью перезапущены на одной машине и перепечатаны.
  • 0
    Хорошо спасибо. Кроме того, я думаю, что df1[j][is.na(df1[j])] = 0 неверно, должно быть df1[[j]][is.na(df1[[j]])] = 0
Показать ещё 18 комментариев
113

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

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

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

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

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

  • 16
    is.na является универсальной функцией и имеет методы для объектов класса data.frame . так что этот также будет работать с data.frame s!
  • 0
    @ aL3xa Хорошая мысль!
Показать ещё 4 комментария
61

пример dplyr:

library(dplyr)

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

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

52

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

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

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

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

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

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

na.zero(some.vector)
19

Более общий подход к использованию 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))
  • 1
    Мой столбец был фактором, поэтому я должен был добавить свои 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")
  • 1
    which здесь не нужен, вы можете использовать x1 <- replace(x,is.na(x),1) .
Показать ещё 1 комментарий
18

С 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
  • 0
    Я проверил coalesce, и он работает примерно так же, как и замена. команда coalesce пока самая простая!
8

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

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

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

7

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

4

Вы можете использовать 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
  • 6
    Правда, но практично только тогда, когда вы знаете индекс NA в вашем векторе. Это хорошо для небольших векторов, как в вашем примере.
  • 3
    @dardisco x1 <- replace(x,is.na(x),1) будет работать без явного перечисления значений индекса.
3

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

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

Специальная функция (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
2

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

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

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

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

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню