Введение

Данная лабораторная работа посвящена способам подготовки исходных данных в языке R.

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

В работе рассматриваются следующие темы:

Перед выполнением отчета необходимые пакеты нужно один раз установить в консоли RStudio:

install.packages("caret")
install.packages("mice")
install.packages("car")

Задание 1. Создание собственного набора данных

Условие

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

Выполнение задания

# Создаем числовой вектор.
# Вектор содержит обычные числа и два пропущенных значения NA.

my_data <- c(10, 20, NA, 30, 40, NA, 50)

# Выводим созданный вектор.

my_data
## [1] 10 20 NA 30 40 NA 50

Вывод по заданию 1

В первом задании был создан простой числовой набор данных с помощью функции c().

Вектор содержит как обычные числовые значения, так и пропущенные значения NA. Такие пропуски часто встречаются в реальных данных, например если часть информации не была собрана или была потеряна.

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

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

Условие

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

Выполнение задания

# Функция is.na() проверяет каждый элемент вектора.
# Если элемент является NA, возвращается TRUE.
# Если элемент не является NA, возвращается FALSE.

na_positions <- is.na(my_data)

na_positions
## [1] FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE
# Чтобы оставить только нормальные значения, используем отрицание !.
# !na_positions означает "все элементы, которые НЕ являются NA".

clean_data <- my_data[!na_positions]

clean_data
## [1] 10 20 30 40 50

Вывод по заданию 2

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

Функция is.na() позволила определить позиции, где находятся NA. После этого с помощью логической индексации были выбраны только те элементы, которые не являются пропущенными.

В результате был получен очищенный вектор:

10, 20, 30, 40, 50

Таким образом, функция is.na() удобна для поиска и удаления пропусков в простых наборах данных.

Задание 3. Очистка таблицы с помощью complete.cases()

Условие

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

Выполнение задания

# Создаем таблицу data.frame.
# В таблице есть числовой столбец numbers и текстовый столбец text.
# В обоих столбцах специально добавлены пропущенные значения NA.

my_table <- data.frame(
  numbers = c(1, 2, 3, NA, 5),
  text = c("A", NA, "C", "D", "E")
)

my_table
##   numbers text
## 1       1    A
## 2       2 <NA>
## 3       3    C
## 4      NA    D
## 5       5    E
# complete.cases() проверяет строки таблицы.
# TRUE означает, что в строке нет пропусков.
# FALSE означает, что в строке есть хотя бы один NA.

complete_rows <- complete.cases(my_table)

complete_rows
## [1]  TRUE FALSE  TRUE FALSE  TRUE
# Оставляем только строки без пропусков.
# До запятой указывается условие для строк.
# После запятой пусто, значит берем все столбцы.

clean_table <- my_table[complete_rows, ]

clean_table
##   numbers text
## 1       1    A
## 3       3    C
## 5       5    E

Вывод по заданию 3

В третьем задании была создана таблица с числовым и текстовым столбцами.

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

Этот способ удобен при работе с таблицами, потому что он удаляет всю строку, если хотя бы в одном столбце есть NA.

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

Условие

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

Выполнение задания

# Подключаем пакет caret.
# Он нужен для функции preProcess(), которая позволяет выполнять предобработку данных.

library(caret)
# Загружаем встроенный набор данных 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(...)) показывает, сколько NA находится в каждом признаке.

colSums(is.na(airquality))
##   Ozone Solar.R    Wind    Temp   Month     Day 
##      37       7       0       0       0       0

Заполнение пропусков средними значениями

# Создаем копию исходного набора данных.
# Это нужно, чтобы не изменять оригинальный airquality.

airquality_mean <- airquality

# В столбце Ozone заменяем NA на среднее значение Ozone.
# na.rm = TRUE означает, что при расчете среднего пропуски не учитываются.

airquality_mean$Ozone[is.na(airquality_mean$Ozone)] <- 
  mean(airquality$Ozone, na.rm = TRUE)

