Как заменить пропущенные значения (NA) в R: na.omit и na.rm

⚡ Умное резюме

В разделе «Замена пропущенных значений в R» рассматриваются обнаружение значений NA, удаление неполных строк с помощью функции na.omit() и заполнение их средним или медианой с помощью функции mutate(). В этом пошаговом руководстве используется набор данных о «Титанике», где и возраст, и стоимость проезда содержат пропущенные значения.

  • 🔎 Обнаружение прежде всего: Функция colSums(is.na(df)) подсчитывает количество пропущенных значений в каждом столбце, прежде чем принимать решение о способе их обработки.
  • 🇧🇷 Удаление строки: Функция na.omit() удаляет все строки, содержащие значения NA, сокращая данные о "Титанике" с 1,309 до 1,045.
  • 📐 Среднее значение: Функция apply() с параметром na.rm = TRUE вычисляет среднее значение столбца, а функция mutate() с параметром ifelse() записывает его в новый столбец.
  • 📊 Медианная альтернатива: Медиана устойчива к выбросам, что делает ее более безопасным выбором для переменных с асимметричным распределением, таких как стоимость проезда.
  • Массовая импутация: Функции sapply() или across() применяют одно и то же правило ко всем числовым столбцам в одном операторе.
  • ⚠️ Известный компромисс: Метод замещения средним значением уменьшает дисперсию и ослабляет корреляции, поэтому его никогда не следует применять вслепую.

Замена пропущенных значений NA в R

Что такое пропущенные значения в R?

Пропущенные значения появляются, когда в столбце наблюдения отсутствует записанное значение или когда нечисловой заполнитель находится там, где должно быть число. Их необходимо удалить или заменить перед любыми вычислениями, поскольку большинство функций R возвращают NA, как только оно появляется.

В этом руководстве показано, как обрабатывать пропущенные значения с помощью библиотеки dplyr, входящей в экосистему tidyverse для анализа данных.

Заменить пропущенные значения в R

Первым шагом всегда является выяснение того, сколько значений отсутствует и где именно.

Как обнаружить и подсчитать пропущенные значения в R

Прежде чем решать, что делать с пропущенными значениями, необходимо знать, сколько их и где они находятся. R предлагает четыре способа проверки: от одного ответа «да» или «нет» до полного подсчета по каждому столбцу.

# 1. Is there any NA at all?
anyNA(df_titanic)

# 2. How many in total?
sum(is.na(df_titanic))

# 3. How many per column, the most useful view
colSums(is.na(df_titanic))

# 4. Percentage missing per column
round(colMeans(is.na(df_titanic)) * 100, 1)

На практике лучше всего использовать функцию colSums(). Она возвращает именованный вектор с одним значением в каждом столбце, что сразу показывает, отсутствуют ли в переменной несколько значений или она в основном пуста.

Подсчет целых строк. Метод complete.cases() возвращает TRUE для строк, в которых нигде нет пропущенных значений, что заранее указывает, сколько данных будет отброшено методом na.omit():

sum(complete.cases(df_titanic))       # rows that would survive
sum(!complete.cases(df_titanic))      # rows that would be dropped

Предупреждение о наличии заполнителей. R распознает только NA. В наборах данных пропущенные значения часто кодируются как пустая строка, пробел, «N/A», «-» или контрольный номер, например, -99 или 999. Они проходят все вышеперечисленные проверки незаметно. Преобразуйте их при импорте, чтобы остальная часть рабочего процесса работала корректно:

df <- read.csv(PATH, na.strings = c("", " ", "NA", "N/A", "-", "-99"))

После импорта всегда проверяйте диапазон каждого числового столбца. Возраст -99 или стоимость проезда 9999 гораздо опаснее, чем честное значение NA, потому что ни одна функция не предупредит вас об этом.

Типы пропущенных данных: MCAR, MAR и MNAR

