В работе показаны базовые шаги предобработки данных: пропуски
(удаление и заполнение), выбросы, дубликаты, импутация с
mice и пример мультиколлинеарности.
Создадим числовой вектор с пропусками.
v <- c(2, NA, 6, 8, NA, 13, 21, 34, NA, 55)
v
## [1] 2 NA 6 8 NA 13 21 34 NA 55
Найдём пропуски и получим «чистый» вектор без NA.
mask_na <- is.na(v)
mask_na
## [1] FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE TRUE FALSE
v_ok <- v[!mask_na]
v_ok
## [1] 2 6 8 13 21 34 55
Вывод: is.na() отмечает пропуски TRUE/
FALSE, а !mask_na оставляет только реальные значения.
Сгенерируем таблицу с пропусками.
tbl <- data.frame(
code = c("X1", "X2", "X3", "X4", "X5"),
qty = c(5, NA, 12, 10, NA),
note = c("ok", "ok", NA, "check", "ok"),
store = c("A", NA, "A", "B", "B")
)
tbl
## code qty note store
## 1 X1 5 ok A
## 2 X2 NA ok <NA>
## 3 X3 12 <NA> A
## 4 X4 10 check B
## 5 X5 NA ok B
Оставим строки, где нет пропусков.
tbl_good <- tbl[complete.cases(tbl), ]
tbl_good
## code qty note store
## 1 X1 5 ok A
## 4 X4 10 check B
Вывод: complete.cases() удаляет строки,
где есть хотя бы один NA.
Датасет airquality содержит NA. Посмотрим их
количество.
data(airquality)
colSums(is.na(airquality))
## Ozone Solar.R Wind Temp Month Day
## 37 7 0 0 0 0
aq_mean <- airquality
num_cols <- names(aq_mean)[sapply(aq_mean, is.numeric)]
for (nm in num_cols) {
aq_mean[[nm]][is.na(aq_mean[[nm]])] <- mean(aq_mean[[nm]], na.rm = TRUE)
}
colSums(is.na(aq_mean))
## Ozone Solar.R Wind Temp Month Day
## 0 0 0 0 0 0
aq_med <- airquality
for (nm in num_cols) {
aq_med[[nm]][is.na(aq_med[[nm]])] <- median(aq_med[[nm]], na.rm = TRUE)
}
colSums(is.na(aq_med))
## Ozone Solar.R Wind Temp Month Day
## 0 0 0 0 0 0
Вывод: медиана обычно лучше, если в данных возможны выбросы.
Сделаем два набора и добавим явные выбросы.
set.seed(7)
p <- c(rnorm(30, 50, 4), 85, 90) # выбросы сверху
q <- c(rnorm(30, 200, 15), 120, 320) # выбросы снизу и сверху
Построим boxplot.
boxplot(p, main="Boxplot набора p (с выбросами)")
boxplot(q, main="Boxplot набора q (с выбросами)")
Вывод: точки за «усами» — это выбросы (слишком большие/маленькие значения).
Найдём и удалим выбросы:
p_out <- boxplot.stats(p)$out
q_out <- boxplot.stats(q)$out
p2 <- p[!(p %in% p_out)]
q2 <- q[!(q %in% q_out)]
Boxplot после удаления:
boxplot(p2, main="Набор p после удаления выбросов")
boxplot(q2, main="Набор q после удаления выбросов")
Вывод: после удаления выбросов коробка становится более «ровной», разброс уменьшается.
Создадим таблицу с повторяющимися строками.
dup <- data.frame(
id = c(1, 1, 2, 3, 3, 3, 4),
tag = c("A", "A", "B", "C", "C", "C", "D")
)
dup
## id tag
## 1 1 A
## 2 1 A
## 3 2 B
## 4 3 C
## 5 3 C
## 6 3 C
## 7 4 D
Через unique():
u1 <- unique(dup)
u1
## id tag
## 1 1 A
## 3 2 B
## 4 3 C
## 7 4 D
Через duplicated():
u2 <- dup[!duplicated(dup), ]
u2
## id tag
## 1 1 A
## 3 2 B
## 4 3 C
## 7 4 D
Проверка:
identical(u1, u2)
## [1] TRUE
Вывод: оба способа оставляют только первые уникальные строки.
Возьмём учебный датасет nhanes2 из пакета mice и
заполним пропуски.
library(mice)
data(nhanes2, package = "mice")
nhanes2
## age bmi hyp chl
## 1 20-39 NA <NA> NA
## 2 40-59 22.7 no 187
## 3 20-39 NA no 187
## 4 60-99 NA <NA> NA
## 5 20-39 20.4 no 113
## 6 60-99 NA <NA> 184
## 7 20-39 22.5 no 118
## 8 20-39 30.1 no 187
## 9 40-59 22.0 no 238
## 10 40-59 NA <NA> NA
## 11 20-39 NA <NA> NA
## 12 40-59 NA <NA> NA
## 13 60-99 21.7 no 206
## 14 40-59 28.7 yes 204
## 15 20-39 29.6 no NA
## 16 20-39 NA <NA> NA
## 17 60-99 27.2 yes 284
## 18 40-59 26.3 yes 199
## 19 20-39 35.3 no 218
## 20 60-99 25.5 yes NA
## 21 20-39 NA <NA> NA
## 22 20-39 33.2 no 229
## 23 20-39 27.5 no 131
## 24 60-99 24.9 no NA
## 25 40-59 27.4 no 186
colSums(is.na(nhanes2))
## age bmi hyp chl
## 0 9 8 10
Импутация:
imp <- mice(nhanes2, m = 3, method = "pmm", seed = 7, printFlag = FALSE)
filled <- complete(imp, 1)
filled
## age bmi hyp chl
## 1 20-39 30.1 no 187
## 2 40-59 22.7 no 187
## 3 20-39 22.0 no 187
## 4 60-99 28.7 no 186
## 5 20-39 20.4 no 113
## 6 60-99 28.7 no 184
## 7 20-39 22.5 no 118
## 8 20-39 30.1 no 187
## 9 40-59 22.0 no 238
## 10 40-59 22.7 no 187
## 11 20-39 35.3 no 206
## 12 40-59 27.5 yes 218
## 13 60-99 21.7 no 206
## 14 40-59 28.7 yes 204
## 15 20-39 29.6 no 187
## 16 20-39 30.1 no 229
## 17 60-99 27.2 yes 284
## 18 40-59 26.3 yes 199
## 19 20-39 35.3 no 218
## 20 60-99 25.5 yes 186
## 21 20-39 30.1 no 229
## 22 20-39 33.2 no 229
## 23 20-39 27.5 no 131
## 24 60-99 24.9 no 206
## 25 40-59 27.4 no 186
colSums(is.na(filled))
## age bmi hyp chl
## 0 0 0 0
Вывод: mice заполняет NA, используя
связи между столбцами (а не просто среднее/медиану).
Сгенерируем зависимые признаки: один почти копия другого.
set.seed(9)
n <- 150
a1 <- rnorm(n)
a2 <- a1 * 0.98 + rnorm(n, sd = 0.03) # почти то же самое
a3 <- rnorm(n)
y <- 5*a1 + rnorm(n)
dat <- data.frame(y, a1, a2, a3)
cor(dat[, c("a1","a2","a3")])
## a1 a2 a3
## a1 1.0000000 0.9995297 0.1651020
## a2 0.9995297 1.0000000 0.1683141
## a3 0.1651020 0.1683141 1.0000000
Модель:
fit <- lm(y ~ a1 + a2 + a3, data = dat)
summary(fit)
##
## Call:
## lm(formula = y ~ a1 + a2 + a3, data = dat)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.66766 -0.60266 0.02922 0.62948 2.51922
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.04493 0.08150 0.551 0.582
## a1 3.98990 2.73658 1.458 0.147
## a2 0.88927 2.78279 0.320 0.750
## a3 -0.04736 0.08552 -0.554 0.581
##
## Residual standard error: 0.9899 on 146 degrees of freedom
## Multiple R-squared: 0.9587, Adjusted R-squared: 0.9578
## F-statistic: 1129 on 3 and 146 DF, p-value: < 2.2e-16
VIF:
if (!requireNamespace("car", quietly = TRUE)) install.packages("car")
library(car)
vif(fit)
## a1 a2 a3
## 1074.92115 1076.10591 1.04033
Вывод:
Если VIF большой (обычно > 5 или > 10), значит признаки сильно
зависят друг от друга.
Здесь a1 и a2 почти одинаковые, поэтому у них
будет высокий VIF.