Как да замените липсващи стойности (NA) в R: na.omit & na.rm

⚡ Умно обобщение

„Заместване на липсващи стойности“ в R обхваща откриване на NA стойности, премахване на непълни редове с na.omit() и импутирането им със средната стойност или медианата чрез mutate(). Това ръководство използва набора от данни Titanic, където възрастта и тарифата носят липсващи наблюдения.

  • ???? Първо откриване: colSums(is.na(df)) преброява липсващите стойности за всяка колона, преди да се вземе решение как да се обработят.
  • 🗑️ Премахване на редове: na.omit() премахва всеки ред, съдържащ NA, намалявайки данните от Titanic от 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, като се имат предвид другите колони. Импутацията, която използва тези колони, се справя добре с това.
  • MNAR, липсва не на случаен принцип. Вероятността зависи от самата ненаблюдавана стойност, например лицата с високи доходи отказват да декларират доходите си. Никой метод за импутиране не може да поправи това само от данните, а самата липса носи информация, която си струва да се запише в колона с флагове.

Средната импутация, както е използвана в този урок, е защитима само при MCAR и опростен MAR. Дори тогава тя има известна цена: запълването на всяка празнина с едно и също число свива дисперсията на колоната и отслабва корелацията ѝ с всичко останало. За сериозна работа използвайте метод, базиран на модели, като например пакета mices, и винаги запазвайте колона с флаг, маркираща кои стойности са били импутирани.

mutate()

mutate() е dplyr глагол, който създава нова променлива или презаписва съществуваща, което го прави естествен инструмент за изграждане на почистено копие на колона.

Ще продължим в две части. Ще научим как да:

  • изключване на липсващи стойности от рамка с данни
  • приписване на липсващи стойности със средната стойност и медианата

Глаголът 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.omit() е базова R функция, а не dplyr глагол, но въпреки това се пренасочва чисто. Премахва всеки ред, съдържащ поне една NA. Това е най-бързият вариант и рядко най-добрият, защото една липсваща стойност отхвърля цялото наблюдение.

За да се справим с проблема с липсващите наблюдения, ще използваме титаничния набор от данни. В този набор от данни имаме достъп до информацията за пътниците на борда по време на трагедията. Този набор от данни има много 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 с оригиналния набор от данни.

Изключване на липсващи стойности

Вмъкнете липсващите данни със средната стойност и медианата

Можете също така да импутирате, т.е. да попълните липсващите стойности със средната стойност или медианата. Добра практика е да се създадат две отделни променливи за средната стойност и медианата. След като бъдат създадени, можем да заменим липсващите стойности с новообразуваните променливи.

Ще използваме метода на прилагане, за да изчислим средната стойност на колоната с 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(тарифа), средна_липсваща[2],тарифа)

Ако възрастта на колоната има липсващи стойности, заменете с първия елемент от average_missing (средна възраст), в противен случай запазете оригиналните стойности. Същата логика за цената

sum(is.na(df_titanic_replace$age))

Изход:

## [1] 263

След замяната, новата колона изобщо не съдържа липсващи стойности:

sum(is.na(df_titanic_replace$replace_mean_age))

Изход:

## [1] 0

Оригиналната колона за възраст съдържа 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 не създава a рамка с данни, така че можем да обвием функцията 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)

Импутацията със средна стойност или медиана може да се извърши по два начина

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

Въпроси и Отговори

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

Използвайте медианата за асиметрични променливи, като например цена на билета или доход, защото отклоненията издърпват средната стойност нагоре. Използвайте средната стойност само когато колоната е приблизително симетрична и без екстремни стойности.

Запълването на всяка празнина с една и съща стойност свива дисперсията на колоната и отслабва нейната корелация с други променливи. Методи, базирани на модели, като например пакета mices, запазват тези връзки много по-добре.

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

Да. Асистентите с изкуствен интелект могат да предложат методи въз основа на модела на липсващи данни и да изготвят dplyr код. Проверете избора спрямо вашия собствен изход от colSums(is.na()) и познанията ви за това защо данните липсват.

Обобщете тази публикация с: