Освоить основные способы подготовки исходных данных в R: работу с
пропусками (NA), обнаружение и удаление выбросов и дублей,
множественную импутацию, а также анализ мультиколлинеарности.
Задание. Сформировать собственный датасет с помощью
функции c() (конкатенация), в котором содержатся числовые
данные и значения NA.
#> Исходный вектор:
#> [1] 10 25 NA 42 NA 7 33 NA 58 19
#> Количество NA: 3
Анализ. Вектор my_data создан функцией
c() и содержит 10 числовых значений, три из которых —
NA (пропуски). Функция sum(is.na(my_data))
подсчитала количество пропусков (3). Такой датасет имитирует «сырые»
данные, которые часто встречаются на практике.
is.na()Задание. Провести очистку данных с использованием
функции is.na() и вывести «чистый» датасет.
#> Логический вектор is.na():
#> [1] FALSE FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE
#>
#> "Чистый" вектор (без NA):
#> [1] 10 25 42 7 33 58 19
#> Количество элементов после очистки: 7
Анализ. Функция is.na() возвращает
логический вектор: TRUE там, где стоит NA, и
FALSE в остальных позициях. С помощью оператора
! (логическое НЕ) инвертируем его и оставляем только
«хорошие» значения. После очистки осталось 7 элементов (из 10
исходных).
complete.cases()Задание. Сгенерировать таблицу данных с числовыми и
текстовыми столбцами (через c()). Очистить данные с помощью
функции complete.cases().
df3 <- data.frame(
id = c(1, 2, 3, 4, 5, 6),
name = c("Анна", "Борис", NA, "Дмитрий", "Елена", NA),
score = c(85, NA, 90, 78, NA, 92),
grade = c("B", "A", "A+", NA, "C", "A")
)
cat("Исходная таблица:\n")#> Исходная таблица:
#> id name score grade
#> 1 1 Анна 85 B
#> 2 2 Борис NA A
#> 3 3 <NA> 90 A+
#> 4 4 Дмитрий 78 <NA>
#> 5 5 Елена NA C
#> 6 6 <NA> 92 A
#>
#> complete.cases():
#> [1] TRUE FALSE FALSE FALSE FALSE FALSE
#>
#> Таблица после complete.cases():
#> id name score grade
#> 1 1 Анна 85 B
#>
#> Количество строк до очистки: 6
#> Количество строк после очистки: 1
Анализ. Функция complete.cases()
проверяет все столбцы строки сразу и возвращает
TRUE только если в строке нет ни одного NA.
После фильтрации из 6 строк осталась только 1 полностью заполненная. Это
удобнее, чем проверять каждый столбец отдельно через
is.na(). Если в данных есть пропуски, представленные
строкой "NA", необходимо при загрузке указать
na.strings = "NA" в функциях чтения (например,
read.table()), иначе complete.cases() не
распознает их как пропуски.
airquality и preProcess из пакета
caretЗадание. Проанализировать датасет
airquality с пропусками из пакета caret. С
использованием функции preProcess() заполнить пропуски
предсказанными значениями (среднее, медиана).
#> Пропуски в airquality (по столбцам):
#> Ozone Solar.R Wind Temp Month Day
#> 37 7 0 0 0 0
# Заполнение медианой через preProcess
preproc_median <- preProcess(airquality, method = "medianImpute")
airquality_median <- predict(preproc_median, airquality)
# Заполнение средним (вручную, т.к. meanImpute в preProcess нет)
airquality_mean <- airquality
for (col in names(airquality_mean)) {
if (any(is.na(airquality_mean[[col]]))) {
airquality_mean[[col]][is.na(airquality_mean[[col]])] <-
mean(airquality_mean[[col]], na.rm = TRUE)
}
}
cat("\nПропуски после medianImpute:\n")#>
#> Пропуски после medianImpute:
#> Ozone Solar.R Wind Temp Month Day
#> 0 0 0 0 0 0
#>
#> Пропуски после meanImpute:
#> Ozone Solar.R Wind Temp Month Day
#> 0 0 0 0 0 0
#>
#> Первые 5 строк airquality с заполнением медианой:
#> 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). Функция preProcess() с методом
medianImpute заполняет пропуски медианным значением каждого
столбца. Медиана предпочтительнее среднего, если в данных присутствуют
выбросы, так как она устойчива к ним. В preProcess метод
meanImpute отсутствует, поэтому заполнение средним
выполнено вручную через цикл.
boxplotЗадание. Сгенерировать два числовых набора данных,
добавить в них выбросы. С использованием функции boxplot()
обнаружить и удалить выбросы.
set.seed(42)
data1 <- c(rnorm(100, mean = 50, sd = 10), 200, -50, 180)
data2 <- c(rnorm(100, mean = 30, sd = 5), 100, -20, 90)
cat("Выбросы в data1 (boxplot):\n")#> Выбросы в data1 (boxplot):
#> [1] 23.43545 20.06910 200.00000 -50.00000 180.00000
#>
#> Выбросы в data2 (boxplot):
#> [1] 43.50946 100.00000 -20.00000 90.00000
data1_clean <- data1[!data1 %in% out1]
data2_clean <- data2[!data2 %in% out2]
cat("\ndata1: было", length(data1), "-> стало", length(data1_clean), "\n")#>
#> data1: было 103 -> стало 98
#> data2: было 103 -> стало 99
par(mfrow = c(1, 2))
boxplot(data1, main = "data1 до очистки", col = "lightblue")
boxplot(data1_clean, main = "data1 после очистки", col = "lightgreen")Анализ. В оба набора были специально добавлены
экстремальные значения (200, -50, 180 для data1 и 100, -20,
90 для data2). Функция boxplot() с параметром
plot = FALSE возвращает список, в элементе
$out которого хранятся обнаруженные выбросы. Выбросами
считаются значения, выходящие за пределы интервала \([Q1 - 1.5 \cdot IQR;\ Q3 + 1.5 \cdot
IQR]\). После удаления размеры наборов стали 100 и 100
соответственно.
unique() и duplicated()Задание. Сгенерировать таблицу данных, в которой
дублируются строки. Удалить дубли с использованием функций
unique(), duplicated(). Сравнить
результаты.
df6 <- data.frame(
id = c(1, 2, 3, 2, 4, 1, 5, 3),
name = c("А", "Б", "В", "Б", "Г", "А", "Д", "В"),
val = c(10, 20, 30, 20, 40, 10, 50, 30)
)
cat("Исходная таблица:\n")#> Исходная таблица:
#> id name val
#> 1 1 А 10
#> 2 2 Б 20
#> 3 3 В 30
#> 4 2 Б 20
#> 5 4 Г 40
#> 6 1 А 10
#> 7 5 Д 50
#> 8 3 В 30
#>
#> Результат unique():
#> id name val
#> 1 1 А 10
#> 2 2 Б 20
#> 3 3 В 30
#> 5 4 Г 40
#> 7 5 Д 50
#>
#> Результат duplicated():
#> [1] FALSE FALSE FALSE TRUE FALSE TRUE FALSE TRUE
#>
#> Таблица без дублей (через duplicated):
#> id name val
#> 1 1 А 10
#> 2 2 Б 20
#> 3 3 В 30
#> 5 4 Г 40
#> 7 5 Д 50
cat("\nСравнение: unique() вернул", nrow(df_unique),
"строк, duplicated() —", nrow(df_no_dup), "строк.\n")#>
#> Сравнение: unique() вернул 5 строк, duplicated() — 5 строк.
Анализ. В таблице df6 строки с
id = 1, 2, 3 повторяются. Функция unique()
возвращает таблицу, в которой оставлены только уникальные строки (первое
вхождение). Функция duplicated() возвращает логический
вектор: TRUE для каждой строки, которая уже встречалась
ранее. Результаты обеих функций совпадают по количеству строк (5), но
duplicated() удобнее, если нужно не просто получить
уникальные строки, а узнать, какие именно являются дублями.
miceЗадание. Обработать пропуски в данных с
использованием пакета mice.
#> Пропуски в airquality:
#> Ozone Solar.R Wind Temp Month Day
#> 37 7 0 0 0 0
imp <- mice(airquality, m = 5, method = "pmm", seed = 123, printFlag = FALSE)
airquality_mice <- complete(imp, 1)
cat("\nПропуски после mice:\n")#>
#> Пропуски после mice:
#> Ozone Solar.R Wind Temp Month Day
#> 0 0 0 0 0 0
#>
#> Первые 5 строк после 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 18 150 14.3 56 5 5
#> 6 28 48 14.9 66 5 6
Анализ. Пакет mice реализует
множественную импутацию: вместо одного значения для
каждого пропуска генерируется несколько (по умолчанию 5) вариантов
заполнения. Метод pmm (predictive mean matching) подбирает
значения из реальных наблюдений, наиболее близких к предсказанным.
Функция complete(imp, 1) извлекает первый завершённый набор
данных. Множественная импутация статистически обоснованнее, чем простое
заполнение средним или медианой, так как учитывает неопределённость.
Задание. Разобрать пример с мультиколлинеарностью.
set.seed(42)
n <- 100
x1 <- rnorm(n, mean = 10, sd = 2)
x2 <- 0.9 * x1 + rnorm(n, sd = 0.3) # x2 почти линейно зависит от x1
x3 <- rnorm(n, mean = 5, sd = 1)
y <- 2 * x1 + 1.5 * x3 + rnorm(n, sd = 2)
df8 <- data.frame(y = y, x1 = x1, x2 = x2, x3 = x3)
model <- lm(y ~ ., data = df8)
cat("Результаты модели:\n")#> Результаты модели:
#>
#> Call:
#> lm(formula = y ~ ., data = df8)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -3.5888 -1.1734 -0.2077 1.2376 4.6560
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) -0.19842 1.34461 -0.148 0.88299
#> x1 2.00478 0.60330 3.323 0.00126 **
#> x2 0.05886 0.65935 0.089 0.92905
#> x3 1.43678 0.17765 8.088 1.85e-12 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 1.773 on 96 degrees of freedom
#> Multiple R-squared: 0.8598, Adjusted R-squared: 0.8554
#> F-statistic: 196.2 on 3 and 96 DF, p-value: < 2.2e-16
#>
#> VIF (Variance Inflation Factor):
#> x1 x2 x3
#> 49.696018 49.523787 1.027438
#>
#> Матрица корреляций:
#> x1 x2 x3
#> x1 1.000 0.990 -0.145
#> x2 0.990 1.000 -0.132
#> x3 -0.145 -0.132 1.000
#>
#> Модель без x2 (VIF):
#> x1 x3
#> 1.021409 1.021409
Анализ. Переменная x2 была построена
как почти линейная функция от x1
(x2 = 0.9 * x1 + шум), что создаёт сильную корреляцию между
ними. В первой модели VIF для x1 и x2
превышает 5, что указывает на мультиколлинеарность —
явление, при котором независимые переменные сильно коррелируют между
собой, и коэффициенты регрессии становятся неустойчивыми. После удаления
x2 значения VIF для оставшихся переменных становятся близки
к 1, что говорит о нормальном состоянии модели. Матрица корреляций
подтверждает: коэффициент корреляции между x1 и
x2 близок к 0.99.
В ходе лабораторной работы были освоены ключевые приёмы подготовки исходных данных в R:
is.na() для
векторов, complete.cases() для таблиц,
preProcess() из caret для заполнения медианой
и mice для множественной импутации.boxplot() и правила межквартильного размаха.unique() и
duplicated() дают одинаковый результат, но применяются
по-разному.Правильная подготовка данных — обязательный этап любого анализа: пропуски, выбросы и дубли искажают статистические оценки, а мультиколлинеарность делает модель регрессии нестабильной.