1. Создание собственного набора данных с NA (c)

Создадим числовой вектор с пропущенными значениями.

x <- c(10, 15, NA, 20, NA, 25, 30, NA, 40)
x
## [1] 10 15 NA 20 NA 25 30 NA 40

2. Очистка данных с использованием is.na()

Найдём пропуски и удалим их, получив «чистый» набор данных.

is.na(x)
## [1] FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE  TRUE FALSE
x_clean <- x[!is.na(x)]
x_clean
## [1] 10 15 20 25 30 40

Короткий вывод: is.na() помогает определить NA, после чего их можно исключить.


3. Таблица с числовыми и текстовыми столбцами + complete.cases()

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

df <- data.frame(
  id   = c(1, 2, 3, 4, 5, 6),
  age  = c(19, NA, 21, 20, NA, 22),
  name = c("Anna", "Boris", NA, "Daria", "Elena", NA),
  city = c("Moscow", "Kazan", "Moscow", NA, "Omsk", "Perm")
)
df
##   id age  name   city
## 1  1  19  Anna Moscow
## 2  2  NA Boris  Kazan
## 3  3  21  <NA> Moscow
## 4  4  20 Daria   <NA>
## 5  5  NA Elena   Omsk
## 6  6  22  <NA>   Perm

Очистим данные, оставив только строки без пропусков.

complete.cases(df)
## [1]  TRUE FALSE FALSE FALSE FALSE FALSE
df_clean <- df[complete.cases(df), ]
df_clean
##   id age name   city
## 1  1  19 Anna Moscow

Короткий вывод: complete.cases() оставляет строки, где нет NA ни в одном столбце.


4. airquality: анализ пропусков и заполнение средним/медианой

Встроенный датасет airquality содержит пропуски. Посмотрим, где они есть.

library(caret)
data(airquality)

colSums(is.na(airquality))
##   Ozone Solar.R    Wind    Temp   Month     Day 
##      37       7       0       0       0       0
summary(airquality)
##      Ozone           Solar.R           Wind             Temp      
##  Min.   :  1.00   Min.   :  7.0   Min.   : 1.700   Min.   :56.00  
##  1st Qu.: 18.00   1st Qu.:115.8   1st Qu.: 7.400   1st Qu.:72.00  
##  Median : 31.50   Median :205.0   Median : 9.700   Median :79.00  
##  Mean   : 42.13   Mean   :185.9   Mean   : 9.958   Mean   :77.88  
##  3rd Qu.: 63.25   3rd Qu.:258.8   3rd Qu.:11.500   3rd Qu.:85.00  
##  Max.   :168.00   Max.   :334.0   Max.   :20.700   Max.   :97.00  
##  NA's   :37       NA's   :7                                       
##      Month            Day      
##  Min.   :5.000   Min.   : 1.0  
##  1st Qu.:6.000   1st Qu.: 8.0  
##  Median :7.000   Median :16.0  
##  Mean   :6.993   Mean   :15.8  
##  3rd Qu.:8.000   3rd Qu.:23.0  
##  Max.   :9.000   Max.   :31.0  
## 

4.1 Заполнение пропусков средним

air_mean <- airquality
num_cols <- sapply(air_mean, is.numeric)

for (col in names(air_mean)[num_cols]) {
  air_mean[[col]][is.na(air_mean[[col]])] <- mean(air_mean[[col]], na.rm = TRUE)
}

colSums(is.na(air_mean))
##   Ozone Solar.R    Wind    Temp   Month     Day 
##       0       0       0       0       0       0

4.2 Заполнение пропусков медианой

air_median <- airquality

for (col in names(air_median)[num_cols]) {
  air_median[[col]][is.na(air_median[[col]])] <- median(air_median[[col]], na.rm = TRUE)
}

colSums(is.na(air_median))
##   Ozone Solar.R    Wind    Temp   Month     Day 
##       0       0       0       0       0       0

Короткий вывод: медиана обычно более устойчива к выбросам, чем среднее.


5. Выбросы: обнаружение boxplot и удаление

Сгенерируем два числовых набора данных и добавим в них выбросы.

set.seed(202)
a <- c(rnorm(40, mean = 10, sd = 2), 30, 35)     # выбросы
b <- c(rnorm(40, mean = 100, sd = 10), 180, 220) # выбросы

Построим boxplot.

boxplot(a, main = "Набор A (с выбросами)")

boxplot(b, main = "Набор B (с выбросами)")

Пояснение графика: точки за пределами «усов» boxplot считаются выбросами.

Найдём выбросы и удалим их.

out_a <- boxplot.stats(a)$out
out_b <- boxplot.stats(b)$out

out_a
## [1] 30 35
out_b
## [1]  76.4159 180.0000 220.0000
a_clean <- a[!(a %in% out_a)]
b_clean <- b[!(b %in% out_b)]

Построим boxplot после удаления.

boxplot(a_clean, main = "Набор A (без выбросов)")

boxplot(b_clean, main = "Набор B (без выбросов)")

Короткий вывод: после удаления выбросов разброс уменьшается, данные становятся более «ровными».


6. Дубликаты: unique() и duplicated()

Создадим таблицу с повторяющимися строками.

dup_tbl <- data.frame(
  product = c("A", "B", "B", "C", "D", "D", "E"),
  price   = c(10, 20, 20, 30, 40, 40, 50)
)
dup_tbl
##   product price
## 1       A    10
## 2       B    20
## 3       B    20
## 4       C    30
## 5       D    40
## 6       D    40
## 7       E    50

