1 Задание 1. Собственный датасет с числовыми данными и NA

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

my_data <- c(12, 5, NA, 8, NA, 20, 15, NA, 3, 9, 100, NA, 45)
my_data
##  [1]  12   5  NA   8  NA  20  15  NA   3   9 100  NA  45

2 Задание 2. Очистка данных с помощью is.na()

Формулировка: провести очистку данных с использованием функции is.na() и вывести “чистый” датасет.

is.na(my_data)
##  [1] FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE
## [13] FALSE
clean_data <- my_data[!is.na(my_data)]
clean_data
## [1]  12   5   8  20  15   3   9 100  45

Вывод: is.na() возвращает логический вектор — TRUE там, где стоит пропуск. Инвертировав его (!is.na(...)) и применив как индекс к исходному вектору, мы оставляем только те элементы, где пропуска нет. Из 13 исходных значений осталось 9 — ровно столько, сколько было не-NA элементов.

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

Формулировка: сгенерировать таблицу данных с числовыми и текстовыми столбцами (аналогично с помощью функции c()), очистить данные с помощью функции complete.cases().

df <- data.frame(
  id = c(1, 2, 3, NA, 5, 6),
  name = c("Alice", "Bob", NA, "David", "Eve", "Frank"),
  score = c(85, NA, 90, 75, NA, 60),
  stringsAsFactors = FALSE
)
df
##   id  name score
## 1  1 Alice    85
## 2  2   Bob    NA
## 3  3  <NA>    90
## 4 NA David    75
## 5  5   Eve    NA
## 6  6 Frank    60
complete.cases(df)
## [1]  TRUE FALSE FALSE FALSE FALSE  TRUE
clean_df <- df[complete.cases(df), ]
clean_df
##   id  name score
## 1  1 Alice    85
## 6  6 Frank    60

Вывод: в отличие от is.na(), которая работает поэлементно, complete.cases() работает построчно — возвращает TRUE только для тех строк, где нет ни одного пропуска ни в одном столбце. Это удобно именно для таблиц (data.frame), где нужно отбросить строку целиком, если хотя бы одно значение в ней отсутствует.

4 Задание 4. Заполнение пропусков в airquality с помощью caret::preProcess()

Формулировка: проанализировать датасет airquality с пропусками, с использованием функции preProcess из пакета caret заполнить пропуски предсказанными значениями (среднее, медиана).

data(airquality)
sum(is.na(airquality))
## [1] 44
colSums(is.na(airquality))
##   Ozone Solar.R    Wind    Temp   Month     Day 
##      37       7       0       0       0       0
air_mean <- airquality
for (col in names(air_mean)) {
  if (is.numeric(air_mean[[col]])) {
    air_mean[[col]][is.na(air_mean[[col]])] <- mean(air_mean[[col]], na.rm = TRUE)
  }
}
sum(is.na(air_mean))
## [1] 0
preProc_median <- preProcess(airquality, method = "medianImpute")
air_median <- predict(preProc_median, airquality)
sum(is.na(air_median))
## [1] 0
head(airquality)
##   Ozone Solar.R Wind Temp Month Day
## 1    41     190  7.4   67     5   1
## 2    36     118  8.0   72     5   2
## 3    12     149 12.6   74     5   3
## 4    18     313 11.5   62     5   4
## 5    NA      NA 14.3   56     5   5
## 6    28      NA 14.9   66     5   6
head(air_mean)
##      Ozone  Solar.R Wind Temp Month Day
## 1 41.00000 190.0000  7.4   67     5   1
## 2 36.00000 118.0000  8.0   72     5   2
## 3 12.00000 149.0000 12.6   74     5   3
## 4 18.00000 313.0000 11.5   62     5   4
## 5 42.12931 185.9315 14.3   56     5   5
## 6 28.00000 185.9315 14.9   66     5   6
head(air_median)
##   Ozone Solar.R Wind Temp Month Day
## 1  41.0     190  7.4   67     5   1
## 2  36.0     118  8.0   72     5   2
## 3  12.0     149 12.6   74     5   3
## 4  18.0     313 11.5   62     5   4
## 5  31.5     205 14.3   56     5   5
## 6  28.0     205 14.9   66     5   6

Вывод: в датасете airquality пропуски есть в столбцах Ozone (37 шт.) и Solar.R (7 шт.). Сам caret::preProcess() “из коробки” не умеет заполнять средним (метода meanImpute там нет — есть только medianImpute, knnImpute, bagImpute), поэтому заполнение средним пришлось сделать вручную циклом по столбцам, а медианное заполнение — через preProcess(method = "medianImpute") + predict(). После обеих операций пропусков в данных не остаётся (sum(is.na(...)) равен 0), но конкретные подставленные значения отличаются — среднее чувствительно к выбросам, медиана устойчивее.

