Як я замінюю значення NA нулями в R фрейму даних?


727

У мене є кадр даних, а деякі стовпці мають NAзначення.

Як замінити ці NAзначення нулями?


13
невелика модифікація stackoverflow.com/questions/7279089/… (яку я знайшов, шукаючи "[r] замінити NA на нуль") ...
Бен Болкер

25
d [is.na (d)] <- 0
психономіка

Відповіді:


879

Дивіться мій коментар у відповіді @ 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. =)

EDIT

Ви також повинні подивитися на normпакет. Він має багато приємних функцій для аналізу відсутніх даних. =)


2
Я вже спробував цей код вчора, перш ніж ви його опублікували і не працював. Тому що це я розмістив питання. Але я намагався знати і працював на відмінно. Я думаю, що я робив щось не так.
Ренато Діньяні

12
@ RenatoDinhaniConceição: якщо ви вже щось спробували, корисно поділитися цією інформацією, коли ви ставите запитання; це допомагає звузити місце, де може бути проблема.
Аарон виїхав з "Переповнення стека"

2
d [is.na (d)] <- 0 не має для мене сенсу. Здається, назад? Як R обробляє це твердження?
користувач798719

13
@ user798719 - "<-" - це оператор присвоєння R, і його можна читати як: зробіть щось праворуч, а потім призначте його розташуванню / імені зліва. У цьому випадку ми насправді нічого не робимо - просто робимо нулі. Ліва сторона говорить: подивіться на d-об’єкт, усередині d-об'єкта (квадратні дужки), знайдіть усі елементи, які повертають TRUE (is.na (d) повертає логіку для кожного елемента). Як тільки вони будуть знайдені, замініть їх ("призначте їх") значенням 0. Це залишає всі не-NA, як вони були, і замінює лише ті, які відсутні.
Twitch_City

3
І ... якщо у вас є кадр даних і ви хочете застосувати заміну лише до конкретних ветеринарних векторів (залишаючи сказати ... рядки з NA):df[19:28][is.na(df[19:28])] <- 0
jtdoud

299

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

Якщо ви боретеся з масивними кадрами даних, data.tableце найшвидший варіант з усіх: на 40% швидше, ніж стандартний підхід Base R. Він також змінює дані на місці, ефективно дозволяючи працювати з майже вдвічі більшою кількістю даних одночасно.


Кластеризація інших корисних підходів до заміни

Локально:

  • покажчик 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()аргументів.

Тестування підходів:

# 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.nafill        <- function(x) { nafill(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

Сфера результатів

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 Порівняння минулого часу

Кольоровий Scatterplot випробувань (з віссю 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 усіх пробних часів

Нота про інших високих виконавців

Коли набори даних стають більше, Tidyr «» s replace_naісторично витягнув попереду. Із поточним набором 100M точок даних, які потрібно пропустити, він працює майже точно так само, як і 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-hereres-why-be095fd4eb8a

Атрибуції та вдячності

З особливою подякою:

  • Тайлер Рінкер та Акрун для демонстрації мікробірок.
  • alexis_laz для роботи над тим, щоб допомогти мені зрозуміти використання local()та (за допомогою пацієнтової допомоги Франка) також ролі, яку мовчазний примус відіграє у прискоренні багатьох із цих підходів.
  • ArthurYip для тюка, щоб додати новішу coalesce()функцію і оновити аналіз.
  • Ґрегор за нудьгу, щоб розібратися з data.tableфункціями досить добре, щоб нарешті включити їх у лінійку.
  • База R Для циклу: alexis_laz
  • data.table для циклів: Matt_Dowle
  • Роман для пояснення, що is.numeric()насправді тестує.

(Звичайно, будь-ласка, зверніться до них і дайте їм підсумки, якщо вам ці підходи корисні.)

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

Обладнання Використовується процесор 3,9 ГГц з 24 ГБ оперативної пам’яті


2
@Frank - Дякую, що знайшли цю невідповідність. Всі посилання очищені, і результати були повністю перероблені на одній машині та повернені.
leerssej

Добре, дякую. Крім того, я думаю, що df1[j][is.na(df1[j])] = 0це неправильно, має бутиdf1[[j]][is.na(df1[[j]])] = 0
Френк

О, тепер я бачу, що ви писали це двічі, по-різному в кожному еталоні. Так чи інакше, forLp_Sbstце не схоже на те, щоб хтось міг розглянути питання про наближення до ньогоforLp_smplfSbst
Френк

1
@UweBlock - чудове запитання: це дозволило мені виконати операцію присвоєння лівого підмножини з усіма функціями, що працюють на точно однаковому кадрі даних. Оскільки мені довелося обернути місцеві навколо цієї функції, то в ім'я науки [Одне завдання, у вас була одна робота!] Я обернув її навколо всіх, щоб ігрове поле було однозначно рівним. Для отримання додаткової інформації - будь ласка, дивіться тут: stackoverflow.com/questions/41604711/… Я підкреслив досить давно попередню відповідь - але цю частину дискусії було б добре додати ще раз. Дякую!
leerssej

1
@ArthurYip - я додавав coalesce()опцію в і повторювався весь час. Дякуємо вам за оновлення для оновлення.
leerssej

128

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

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

Для data.frame зробіть функцію з вищезазначеного, а потім applyу стовпці.

Будь ласка, надайте наступний приклад, який можна відтворити, як це детально описано тут:

Як зробити чудовий приклад відтворення R?


18
is.naє загальною функцією та має методи для об'єктів data.frameкласу. так що цей також буде працювати на data.frames!
aL3xa

3
Коли я methods(is.na)вперше бігав , я був схожий на whaaa?!? . Я люблю, коли подібні речі трапляються! =)
aL3xa

