my_data <- c(10, 20, NA, 40, 50, NA, 70, 80, 90, NA)
print(my_data)
## [1] 10 20 NA 40 50 NA 70 80 90 NA
Создан вектор из 10 числовых значений, три из которых — NA. Это типичная ситуация в реальных данных.
is.na(my_data)
## [1] FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE TRUE
clean_data <- my_data[!is.na(my_data)]
print(clean_data)
## [1] 10 20 40 50 70 80 90
length(my_data) - length(clean_data)
## [1] 3
is.na() вернул логический вектор, где TRUE соответствует пропускам. С помощью !is.na() и индексации мы оставили только «полные» значения. Из 10 наблюдений удалены 3 пропуска, осталось 7.
df <- data.frame(
num = c(1, 2, NA, 4, 5),
txt = c("a", "b", "c", NA, "e"),
stringsAsFactors = FALSE
)
print(df)
## num txt
## 1 1 a
## 2 2 b
## 3 NA c
## 4 4 <NA>
## 5 5 e
# complete.cases — TRUE, если в строке нет ни одного NA
complete.cases(df)
## [1] TRUE TRUE FALSE FALSE TRUE
df_clean <- df[complete.cases(df), ]
print(df_clean)
## num txt
## 1 1 a
## 2 2 b
## 5 5 e
complete.cases() вернул FALSE для 3-й и 4-й строк (в одной NA в num, в другой — в txt). Фильтрация оставила 3 полные строки. В отличие от is.na(), который проверяет один столбец, complete.cases() проверяет всю строку целиком — удобно для смешанных таблиц.
library(caret)
data(airquality)
# Сколько пропусков в каждом столбце
colSums(is.na(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)
cat("После медианной импутации, NA:\n"); print(colSums(is.na(airquality_median)))
## После медианной импутации, NA:
## Ozone Solar.R Wind Temp Month Day
## 0 0 0 0 0 0
# Заполнение средним вручную (preProcess поддерживает medianImpute,
# но не meanImpute — среднее считаем сами)
airquality_mean <- airquality
for (col in names(airquality_mean)) {
if (anyNA(airquality_mean[[col]])) {
airquality_mean[[col]][is.na(airquality_mean[[col]])] <-
mean(airquality_mean[[col]], na.rm = TRUE)
}
}
cat("После средней импутации, NA:\n"); print(colSums(is.na(airquality_mean)))
## После средней импутации, NA:
## Ozone Solar.R Wind Temp Month Day
## 0 0 0 0 0 0
# Сравним распределения
summary(airquality$Ozone)
## Min. 1st Qu. Median Mean 3rd Qu. Max. NAs
## 1.00 18.00 31.50 42.13 63.25 168.00 37
summary(airquality_median$Ozone)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1.00 21.00 31.50 39.56 46.00 168.00
summary(airquality_mean$Ozone)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1.00 21.00 42.13 42.13 46.00 168.00
В airquality пропуски только в Ozone (37) и Solar.R (7). preProcess(method = “medianImpute”) заполнил их медианой: пропусков не осталось. Среднее заполнение мы реализовали вручную — оно даёт чуть другое распределение (среднее чувствительнее к выбросам, чем медиана). Медиана предпочтительнее для скошенных распределений (как у Ozone).
set.seed(123)
# Два набора с добавленными выбросами
data1 <- c(rnorm(100, mean = 50, sd = 10), 200, 250, -100)
data2 <- c(rnorm(100, mean = 100, sd = 20), 300, -50)
# Boxplot "до"
boxplot(data1, main = "data1: с выбросами", col = "lightblue")
boxplot(data2, main = "data2: с выбросами", col = "lightgreen")
# Функция поиска выбросов по правилу 1.5*IQR
find_outliers <- function(x) {
q1 <- quantile(x, 0.25); q3 <- quantile(x, 0.75)
iqr <- q3 - q1
x[x < (q1 - 1.5*iqr) | x > (q3 + 1.5*iqr)]
}
out1 <- find_outliers(data1); out2 <- find_outliers(data2)
cat("Выбросы в data1:", out1, "\n")
## Выбросы в data1: 200 250 -100
cat("Выбросы в data2:", out2, "\n")
## Выбросы в data2: 164.8208 300 -50
# Удаление выбросов
clean1 <- data1[!(data1 %in% out1)]
clean2 <- data2[!(data2 %in% out2)]
# Boxplot "после"
boxplot(clean1, main = "data1: очищено", col = "lightblue")
boxplot(clean2, main = "data2: очищено", col = "lightgreen")
Boxplot визуально помечает выбросы точками за пределами «усов». Правило 1.5×IQR: всё, что ниже Q1 − 1.5·IQR или выше Q3 + 1.5·IQR, считается выбросом. В data1 обнаружены 3 выброса (200, 250, −100), в data2 — 2 (300, −50). После удаления «усы» на графиках расширились, распределение стало компактнее.
df_dup <- data.frame(
id = c(1, 2, 2, 3, 4, 4, 4, 5),
val = c("a", "b", "b", "c", "d", "d", "d", "e"),
stringsAsFactors = FALSE
)
print(df_dup)
## id val
## 1 1 a
## 2 2 b
## 3 2 b
## 4 3 c
## 5 4 d
## 6 4 d
## 7 4 d
## 8 5 e
cat("Строк всего:", nrow(df_dup), "\n")
## Строк всего: 8
# duplicated: TRUE для повторных вхождений (кроме первого)
print(duplicated(df_dup))
## [1] FALSE FALSE TRUE FALSE FALSE TRUE TRUE FALSE
# Метод 1: unique()
u1 <- unique(df_dup)
print(u1)
## id val
## 1 1 a
## 2 2 b
## 4 3 c
## 5 4 d
## 8 5 e
# Метод 2: duplicated() + индексация
u2 <- df_dup[!duplicated(df_dup), ]
print(u2)
## id val
## 1 1 a
## 2 2 b
## 4 3 c
## 5 4 d
## 8 5 e
# Сравнение
identical(u1, u2)
## [1] TRUE
duplicated() возвращает TRUE для строк, которые уже встречались выше (первое вхождение — FALSE). unique() и df[!duplicated(df), ] дают идентичный результат (проверено через identical()): удалены 3 дублирующиеся строки. Разница только в синтаксисе: unique() короче, а duplicated() можно комбинировать с дополнительной логикой фильтрации.
library(mice)
data(airquality)
# Карта пропусков
md.pattern(airquality)
## Wind Temp Month Day Solar.R Ozone
## 111 1 1 1 1 1 1 0
## 35 1 1 1 1 1 0 1
## 5 1 1 1 1 0 1 1
## 2 1 1 1 1 0 0 2
## 0 0 0 0 7 37 44
# Множественная импутация (m=5 наборов, pmm = predictive mean matching)
set.seed(123)
imp <- mice(airquality, m = 5, method = "pmm", printFlag = FALSE)
# Берём первый заполненный набор
airquality_imp <- complete(imp, 1)
colSums(is.na(airquality_imp))
## Ozone Solar.R Wind Temp Month Day
## 0 0 0 0 0 0
# Сравним распределения до/после
summary(airquality$Ozone)
## Min. 1st Qu. Median Mean 3rd Qu. Max. NAs
## 1.00 18.00 31.50 42.13 63.25 168.00 37
summary(airquality_imp$Ozone)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1.00 18.00 29.00 40.66 59.00 168.00
mice выполняет множественную импутацию: создаёт m полных копий данных, в каждой пропуски заполняются по модели (метод pmm — predictive mean matching, учитывает связи между переменными). Это принципиально отличается от заполнения средним/медианой: сохраняется дисперсия и корреляционная структура. complete(imp, 1) извлекает первый заполненный набор — в нём пропусков нет. Множественная импутация считается «золотым стандартом» при работе с NA.
library(car)
set.seed(123)
n <- 100
x1 <- rnorm(n)
x2 <- 0.95 * x1 + rnorm(n, sd = 0.1) # сильно коррелирует с x1
x3 <- rnorm(n)
y <- 2 * x1 + 3 * x3 + rnorm(n)
df_mc <- data.frame(y, x1, x2, x3)
# Матрица корреляций
round(cor(df_mc), 3)
## y x1 x2 x3
## y 1.000 0.430 0.432 0.783
## x1 0.430 1.000 0.994 -0.129
## x2 0.432 0.994 1.000 -0.126
## x3 0.783 -0.129 -0.126 1.000
# Модель со всеми предикторами
m1 <- lm(y ~ x1 + x2 + x3, data = df_mc)
summary(m1)
##
## Call:
## lm(formula = y ~ x1 + x2 + x3, data = df_mc)
##
## 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) -0.01933 0.10734 -0.180 0.858
## x1 1.50548 1.04102 1.446 0.151
## x2 0.46218 1.09456 0.422 0.674
## x3 2.94261 0.11223 26.219 <2e-16 ***
## ---
## 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.9003, Adjusted R-squared: 0.8972
## F-statistic: 289 on 3 and 96 DF, p-value: < 2.2e-16
# VIF: > 5-10 — признак мультиколлинеарности
vif(m1)
## x1 x2 x3
## 80.852638 80.779423 1.017576
# Убираем один из коррелирующих признаков
m2 <- lm(y ~ x1 + x3, data = df_mc)
summary(m2)
##
## Call:
## lm(formula = y ~ x1 + x3, data = df_mc)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.44091 -0.63799 0.04562 0.69463 2.52196
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.02423 0.10626 -0.228 0.82
## x1 1.94228 0.11626 16.707 <2e-16 ***
## x3 2.94377 0.11172 26.349 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.047 on 97 degrees of freedom
## Multiple R-squared: 0.9001, Adjusted R-squared: 0.8981
## F-statistic: 437.1 on 2 and 97 DF, p-value: < 2.2e-16
vif(m2)
## x1 x3
## 1.01697 1.01697
Корреляция x1 и x2 ≈ 0.99. VIF для этих двух признаков сильно превышает порог 10 — это классический признак мультиколлинеарности: коэффициенты модели становятся неустойчивыми, стандартные ошибки растут, значимость теряется. После удаления x2 VIF для оставшихся предикторов < 5, коэффициенты модели стабильны и интерпретируемы. Практический вывод: при VIF > 10 один из коррелирующих признаков нужно удалить или объединить (например, через PCA).