Причина отсутствия значения определяет, безопасно ли его восполнять. Статистики выделяют три механизма.

  • MCAR, пропал совершенно случайным образом. Вероятность пропажи не зависит ни от чего, наблюдаемого или нет, например, от датчика, который выходит из строя в случайные моменты. Падениеping Использование этих строк для заполнения пропущенных значений не вносит никакой погрешности, а лишь приводит к потере точности.
  • MAR, отсутствует случайным образом. Вероятность зависит от других наблюдаемых переменных, но не от самого пропущенного значения. Если у пожилых пассажиров вероятность указания возраста ниже, то возраст будет MAR (Master of Record — не указан) с учетом других столбцов. Метод импутации, использующий эти столбцы, хорошо справляется с этой задачей.
  • MNAR, пропажа произошла не случайно. Вероятность зависит от самого ненаблюдаемого значения, например, от того, что высокооплачиваемые работники отказываются указывать свой доход. Ни один метод восполнения не может исправить это, опираясь только на данные, а само отсутствие данных содержит информацию, которую стоит зафиксировать в столбце-флаге.

Метод замещения средним значением, используемый в этом руководстве, оправдан только при условии MCAR и простого MAR. Даже в этом случае у него есть известная цена: заполнение каждого пробела одним и тем же числом уменьшает дисперсию столбца и ослабляет его корреляцию со всеми остальными. Для серьезной работы используйте метод, основанный на моделях, например, пакет mice, и всегда ведите столбец-флаг, указывающий, какие значения были замещены.

мутировать ()

Функция mutate() — это дплир Глагол, который создает новую переменную или перезаписывает существующую, что делает его естественным инструментом для создания очищенной копии столбца.

Мы продолжим работу в двух частях. Мы научимся:

  • исключить пропущенные значения из фрейма данных
  • вменить пропущенные значения с помощью среднего и медианы

Глагол mutate() очень прост в использовании. Мы можем создать новую переменную, следуя этому синтаксису:

mutate(df, name_variable_1 = condition, ...)
arguments:
-df: Data frame used to create a new variable
-name_variable_1: Name and the formula to create the new variable
-...: No limit constraint. Possibility to create more than one variable inside mutate()

Исключить пропущенные значения (NA)

Функция na.omit() — это базовая функция R, а не команда dplyr, но она всё равно корректно обрабатывает данные через конвейер. Она удаляет все строки, содержащие хотя бы одно значение NA. Это самый быстрый вариант, но редко лучший, поскольку одно пропущенное значение отбрасывает всё наблюдение.

Чтобы решить проблему отсутствия наблюдений, мы будем использовать набор данных Titanic. В этом наборе данных у нас есть доступ к информации о пассажирах, находившихся на борту во время трагедии. В этом наборе данных много NA, о которых необходимо позаботиться.

Загрузите CSV-файл из интернета, затем выведите список столбцов, содержащих значения NA:

PATH <- "https://raw.githubusercontent.com/guru99-edu/R-Programming/master/test.csv"
df_titanic <- read.csv(PATH, sep = ",")
# Return the column names containing missing observations
list_na <- colnames(df_titanic)[ apply(df_titanic, 2, anyNA) ]
list_na

Выход:

## [1] "age"  "fare"

Здесь,

colnames(df_titanic)[apply(df_titanic, 2, anyNA)]

Возвращает названия столбцов, содержащих хотя бы одно пропущенное значение.

В столбцах «возраст» и «тариф» отсутствуют значения.

Мы можем удалить их с помощью na.omit().

library(dplyr)
# Exclude the missing observations
df_titanic_drop <-df_titanic %>%
na.omit()		
dim(df_titanic_drop)

Выход:

## [1] 1045   13

Новый набор данных содержит 1045 строк по сравнению с 1309 строками в исходном наборе данных.

Исключить пропущенные значения

Заполните недостающие данные средним значением и медианой.

Также можно заполнить пропущенные значения средним или медианой. Рекомендуется создать две отдельные переменные для среднего значения и медианы. После создания этих переменных можно заменить пропущенные значения.

Мы будем использовать метод apply для вычисления среднего значения столбца с NA. Давайте посмотрим пример