9
Припустимо, у вас є фрейм даних з ім'ям df замість одного вектора, і ви просто хочете замінити пропущені спостереження в одному стовпчику під назвою X3. Ви можете зробити це за допомогою цього рядка: df $ X3 [is.na (df $ X3)] <- 0
Марк Міллер

8
Припустимо, ви хочете замінити NA на 0 у стовпцях 4-6 кадру даних з назвою my.df. Ви можете використовувати: my.df [, 4: 6] [is.na (my.df [, 4: 6])] <- 0
Марк Міллер

як ви переходите "x" до is.na (x) чи є спосіб сказати, які підпрограми бібліотеки в R векторизовані?
uh_big_mike_boi

73

Приклад dplyr:

library(dplyr)

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

Примітка: Це працює в обраному стовпці, якщо ми повинні зробити це для всіх стовпців см @reidjax відповідь «s з допомогою mutate_each .


57

Якщо ми намагаємося замінити NAs при експорті, наприклад, під час запису в csv, ми можемо використовувати:

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

47

Я знаю, що на питання вже відповіли, але зробити це таким чином може бути кориснішим для деяких:

Визначте цю функцію:

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

Тепер, коли вам потрібно конвертувати NA у векторному, до нуля ви можете:

na.zero(some.vector)

22

З dplyr0,5.0 ви можете використовувати coalesceфункцію, яку можна легко інтегрувати в %>%трубопровід coalesce(vec, 0). Це замінює всі НС на vec0:

Скажімо, у нас є кадр даних з NAs:

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

Я перевірив coalesce, і він виконує приблизно те саме, що замінити. команда coalesce - найпростіша поки що!
Артур Іп

було б корисно, якби ви представили, як застосувати це до всіх стовпців тиблиці 2+.
jangorecki

21

Більш загальний підхід використання 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")
Climbs_lika_Spyder

1
whichтут не потрібно, ви можете використовувати x1 <- replace(x,is.na(x),1).
lmo

Я перепробував багато способів , запропонованих в цій темі , щоб замінити NAна 0всього один конкретному стовпчику у великій кадрі даних і ця функція replace()працювала найбільш ефективно , а також найбільш просто.
Duc

19

Також можливо використовувати tidyr::replace_na.

    library(tidyr)
    df <- df %>% mutate_all(funs(replace_na(.,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-рівнем" на ваш вибір.


8

Я б прокоментував пост @ ianmunoz, але у мене недостатньо репутації. Ви можете комбінувати dplyr«S mutate_eachі replaceщоб піклуватися про NAдля 0заміни. Використання фрейму даних з відповіді @ 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's 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
Правда, але лише практично, коли ви знаєте індекс NAs у своєму векторі. Це добре для малих векторів, як у вашому прикладі.
dardisco

4
@dardisco x1 <- replace(x,is.na(x),1)буде працювати без явного перерахування значень індексу.
lmo

4

Ще один dplyrваріант, сумісний з трубою, із 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)

4

Виділена функція ( 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

Для тих, хто виступає проти, будь ласка, надайте відгуки, щоб мою відповідь можна було покращити.
jangorecki

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

Простий спосіб написати це за 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


1

якщо ви хочете призначити нове ім'я після зміни NA в конкретному стовпчику в цьому стовпці V3, використовуйте, ви також можете зробити це так

my.data.frame$the.new.column.name <- ifelse(is.na(my.data.frame$V3),0,1)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.