Удаление через unique():

unique_tbl <- unique(dup_tbl)
unique_tbl
##   product price
## 1       A    10
## 2       B    20
## 4       C    30
## 5       D    40
## 7       E    50

Удаление через duplicated():

dup_removed_tbl <- dup_tbl[!duplicated(dup_tbl), ]
dup_removed_tbl
##   product price
## 1       A    10
## 2       B    20
## 4       C    30
## 5       D    40
## 7       E    50

Сравним результаты:

identical(unique_tbl, dup_removed_tbl)
## [1] TRUE

Короткий вывод: оба метода дают одинаковый итог — уникальные строки.


7. Пропуски: обработка с помощью mice

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

library(mice)

data(nhanes, package = "mice")
nhanes
##    age  bmi hyp chl
## 1    1   NA  NA  NA
## 2    2 22.7   1 187
## 3    1   NA   1 187
## 4    3   NA  NA  NA
## 5    1 20.4   1 113
## 6    3   NA  NA 184
## 7    1 22.5   1 118
## 8    1 30.1   1 187
## 9    2 22.0   1 238
## 10   2   NA  NA  NA
## 11   1   NA  NA  NA
## 12   2   NA  NA  NA
## 13   3 21.7   1 206
## 14   2 28.7   2 204
## 15   1 29.6   1  NA
## 16   1   NA  NA  NA
## 17   3 27.2   2 284
## 18   2 26.3   2 199
## 19   1 35.3   1 218
## 20   3 25.5   2  NA
## 21   1   NA  NA  NA
## 22   1 33.2   1 229
## 23   1 27.5   1 131
## 24   3 24.9   1  NA
## 25   2 27.4   1 186
colSums(is.na(nhanes))
## age bmi hyp chl 
##   0   9   8  10

Заполним пропуски методом pmm (predictive mean matching).

imp <- mice(nhanes, m = 5, method = "pmm", seed = 55, printFlag = FALSE)
nhanes_filled <- complete(imp, 1)

nhanes_filled
##    age  bmi hyp chl
## 1    1 29.6   1 187
## 2    2 22.7   1 187
## 3    1 27.5   1 187
## 4    3 21.7   1 204
## 5    1 20.4   1 113
## 6    3 20.4   2 184
## 7    1 22.5   1 118
## 8    1 30.1   1 187
## 9    2 22.0   1 238
## 10   2 22.5   1 187
## 11   1 27.2   1 187
## 12   2 27.5   2 238
## 13   3 21.7   1 206
## 14   2 28.7   2 204
## 15   1 29.6   1 187
## 16   1 20.4   1 131
## 17   3 27.2   2 284
## 18   2 26.3   2 199
## 19   1 35.3   1 218
## 20   3 25.5   2 229
## 21   1 29.6   1 238
## 22   1 33.2   1 229
## 23   1 27.5   1 131
## 24   3 24.9   1 204
## 25   2 27.4   1 186
colSums(is.na(nhanes_filled))
## age bmi hyp chl 
##   0   0   0   0

Короткий вывод: mice восстанавливает пропуски, используя связи между переменными, формируя несколько вариантов заполнения.


8. Пример мультиколлинеарности

Сгенерируем признаки, где два столбца сильно коррелируют, и проверим VIF.

set.seed(500)
n <- 120

x1 <- rnorm(n)
x2 <- 0.95*x1 + rnorm(n, sd = 0.05)  # почти линейная зависимость от x1
x3 <- rnorm(n)
y  <- 2*x1 + rnorm(n)

mc <- data.frame(y, x1, x2, x3)
cor(mc[, c("x1","x2","x3")])
##            x1         x2         x3
## x1 1.00000000 0.99857385 0.07550668
## x2 0.99857385 1.00000000 0.08271294
## x3 0.07550668 0.08271294 1.00000000

Построим модель:

model <- lm(y ~ x1 + x2 + x3, data = mc)
summary(model)
## 
## Call:
## lm(formula = y ~ x1 + x2 + x3, data = mc)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -2.58923 -0.63963 -0.05886  0.76860  2.67739 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.04788    0.09598  -0.499    0.619
## x1           1.96808    1.76412   1.116    0.267
## x2           0.07617    1.85299   0.041    0.967
## x3          -0.03520    0.10217  -0.345    0.731
## 
## Residual standard error: 1.041 on 116 degrees of freedom
## Multiple R-squared:  0.8043, Adjusted R-squared:  0.7993 
## F-statistic:   159 on 3 and 116 DF,  p-value: < 2.2e-16

Проверим VIF:

if (!requireNamespace("car", quietly = TRUE)) install.packages("car")
library(car)
vif(model)
##         x1         x2         x3 
## 357.184113 357.594170   1.025083

Пояснение:
Если VIF высокий (обычно > 5 или > 10), это признак мультиколлинеарности.
В данном примере x1 и x2 почти одинаковые по смыслу, поэтому VIF у них будет большим.


Заключение

В работе были выполнены все пункты задания:
создание данных с пропусками, очистка is.na() и complete.cases(), заполнение пропусков (среднее/медиана), обработка выбросов по boxplot, удаление дубликатов, импутация mice и пример мультиколлинеарности с VIF.