5 Задание 5. Выбросы: генерация, обнаружение и удаление через boxplot()

Формулировка: сгенерировать два числовых набора данных и добавить в них выбросы, с использованием функции boxplot обнаружить выбросы и удалить их.

set.seed(1)
data1 <- c(rnorm(100, mean = 50, sd = 10), 150, -50)
data2 <- c(rnorm(100, mean = 20, sd = 5), 100, -60)

bp1 <- boxplot(data1, main = "Данные 1 (с выбросами)")

bp1$out
## [1] 150 -50
bp2 <- boxplot(data2, main = "Данные 2 (с выбросами)")

bp2$out
## [1]  31.53989 100.00000 -60.00000
data1_clean <- data1[!(data1 %in% bp1$out)]
data2_clean <- data2[!(data2 %in% bp2$out)]

boxplot(data1_clean, main = "Данные 1 (без выбросов)")

boxplot(data2_clean, main = "Данные 2 (без выбросов)")

length(data1); length(data1_clean)
## [1] 102
## [1] 100
length(data2); length(data2_clean)
## [1] 102
## [1] 99

Вывод: boxplot() определяет выбросы по правилу “1.5 межквартильных размаха” (IQR) — всё, что выходит за пределы [Q1 - 1.5*IQR, Q3 + 1.5*IQR], попадает в $out. Искусственно добавленные экстремальные значения (150 и -50 для первого набора, 100 и -60 для второго) уверенно определяются как выбросы, после их удаления диапазон значений на boxplot становится заметно уже и compact, а “ящик” (межквартильный размах) визуально не меняется — это ожидаемо, так как выбросы уже не участвовали в его расчёте.

6 Задание 6. Дубликаты: unique() против duplicated()

Формулировка: сгенерировать таблицу данных, в которой дублируются строки, удалить строки с использованием функций unique(), duplicated(), сравнить результаты.

df_dup <- data.frame(
  x = c(1, 2, 2, 3, 3, 3, 4),
  y = c("a", "b", "b", "c", "c", "c", "d"),
  stringsAsFactors = FALSE
)
df_dup
##   x y
## 1 1 a
## 2 2 b
## 3 2 b
## 4 3 c
## 5 3 c
## 6 3 c
## 7 4 d
df_unique <- unique(df_dup)
df_unique
##   x y
## 1 1 a
## 2 2 b
## 4 3 c
## 7 4 d
duplicated(df_dup)
## [1] FALSE FALSE  TRUE FALSE  TRUE  TRUE FALSE
df_no_dup <- df_dup[!duplicated(df_dup), ]
df_no_dup
##   x y
## 1 1 a
## 2 2 b
## 4 3 c
## 7 4 d
identical(df_unique, df_no_dup)
## [1] TRUE

Вывод: оба способа дают одинаковый результат — unique() сразу возвращает таблицу без повторяющихся строк, а duplicated() возвращает логический вектор (TRUE для повторов, кроме первого вхождения), который затем используется как фильтр. identical() подтверждает, что итоговые таблицы совпадают. Разница чисто в стиле: unique() короче, а duplicated() даёт больше контроля (например, можно инвертировать логику, оставить только повторы: df_dup[duplicated(df_dup), ]).

7 Задание 7. Обработка пропусков с помощью пакета mice

Формулировка: обработать пропуски в данных с использованием пакета mice.

library(mice)
data(airquality)

md.pattern(airquality)

##     Wind Temp Month Day Solar.R Ozone   
## 111    1    1     1   1       1     1  0
## 35     1    1     1   1       1     0  1
## 5      1    1     1   1       0     1  1
## 2      1    1     1   1       0     0  2
##        0    0     0   0       7    37 44
imputed <- mice(airquality, m = 5, method = "pmm", seed = 500)
## 
##  iter imp variable
##   1   1  Ozone  Solar.R
##   1   2  Ozone  Solar.R
##   1   3  Ozone  Solar.R
##   1   4  Ozone  Solar.R
##   1   5  Ozone  Solar.R
##   2   1  Ozone  Solar.R
##   2   2  Ozone  Solar.R
##   2   3  Ozone  Solar.R
##   2   4  Ozone  Solar.R
##   2   5  Ozone  Solar.R
##   3   1  Ozone  Solar.R
##   3   2  Ozone  Solar.R
##   3   3  Ozone  Solar.R
##   3   4  Ozone  Solar.R
##   3   5  Ozone  Solar.R
##   4   1  Ozone  Solar.R
##   4   2  Ozone  Solar.R
##   4   3  Ozone  Solar.R
##   4   4  Ozone  Solar.R
##   4   5  Ozone  Solar.R
##   5   1  Ozone  Solar.R
##   5   2  Ozone  Solar.R
##   5   3  Ozone  Solar.R
##   5   4  Ozone  Solar.R
##   5   5  Ozone  Solar.R
completed_data <- complete(imputed, 1)