Шаг 1) Ранее в этом руководстве мы сохранили имена столбцов с отсутствующими значениями в списке list_na. Мы будем использовать этот список

Шаг 2) Вычислите среднее значение с аргументом na.rm = TRUE. Этот аргумент является обязательным, поскольку в столбцах содержатся пропущенные данные, и он указывает R игнорировать их.

# Create mean
average_missing <- apply(df_titanic[,colnames(df_titanic) %in% list_na],
      2,
      mean,
      na.rm =  TRUE)
average_missing

Code Объяснение:

Мы передаем 4 аргумента в метод apply.

  • df: df_titanic[,colnames(df_titanic) %in% list_na]. Этот код вернет имена столбцов из объекта list_na (т. е. «возраст» и «тариф»).
  • 2: Вычислить функцию по столбцам
  • среднее: вычислить среднее значение
  • na.rm = TRUE: игнорировать пропущенные значения.

Выход:

##      age     fare 
## 29.88113 33.29548

Мы успешно создали среднее значение столбцов, содержащих отсутствующие наблюдения. Эти два значения будут использоваться для замены отсутствующих наблюдений.

Шаг 3) Замените значения NA

Глагол mutate из библиотеки dplyr полезен при создании новой переменной. Нам не обязательно менять исходный столбец, поэтому мы можем создать новую переменную без NA. mutate прост в использовании, мы просто выбираем имя переменной и определяем, как создать эту переменную. Вот полный код

# Create a new variable with the mean and median
df_titanic_replace <- df_titanic %>%
   mutate(replace_mean_age  = ifelse(is.na(age), average_missing[1], age),
   replace_mean_fare = ifelse(is.na(fare), average_missing[2], fare))

Code Объяснение:

Мы создаем две переменные replace_mean_age и replace_mean_fare следующим образом:

  • replace_mean_age = ifelse(is.na(возраст), среднее_отсутствие[1], возраст)
  • replace_mean_fare = ifelse(is.na(fare), Average_missing[2],fare)

Если в столбце «Возраст» отсутствуют значения, замените их первым элементом Average_missing (средний возраст), иначе сохраните исходные значения. Та же логика для тарифа

sum(is.na(df_titanic_replace$age))

Выход:

## [1] 263

После замены в новом столбце полностью отсутствуют пропущенные значения:

sum(is.na(df_titanic_replace$replace_mean_age))

Выход:

## [1] 0

В исходном столбце age содержалось 263 пропущенных значения, тогда как в новом столбце replace_mean_age каждое из них заполнено средним возрастом.

Шаг 4) Мы также можем заменить недостающие наблюдения медианой.

median_missing <- apply(df_titanic[,colnames(df_titanic) %in% list_na],
      2,
      median,
      na.rm =  TRUE)
df_titanic_replace <- df_titanic %>%
            mutate(replace_median_age  = ifelse(is.na(age), median_missing[1], age), 
            replace_median_fare = ifelse(is.na(fare), median_missing[2], fare))
head(df_titanic_replace)

Выход:

Вменение недостающих данных с помощью среднего и медианы

Шаг 5) При работе с большим набором данных пошаговый метод становится утомительным. Функция sapply() сводит всю процедуру к одному оператору, но при этом никогда не отображает вмененные значения.

sapply не создает фрейм данных, поэтому мы можем обернуть функцию sapply() внутри data.frame() для создания объекта фрейма данных.

# Quick code to replace missing values with the mean
df_titanic_impute_mean <- data.frame(
    sapply(
        df_titanic,
        function(x) ifelse(is.na(x),
            mean(x, na.rm = TRUE),
            x)))

Современная импутация с использованием функций replace_na() и across()

Приведенные выше подходы с использованием apply() и sapply() по-прежнему работают, но библиотеки tidyr и dplyr теперь выражают те же операции более безопасно и читаемо.

Функция replace_na() заменяет фиксированные значения. Функция tidyr::replace_na() принимает именованный список столбцов и список заменяемых столбцов:

library(tidyr)

df_titanic %>%
    replace_na(list(age = 29.88, fare = 33.30))

