Цель работы

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

Задание 1. Создание числового набора данных с пропусками

values <- c(12, 15, NA, 18, 21, NA, 25, 30)

data_numeric <- data.frame(
  ID = 1:8,
  Value = values
)

print(data_numeric)
##   ID Value
## 1  1    12
## 2  2    15
## 3  3    NA
## 4  4    18
## 5  5    21
## 6  6    NA
## 7  7    25
## 8  8    30

В результате был создан набор данных из восьми наблюдений. Два значения в столбце Value являются пропущенными.

Задание 2. Обнаружение и удаление пропущенных значений

is.na(data_numeric)
##         ID Value
## [1,] FALSE FALSE
## [2,] FALSE FALSE
## [3,] FALSE  TRUE
## [4,] FALSE FALSE
## [5,] FALSE FALSE
## [6,] FALSE  TRUE
## [7,] FALSE FALSE
## [8,] FALSE FALSE
colSums(is.na(data_numeric))
##    ID Value 
##     0     2
data_clean <- data_numeric[
  !is.na(data_numeric$Value),
]

print(data_clean)
##   ID Value
## 1  1    12
## 2  2    15
## 4  4    18
## 5  5    21
## 7  7    25
## 8  8    30
colSums(is.na(data_clean))
##    ID Value 
##     0     0

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

Задание 3. Очистка таблицы со смешанными типами данных

data_mixed <- data.frame(
  ID = 1:6,
  Age = c(20, 25, NA, 30, 22, 28),
  City = c("Berlin", "Paris", "Rome", NA, "Madrid", "Moscow"),
  Score = c(80, 90, 75, 85, NA, 95)
)

print(data_mixed)
##   ID Age   City Score
## 1  1  20 Berlin    80
## 2  2  25  Paris    90
## 3  3  NA   Rome    75
## 4  4  30   <NA>    85
## 5  5  22 Madrid    NA
## 6  6  28 Moscow    95
complete.cases(data_mixed)
## [1]  TRUE  TRUE FALSE FALSE FALSE  TRUE
data_mixed_clean <- data_mixed[
  complete.cases(data_mixed),
]

print(data_mixed_clean)
##   ID Age   City Score
## 1  1  20 Berlin    80
## 2  2  25  Paris    90
## 6  6  28 Moscow    95

Функция complete.cases() определяет строки, в которых все значения заполнены. В результате остались наблюдения с ID 1, 2 и 6. Строки, содержащие хотя бы один пропуск, были удалены.

Задание 4. Обработка пропусков в наборе airquality

data("airquality")

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
colSums(is.na(airquality))
##   Ozone Solar.R    Wind    Temp   Month     Day 
##      37       7       0       0       0       0

С помощью colSums(is.na()) определяем количество пропущенных значений в каждом столбце.

airquality_mean <- airquality

columns_with_na <- names(airquality_mean)[
  colSums(is.na(airquality_mean)) > 0
]

for (col in columns_with_na) {
  airquality_mean[[col]][is.na(airquality_mean[[col]])] <-
    mean(airquality_mean[[col]], na.rm = TRUE)
}

colSums(is.na(airquality_mean))
##   Ozone Solar.R    Wind    Temp   Month     Day 
##       0       0       0       0       0       0
library(caret)
## Loading required package: ggplot2
## Loading required package: lattice
median_model <- preProcess(
  airquality,
  method = "medianImpute"
)

airquality_median <- predict(
  median_model,
  airquality
)

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

Функция preProcess() настраивает процедуру обработки данных, а predict() применяет её к исходному набору. Метод medianImpute заменяет пропущенные значения медианами соответствующих переменных.

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

Задание 5. Выявление и удаление выбросов

set.seed(123)

data1 <- rnorm(100, mean = 50, sd = 10)
data2 <- rnorm(100, mean = 100, sd = 15)

data1 <- c(data1, 120, -30)
data2 <- c(data2, 200, 10)

Визуальное обнаружение выбросов

boxplot(
  data1,
  main = "Набор 1 до очистки",
  ylab = "Значения"
)

boxplot(
  data2,
  main = "Набор 2 до очистки",
  ylab = "Значения"
)

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

outliers1 <- boxplot.stats(data1)$out
outliers2 <- boxplot.stats(data2)$out

print(outliers1)
## [1]  26.90831 120.00000 -30.00000
print(outliers2)
## [1] 148.6156 200.0000  10.0000
data1_clean <- data1[!data1 %in% outliers1]
data2_clean <- data2[!data2 %in% outliers2]

cat("Размер первого набора до очистки:", length(data1), "\n")
## Размер первого набора до очистки: 102
cat("Размер первого набора после очистки:", length(data1_clean), "\n")
## Размер первого набора после очистки: 99
cat("Размер второго набора до очистки:", length(data2), "\n")
## Размер второго набора до очистки: 102
cat("Размер второго набора после очистки:", length(data2_clean), "\n")
## Размер второго набора после очистки: 99

Диаграммы после очистки

boxplot(
  data1_clean,
  main = "Набор 1 после очистки",
  ylab = "Значения"
)

boxplot(
  data2_clean,
  main = "Набор 2 после очистки",
  ylab = "Значения"
)

Для обнаружения потенциальных выбросов использовалась функция boxplot.stats(). Выявленные значения были удалены, после чего наборы данных были повторно визуализированы. Следует учитывать, что статистически необычные значения не всегда являются ошибками и требуют дополнительного анализа.

Задание 6. Удаление дублирующихся записей

data_duplicates <- data.frame(
  ID = c(1, 2, 3, 2, 4, 3),
  Name = c("Anna", "Boris", "Olga", "Boris", "Ivan", "Olga"),
  Score = c(85, 90, 78, 90, 88, 78)
)

print(data_duplicates)
##   ID  Name Score
## 1  1  Anna    85
## 2  2 Boris    90
## 3  3  Olga    78
## 4  2 Boris    90
## 5  4  Ivan    88
## 6  3  Olga    78
data_unique <- unique(data_duplicates)

data_no_duplicates <- data_duplicates[
  !duplicated(data_duplicates),
]

print(data_unique)
##   ID  Name Score
## 1  1  Anna    85
## 2  2 Boris    90
## 3  3  Olga    78
## 5  4  Ivan    88
print(data_no_duplicates)
##   ID  Name Score
## 1  1  Anna    85
## 2  2 Boris    90
## 3  3  Olga    78
## 5  4  Ivan    88
cat("Исходное количество строк:", nrow(data_duplicates), "\n")
## Исходное количество строк: 6
cat("После unique():", nrow(data_unique), "\n")
## После unique(): 4
cat("После duplicated():", nrow(data_no_duplicates), "\n")
## После duplicated(): 4

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

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

.

library(mice)
## 
## Attaching package: 'mice'
## The following object is masked from 'package:stats':
## 
##     filter
## The following objects are masked from 'package:base':
## 
##     cbind, rbind
data_mice <- airquality[
  , c("Ozone", "Solar.R", "Wind", "Temp")
]

colSums(is.na(data_mice))
##   Ozone Solar.R    Wind    Temp 
##      37       7       0       0
set.seed(123)

imputed <- mice(
  data_mice,
  m = 5,
  maxit = 5,
  method = "pmm",
  printFlag = FALSE
)

data_completed <- complete(imputed, 1)

head(data_completed)
##   Ozone Solar.R Wind Temp
## 1    41     190  7.4   67
## 2    36     118  8.0   72
## 3    12     149 12.6   74
## 4    18     313 11.5   62
## 5     6     313 14.3   56
## 6    28      51 14.9   66
colSums(is.na(data_completed))
##   Ozone Solar.R    Wind    Temp 
##       0       0       0       0

Параметр m = 5 задаёт количество заполненных наборов данных, а maxit = 5 — количество итераций алгоритма. Метод pmm подбирает значения на основе наблюдений с похожими предсказанными значениями.

Для дальнейшего анализа извлечён первый из пяти заполненных наборов.

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

set.seed(123)

x1 <- rnorm(100)
x2 <- x1 * 0.9 + rnorm(100, sd = 0.2)
x3 <- rnorm(100)

y <- 3 + 2 * x1 + 0.5 * x2 - x3 + rnorm(100)

data_multi <- data.frame(x1, x2, x3, y)

cor(data_multi[, c("x1", "x2", "x3")])
##            x1         x2         x3
## x1  1.0000000  0.9728501 -0.1291760
## x2  0.9728501  1.0000000 -0.1200657
## x3 -0.1291760 -0.1200657  1.0000000

Построим линейную регрессионную модель.

model <- lm(y ~ x1 + x2 + x3, data = data_multi)

summary(model)
## 
## Call:
## lm(formula = y ~ x1 + x2 + x3, data = data_multi)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -2.49138 -0.65392  0.05664  0.67033  2.53210 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)   2.9807     0.1073  27.768  < 2e-16 ***
## x1            1.7366     0.5010   3.466 0.000791 ***
## x2            0.7311     0.5473   1.336 0.184752    
## x3           -1.0574     0.1122  -9.422  2.6e-15 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.052 on 96 degrees of freedom
## Multiple R-squared:  0.8552, Adjusted R-squared:  0.8507 
## F-statistic: 189.1 on 3 and 96 DF,  p-value: < 2.2e-16

Расчёт показателя VIF Чем выше значение VIF, тем сильнее независимая переменная связана с остальными предикторами.

predictors <- data_multi[, c("x1", "x2", "x3")]

vif_values <- sapply(names(predictors), function(variable) {
  other_variables <- predictors[
    , names(predictors) != variable,
    drop = FALSE
  ]

  auxiliary_model <- lm(
    predictors[[variable]] ~ .,
    data = other_variables
  )

  1 / (1 - summary(auxiliary_model)$r.squared)
})

print(vif_values)
##        x1        x2        x3 
## 18.723996 18.680858  1.017576

В данном примере x1 и x2 специально созданы зависимыми друг от друга. Корреляционная матрица и показатели VIF позволяют оценить степень этой зависимости. При интерпретации VIF следует учитывать контекст задачи: значения выше 5 или 10 часто рассматривают как повод для дополнительной проверки, но это не универсальные пороги.

Общий вывод

В ходе лабораторной работы были изучены основные способы подготовки исходных данных к анализу.

Были созданы числовые и смешанные наборы данных с пропущенными значениями, рассмотрены функции is.na() и complete.cases() для обнаружения и удаления пропусков. На наборе airquality выполнено заполнение пропущенных значений средними и медианами с помощью пакета caret, а также множественная импутация с использованием пакета mice.

Для обнаружения потенциальных выбросов применялись диаграммы размаха и функция boxplot.stats(). Для удаления повторяющихся записей использовались функции unique() и duplicated(). Кроме того, был создан пример линейной регрессии с мультиколлинеарностью и рассчитан показатель VIF.