# В столбце Solar.R заменяем NA на среднее значение Solar.R.

airquality_mean$Solar.R[is.na(airquality_mean$Solar.R)] <- 
  mean(airquality$Solar.R, na.rm = TRUE)

head(airquality_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
# Проверяем, остались ли пропуски после заполнения средним.

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

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

# preProcess() с методом medianImpute заменяет пропуски медианными значениями.
# Медиана часто устойчивее среднего значения, потому что меньше зависит от выбросов.

preproc_median <- preProcess(
  airquality,
  method = "medianImpute"
)

airquality_median <- predict(preproc_median, airquality)

head(airquality_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
# Проверяем, остались ли NA после заполнения медианой.

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

Вывод по заданию 4

В четвертом задании был рассмотрен набор данных airquality.

Сначала было найдено количество пропущенных значений в каждом столбце. Пропуски присутствовали в столбцах Ozone и Solar.R.

Были использованы два способа заполнения пропусков:

  • замена на среднее значение;
  • замена на медиану с помощью preProcess().

После обработки количество пропусков стало равно нулю.

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

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

Условие

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

Выполнение задания

# Создаем два числовых набора данных.
# В первом наборе выбросом является 123.
# Во втором наборе выбросом является 250.

set1 <- c(1, 2, 3, 4, 123)
set2 <- c(10, 20, 30, 40, 250)

set1
## [1]   1   2   3   4 123
set2
## [1]  10  20  30  40 250
# Строим boxplot для первого набора.
# Отдельная точка на графике показывает выброс.

boxplot(
  set1,
  main = "Boxplot для первого набора данных"
)

# Строим boxplot для второго набора.

boxplot(
  set2,
  main = "Boxplot для второго набора данных"
)

# boxplot.stats() позволяет получить значения выбросов.
# $out возвращает именно выбросы.

outliers_set1 <- boxplot.stats(set1)$out
outliers_set2 <- boxplot.stats(set2)$out

outliers_set1
## [1] 123
outliers_set2
## [1] 250
# Удаляем выбросы.
# %in% проверяет, входит ли элемент в список выбросов.
# ! означает "не входит".

clean_set1 <- set1[!set1 %in% outliers_set1]
clean_set2 <- set2[!set2 %in% outliers_set2]

clean_set1
## [1] 1 2 3 4
clean_set2
## [1] 10 20 30 40

Вывод по заданию 5

В пятом задании были созданы два числовых набора данных с явно выраженными выбросами.

С помощью графика boxplot() выбросы были визуально обнаружены. В первом наборе выбросом оказалось значение 123, во втором — значение 250.

Функция boxplot.stats() позволила получить выбросы программно, после чего они были удалены из наборов данных.

После удаления выбросов данные стали более однородными и пригодными для дальнейшего анализа.

Задание 6. Удаление дубликатов

Условие

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

Выполнение задания

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

dup_table <- data.frame(
  col1 = c(1, 2, 3, 3, 2, 1),
  col2 = c("A", "B", "C", "C", "B", "A")
)

dup_table
##   col1 col2
## 1    1    A
## 2    2    B
## 3    3    C
## 4    3    C
## 5    2    B
## 6    1    A
# unique() сразу возвращает таблицу без повторов.

unique_table <- unique(dup_table)

unique_table
##   col1 col2
## 1    1    A
## 2    2    B
## 3    3    C
# duplicated() показывает, какие строки повторяются.
# !duplicated() оставляет только первые уникальные строки.

no_dup_table <- dup_table[!duplicated(dup_table), ]

no_dup_table
##   col1 col2
## 1    1    A
## 2    2    B
## 3    3    C

Вывод по заданию 6

В шестом задании была создана таблица с дублирующимися строками.

Для удаления повторов были использованы два способа:

  • unique();
  • duplicated().

Функция unique() сразу возвращает таблицу только с уникальными строками. Это самый простой способ удаления дубликатов.

Функция duplicated() показывает, какие строки являются повторными. В сочетании с отрицанием ! она позволяет оставить только первые вхождения строк.

Оба метода дали одинаковый результат, но duplicated() дает больше контроля над процессом отбора.

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

Условие

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

Выполнение задания

# Подключаем пакет mice.
# Он используется для более сложного заполнения пропущенных значений.

library(mice)
# Снова используем датасет airquality.
# Метод pmm означает predictive mean matching.
# Он подбирает пропущенные значения на основе похожих наблюдений.

imp <- mice(
  airquality,
  m = 2,
  maxit = 10,
  method = "pmm",
  seed = 123,
  printFlag = FALSE
)
# complete() возвращает готовый набор данных без пропусков.

airquality_mice <- complete(imp)

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(airquality_mice)
##   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     6     273 14.3   56     5   5
## 6    28     186 14.9   66     5   6
# Проверяем количество пропусков после обработки.

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

Вывод по заданию 7

В седьмом задании пропуски в наборе airquality были обработаны с помощью пакета mice.

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

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

Этот способ считается более гибким, потому что он лучше сохраняет структуру исходных данных.

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

Условие

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

Выполнение задания

# Создаем искусственные данные.
# x1 — случайная переменная.
# x2 создается на основе x1, поэтому между ними будет сильная связь.
# y зависит от x1 и x2.

set.seed(123)

x1 <- rnorm(100)
x2 <- x1 * 2 + rnorm(100, 0, 0.1)
y <- 3 * x1 + 2 * x2 + rnorm(100)

data_mc <- data.frame(y, x1, x2)

head(data_mc)
##            y          x1         x2
## 1 -1.8666005 -0.56047565 -1.1919919
## 2 -0.2474527 -0.23017749 -0.4346666
## 3 10.5964748  1.55870831  3.0927474
## 4  0.9672443  0.07050839  0.1062625
## 5  0.3003505  0.12928774  0.1634136
## 6 11.5202025  1.71506499  3.4256272
# Корреляционная матрица показывает связь между переменными.
# Значения, близкие к 1 или -1, говорят о сильной связи.

cor_matrix <- cor(data_mc)

cor_matrix
##            y        x1        x2
## y  1.0000000 0.9882897 0.9887106
## x1 0.9882897 1.0000000 0.9985963
## x2 0.9887106 0.9985963 1.0000000
# Строим линейную регрессию.
# В модель включены оба признака: x1 и x2.

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

summary(model)
## 
## Call:
## lm(formula = y ~ x1 + x2, data = data_mc)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1.8730 -0.6607 -0.1245  0.6214  2.0798 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)  
## (Intercept)  0.13507    0.09614   1.405    0.163  
## x1           2.39060    1.97748   1.209    0.230  
## x2           2.23811    0.98995   2.261    0.026 *
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.9513 on 97 degrees of freedom
## Multiple R-squared:  0.9779, Adjusted R-squared:  0.9774 
## F-statistic:  2144 on 2 and 97 DF,  p-value: < 2.2e-16
# Подключаем пакет car для функции vif().
# VIF показывает, насколько сильно признак связан с другими признаками модели.

library(car)

vif(model)
##       x1       x2 
## 356.4434 356.4434

Вывод по заданию 8

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

Мультиколлинеарность возникает, когда признаки в модели сильно связаны друг с другом. В данном случае переменная x2 была специально создана на основе x1, поэтому между ними появилась очень высокая корреляция.

Корреляционная матрица показывает, что связь между x1 и x2 близка к 1. Это означает почти линейную зависимость между признаками.

Функция vif() показала очень высокие значения VIF. Большое значение VIF говорит о проблеме мультиколлинеарности. Из-за этого коэффициенты модели могут становиться нестабильными, а интерпретация результатов — менее надежной.

Общий вывод по лабораторной работе

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

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

Предварительная подготовка данных является важным этапом анализа, потому что от качества исходных данных зависит корректность дальнейших расчетов, моделей и выводов.