Функция across() применяется к каждому числовому столбцу. Это прямая, типобезопасная замена однострочному вызову функции sapply(), и в отличие от sapply() она никогда не затрагивает символьные или факторные столбцы:

library(dplyr)

# Mean-impute every numeric column
df_titanic %>%
    mutate(across(where(is.numeric),
                  ~ ifelse(is.na(.x), mean(.x, na.rm = TRUE), .x)))

# Median-impute instead
df_titanic %>%
    mutate(across(where(is.numeric),
                  ~ ifelse(is.na(.x), median(.x, na.rm = TRUE), .x)))

⚠️ Почему использование сокращения sapply() рискованно: Пример использования функции sapply() в одной строке занимает слишком много времени. каждую столбец, включая символьные и факторные. Вызов функции mean() для них возвращает NA с предупреждением, а затем sapply() преобразует весь результат в символьный формат. Приведенная выше версия across(where(is.numeric), …) полностью избегает этой проблемы.

Функция coalesce() используется для резервных столбцов. Если недостающее значение может быть получено из второго столбца, функция coalesce() возвращает первую содержащуюся в столбце запись среди всех своих аргументов:

df %>% mutate(age_final = coalesce(age, age_estimated, 0))

Записывайте все внесенные изменения. Перед заполнением пропущенных данных добавьте флаг, чтобы любая последующая модель могла извлечь урок из того факта, что значение отсутствовало:

df_titanic %>%
    mutate(age_was_missing = is.na(age),
           age = ifelse(is.na(age), mean(age, na.rm = TRUE), age))

Обработка пропущенных значений в R: Справочник по методам

В этом руководстве рассматриваются три подхода:

  • Исключить все недостающие наблюдения
  • Вменить среднее значение
  • Вменить с медианой

В таблице ниже приведена сводная информация об обнаружении и удалении:

Библиотека Цель Code
Использование темпера с изогнутым основанием Перечислите недостающие наблюдения
colnames(df)[apply(df, 2, anyNA)]
Использование темпера с изогнутым основанием Удалите все строки, содержащие NA.
na.omit(df)

Вменение среднего значения или медианы может быть выполнено двумя способами.

  • Использование приложения
  • Использование саппли
Способ доставки Описание Преимущества Недостатки
Шаг за шагом с применением Проверьте столбцы с отсутствующими значениями, вычислите среднее/медиану, сохраните значение, замените с помощью mutate() Вы можете увидеть вмененное среднее или медиану. Больше времени выполнения. Может быть медленным с большим набором данных
Быстрый способ с sapply Используйте sapply() и data.frame() для автоматического поиска и замены пропущенных значений средним/медианным значением. Короткий код и быстро Вмененные значения никогда не отображаются.

Часто задаваемые вопросы (FAQ)

NA обозначает пропущенное значение в векторе. NULL означает отсутствие объекта и имеет нулевую длину. NaN — это результат неопределенной числовой операции, например, деления нуля на ноль.

Для переменных с асимметричным распределением, таких как стоимость проезда или доход, используйте медиану, поскольку выбросы завышают среднее значение. Используйте среднее значение только тогда, когда столбец приблизительно симметричен и не содержит экстремальных значений.

Заполнение каждого пробела одним и тем же значением уменьшает дисперсию столбца и ослабляет его корреляцию с другими переменными. Методы, основанные на моделях, такие как пакет mice, сохраняют эти взаимосвязи гораздо лучше.

Большинство алгоритмов не принимают значения NA и будут выдавать ошибки или молча отбрасывать строки. Неосторожное заполнение пропущенных значений приводит к утечке информации между обучающим и тестовым наборами данных, поэтому всегда вычисляйте значения для заполнения пропущенных значений только на обучающей выборке.

Да. Искусственный интеллект может предлагать методы, основанные на характере пропущенных данных, и составлять код на языке dplyr. Проверьте выбранный метод, сопоставив его с результатами функции colSums(is.na()) и своими знаниями о причинах отсутствия данных.

Подведем итог этой публикации следующим образом: