Как да замените липсващи стойности (NA) в R: na.omit & na.rm
⚡ Умно обобщение
„Заместване на липсващи стойности“ в R обхваща откриване на NA стойности, премахване на непълни редове с na.omit() и импутирането им със средната стойност или медианата чрез mutate(). Това ръководство използва набора от данни Titanic, където възрастта и тарифата носят липсващи наблюдения.
Какво представляват липсващите стойности в R?
Липсващи стойности се появяват, когато наблюдение няма записана стойност в колона или когато заместител, който не е число, се намира там, където трябва да бъде число. Те трябва да бъдат премахнати или заменени преди всяко изчисление, защото повечето R функции връщат NA в момента, в който има такава.
Този урок показва как да се обработват липсващи стойности с библиотеката dplyr, част от екосистемата tidyverse за анализ на данни.
Първата стъпка винаги е да се установи колко стойности липсват и къде.
Как да открием и преброим липсващи стойности в 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() за автоматично търсене и заместване на липсващи стойности със средна/медиана | Кратък код и бързо | Вменените стойности никога не се показват |




