Данная лабораторная работа посвящена способам подготовки исходных данных в языке R.
Перед анализом данных часто необходимо выполнить предварительную обработку: найти и удалить пропуски, обработать выбросы, удалить дубликаты, заполнить отсутствующие значения и проверить признаки на сильную взаимосвязь.
В работе рассматриваются следующие темы:
NA;complete.cases();mice;VIF.Перед выполнением отчета необходимые пакеты нужно один раз установить в консоли RStudio:
install.packages("caret")
install.packages("mice")
install.packages("car")
Сформировать собственный датасет с помощью функции c(),
в котором содержатся числовые данные и значения NA.
# Создаем числовой вектор.
# Вектор содержит обычные числа и два пропущенных значения NA.
my_data <- c(10, 20, NA, 30, 40, NA, 50)
# Выводим созданный вектор.
my_data
## [1] 10 20 NA 30 40 NA 50
В первом задании был создан простой числовой набор данных с помощью
функции c().
Вектор содержит как обычные числовые значения, так и пропущенные
значения 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
Во втором задании была выполнена очистка вектора от пропущенных значений.
Функция is.na() позволила определить позиции, где
находятся NA. После этого с помощью логической индексации
были выбраны только те элементы, которые не являются пропущенными.
В результате был получен очищенный вектор:
10, 20, 30, 40, 50
Таким образом, функция is.na() удобна для поиска и
удаления пропусков в простых наборах данных.
Сгенерировать таблицу данных с числовыми и текстовыми столбцами.
Очистить данные с помощью функции 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
В третьем задании была создана таблица с числовым и текстовым столбцами.
Функция complete.cases() позволила определить строки, в
которых нет ни одного пропущенного значения. После применения этой
функции в очищенной таблице остались только полностью заполненные
строки.
Этот способ удобен при работе с таблицами, потому что он удаляет всю
строку, если хотя бы в одном столбце есть NA.
Проанализировать датасет 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
В четвертом задании был рассмотрен набор данных
airquality.
Сначала было найдено количество пропущенных значений в каждом
столбце. Пропуски присутствовали в столбцах Ozone и
Solar.R.
Были использованы два способа заполнения пропусков:
preProcess().После обработки количество пропусков стало равно нулю.
Среднее значение удобно использовать, когда данные распределены относительно равномерно и нет сильных выбросов. Медиана является более устойчивым вариантом, потому что она меньше зависит от очень больших или очень маленьких значений.
Сгенерировать два числовых набора данных и добавить в них выбросы. С
использованием функции 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
В пятом задании были созданы два числовых набора данных с явно выраженными выбросами.
С помощью графика boxplot() выбросы были визуально
обнаружены. В первом наборе выбросом оказалось значение
123, во втором — значение 250.
Функция boxplot.stats() позволила получить выбросы
программно, после чего они были удалены из наборов данных.
После удаления выбросов данные стали более однородными и пригодными для дальнейшего анализа.
Сгенерировать таблицу данных, в которой дублируются строки. Удалить
строки с использованием функций 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
В шестом задании была создана таблица с дублирующимися строками.
Для удаления повторов были использованы два способа:
unique();duplicated().Функция unique() сразу возвращает таблицу только с
уникальными строками. Это самый простой способ удаления дубликатов.
Функция duplicated() показывает, какие строки являются
повторными. В сочетании с отрицанием ! она позволяет
оставить только первые вхождения строк.
Оба метода дали одинаковый результат, но duplicated()
дает больше контроля над процессом отбора.
Обработать пропуски в данных с использованием пакета
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
В седьмом задании пропуски в наборе airquality были
обработаны с помощью пакета mice.
Метод mice отличается от простой замены средним или
медианой тем, что он учитывает связь между переменными. Значения
подбираются не одинаковыми для всех строк, а на основе похожих
наблюдений.
После применения mice в наборе данных не осталось
пропущенных значений.
Этот способ считается более гибким, потому что он лучше сохраняет структуру исходных данных.
Разобрать пример с мультиколлинеарностью.
# Создаем искусственные данные.
# 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
В восьмом задании был создан пример с мультиколлинеарностью.
Мультиколлинеарность возникает, когда признаки в модели сильно
связаны друг с другом. В данном случае переменная x2 была
специально создана на основе x1, поэтому между ними
появилась очень высокая корреляция.
Корреляционная матрица показывает, что связь между x1 и
x2 близка к 1. Это означает почти линейную зависимость
между признаками.
Функция vif() показала очень высокие значения
VIF. Большое значение VIF говорит о проблеме
мультиколлинеарности. Из-за этого коэффициенты модели могут становиться
нестабильными, а интерпретация результатов — менее надежной.
В результате выполнения лабораторной работы были изучены основные способы предварительной подготовки данных в языке R.
Были рассмотрены методы поиска и удаления пропущенных значений,
очистки таблиц, заполнения пропусков средним и медианой, обработки
пропусков с помощью пакета mice, поиска выбросов, удаления
дубликатов и анализа мультиколлинеарности.
Предварительная подготовка данных является важным этапом анализа, потому что от качества исходных данных зависит корректность дальнейших расчетов, моделей и выводов.