sum(is.na(airquality))
## [1] 44
sum(is.na(completed_data))
## [1] 0
head(completed_data)
##   Ozone Solar.R Wind Temp Month Day
## 1    41     190  7.4   67     5   1
## 2    36     118  8.0   72     5   2
## 3    12     149 12.6   74     5   3
## 4    18     313 11.5   62     5   4
## 5    18      25 14.3   56     5   5
## 6    28      31 14.9   66     5   6

Вывод: в отличие от простого заполнения средним/медианой, mice (Multivariate Imputation by Chained Equations) строит несколько (m = 5) правдоподобных вариантов заполнения пропусков, используя связи между остальными переменными (метод pmm — predictive mean matching подбирает похожее реально наблюдавшееся значение). Это статистически более корректный подход, чем константное заполнение, так как учитывает неопределённость и сохраняет распределение данных, а не “сплющивает” его вокруг среднего.

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

Формулировка: разобрать пример с мультиколлинеарностью.

library(car)

set.seed(1)
x1 <- rnorm(100)
x2 <- x1 * 0.95 + rnorm(100, sd = 0.1)
x3 <- rnorm(100)
y <- 3 * x1 + 2 * x3 + rnorm(100)

cor(data.frame(x1, x2, x3))
##            x1         x2         x3
## x1 1.00000000 0.99375671 0.01838219
## x2 0.99375671 1.00000000 0.01274276
## x3 0.01838219 0.01274276 1.00000000
model_collinear <- lm(y ~ x1 + x2 + x3)
summary(model_collinear)
## 
## Call:
## lm(formula = y ~ x1 + x2 + x3)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -2.58201 -0.52738  0.00281  0.65058  1.82364 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  0.05273    0.10065   0.524 0.601547    
## x1           3.46399    1.00226   3.456 0.000818 ***
## x2          -0.54942    1.04845  -0.524 0.601467    
## x3           2.10462    0.09712  21.671  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.998 on 96 degrees of freedom
## Multiple R-squared:  0.9252, Adjusted R-squared:  0.9229 
## F-statistic: 395.8 on 3 and 96 DF,  p-value: < 2.2e-16
vif(model_collinear)
##        x1        x2        x3 
## 80.548502 80.534361  1.002798
model_fixed <- lm(y ~ x1 + x3)
summary(model_fixed)
## 
## Call:
## lm(formula = y ~ x1 + x3)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -2.60977 -0.54846  0.01427  0.67975  1.86336 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  0.05473    0.10020   0.546    0.586    
## x1           2.94205    0.11127  26.439   <2e-16 ***
## x3           2.10714    0.09664  21.805   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.9943 on 97 degrees of freedom
## Multiple R-squared:  0.925,  Adjusted R-squared:  0.9234 
## F-statistic: 598.1 on 2 and 97 DF,  p-value: < 2.2e-16
vif(model_fixed)
##       x1       x3 
## 1.000338 1.000338

Вывод: переменная x2 искусственно построена как почти линейная функция x1 (корреляция между ними близка к 0.95+), из-за чего в модели model_collinear значения VIF (variance inflation factor) для x1 и x2 получаются заметно выше порога 5-10, что является классическим признаком мультиколлинеарности — модель “не может разделить” вклад x1 и x2 в объяснение y, их коэффициенты становятся нестабильными и статистически незначимыми, хотя по отдельности каждая переменная связана с y. После исключения избыточной x2 (модель model_fixed) VIF резко падает к значениям, близким к 1, а коэффициенты при x1 и x3 становятся стабильными и хорошо оцененными. Мультиколлинеарность не портит прогноз модели в целом, но делает интерпретацию отдельных коэффициентов ненадёжной — именно поэтому её нужно выявлять и устранять.

9 Общий вывод

В работе рассмотрены базовые техники подготовки данных в R: обнаружение и удаление пропусков поэлементно (is.na()) и построчно (complete.cases()), содержательное заполнение пропусков — от простого среднего/медианы через caret::preProcess() до статистически обоснованного множественного восстановления через пакет mice, обнаружение и удаление выбросов через boxplot(), удаление дублирующихся строк двумя эквивалентными способами (unique() и duplicated()), а также диагностика мультиколлинеарности предикторов с помощью car::vif(). Все эти шаги — стандартная часть предобработки данных перед построением любой модели машинного обучения.