Задание 1: Создание датасета с NA значениями и очистка через is.na()

# Создаем датасет с числовыми данными и NA
set.seed(123)
dataset1 <- c(10, 15, NA, 25, 30, NA, 45, 50, 55, NA, 65, 70)
cat("Исходный датасет с пропусками:\n")
## Исходный датасет с пропусками:
print(dataset1)
##  [1] 10 15 NA 25 30 NA 45 50 55 NA 65 70
# Проверяем наличие NA
na_check <- is.na(dataset1)
cat("\nПроверка на NA (TRUE - пропуск):\n")
## 
## Проверка на NA (TRUE - пропуск):
print(na_check)
##  [1] FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE
# Количество пропусков
cat("\nКоличество пропусков:", sum(na_check), "\n")
## 
## Количество пропусков: 3
# Очистка данных - удаляем NA
clean_dataset1 <- dataset1[!is.na(dataset1)]
cat("\nОчищенный датасет (без NA):\n")
## 
## Очищенный датасет (без NA):
print(clean_dataset1)
## [1] 10 15 25 30 45 50 55 65 70
# Альтернатива - замена NA на среднее
mean_value <- mean(dataset1, na.rm = TRUE)
dataset1_filled <- ifelse(is.na(dataset1), mean_value, dataset1)
cat("\nДатасет с заполненными пропусками (среднее =", round(mean_value, 2), "):\n")
## 
## Датасет с заполненными пропусками (среднее = 40.56 ):
print(dataset1_filled)
##  [1] 10.00000 15.00000 40.55556 25.00000 30.00000 40.55556 45.00000 50.00000
##  [9] 55.00000 40.55556 65.00000 70.00000

Вывод: Функция is.na() позволяет обнаружить пропущенные значения. Их можно удалить или заменить на среднее/медиану.

Задание 2: Таблица с числовыми и текстовыми данными + complete.cases()

# Создаем таблицу с числовыми и текстовыми данными
df2 <- data.frame(
  ID = 1:10,
  Name = c("Alice", "Bob", NA, "David", "Eva", NA, "George", "Hannah", NA, "Jack"),
  Age = c(25, 30, NA, 35, 28, NA, 32, 29, 40, 27),
  Score = c(85, 90, 78, NA, 88, 92, NA, 85, 91, 89),
  City = c("NY", "LA", "NY", NA, "LA", "NY", "LA", NA, "NY", "LA")
)

cat("Исходная таблица с пропусками:\n")
## Исходная таблица с пропусками:
print(df2)
##    ID   Name Age Score City
## 1   1  Alice  25    85   NY
## 2   2    Bob  30    90   LA
## 3   3   <NA>  NA    78   NY
## 4   4  David  35    NA <NA>
## 5   5    Eva  28    88   LA
## 6   6   <NA>  NA    92   NY
## 7   7 George  32    NA   LA
## 8   8 Hannah  29    85 <NA>
## 9   9   <NA>  40    91   NY
## 10 10   Jack  27    89   LA
# Используем complete.cases() для поиска полных строк
complete_rows <- complete.cases(df2)
cat("\nПолные строки (TRUE - нет пропусков):\n")
## 
## Полные строки (TRUE - нет пропусков):
print(complete_rows)
##  [1]  TRUE  TRUE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE
# Очищаем данные - оставляем только полные строки
clean_df2 <- df2[complete.cases(df2), ]
cat("\nОчищенная таблица (только полные строки):\n")
## 
## Очищенная таблица (только полные строки):
print(clean_df2)
##    ID  Name Age Score City
## 1   1 Alice  25    85   NY
## 2   2   Bob  30    90   LA
## 5   5   Eva  28    88   LA
## 10 10  Jack  27    89   LA
# Статистика
cat("\nСтатистика очистки:\n")
## 
## Статистика очистки:
cat("Исходное количество строк:", nrow(df2), "\n")
## Исходное количество строк: 10
cat("Удалено строк с пропусками:", sum(!complete_rows), "\n")
## Удалено строк с пропусками: 6
cat("Осталось строк:", nrow(clean_df2), "\n")
## Осталось строк: 4

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

Задание 3: Анализ airquality и заполнение пропусков (preProcess)

library(caret)

# Загружаем датасет airquality
data(airquality)
cat("Исходные данные airquality:\n")
## Исходные данные airquality:
print(head(airquality, 10))
##    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
## 7     23     299  8.6   65     5   7
## 8     19      99 13.8   59     5   8
## 9      8      19 20.1   61     5   9
## 10    NA     194  8.6   69     5  10
# Анализ пропусков
na_count <- colSums(is.na(airquality))
cat("\nКоличество пропусков в каждом столбце:\n")
## 
## Количество пропусков в каждом столбце:
print(na_count)
##   Ozone Solar.R    Wind    Temp   Month     Day 
##      37       7       0       0       0       0
# Визуализация пропусков
na_plot <- data.frame(
  Variable = names(na_count),
  NA_Count = na_count
)

ggplot(na_plot, aes(x = Variable, y = NA_Count, fill = Variable)) +
  geom_bar(stat = "identity") +
  theme_minimal() +
  labs(title = "Количество пропусков в данных airquality",
       x = "Переменные", y = "Количество NA") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Заполнение пропусков методом bagImpute (не требует RANN)
preProcess_bag <- preProcess(airquality, method = "bagImpute")
airquality_bag <- predict(preProcess_bag, airquality)
cat("\nДанные после заполнения пропусков (bagImpute):\n")
## 
## Данные после заполнения пропусков (bagImpute):
print(head(airquality_bag, 10))
##       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  14.76573 194.5703 14.3   56     5   5
## 6  28.00000 234.7085 14.9   66     5   6
## 7  23.00000 299.0000  8.6   65     5   7
## 8  19.00000  99.0000 13.8   59     5   8
## 9   8.00000  19.0000 20.1   61     5   9
## 10 23.50119 194.0000  8.6   69     5  10
# Заполнение пропусков медианой (через простую замену)
airquality_median <- airquality
for(i in 1:ncol(airquality_median)) {
  if(is.numeric(airquality_median[, i])) {
    airquality_median[is.na(airquality_median[, i]), i] <- median(airquality_median[, i], na.rm = TRUE)
  }
}

cat("\nПроверка пропусков после заполнения:\n")
## 
## Проверка пропусков после заполнения:
print(colSums(is.na(airquality_median)))
##   Ozone Solar.R    Wind    Temp   Month     Day 
##       0       0       0       0       0       0

Вывод: preProcess из пакета caret предоставляет различные методы для заполнения пропусков (knnImpute, medianImpute, bagImpute и др.).

Задание 4: Генерация выбросов и их удаление через boxplot

# Генерация двух наборов данных с выбросами
set.seed(123)
data1 <- c(rnorm(50, mean = 10, sd = 2), 50, 55, 60)  # нормальные данные + выбросы
data2 <- c(rnorm(50, mean = 20, sd = 3), 80, 85, 90, 95)  # нормальные данные + выбросы

# Создаем dataframe
df_outliers <- data.frame(
  Group = rep(c("Group1", "Group2"), c(length(data1), length(data2))),
  Value = c(data1, data2)
)

# Визуализация до удаления выбросов
par(mfrow = c(1, 2))
boxplot(data1, main = "Group 1 - Boxplot", ylab = "Values", col = "lightblue")
boxplot(data2, main = "Group 2 - Boxplot", ylab = "Values", col = "lightgreen")

par(mfrow = c(1, 1))

# Обнаружение выбросов
boxplot_stats1 <- boxplot(data1, plot = FALSE)
boxplot_stats2 <- boxplot(data2, plot = FALSE)

cat("Выбросы в Group 1:", boxplot_stats1$out, "\n")
## Выбросы в Group 1: 50 55 60
cat("Выбросы в Group 2:", boxplot_stats2$out, "\n")
## Выбросы в Group 2: 80 85 90 95
# Функция для удаления выбросов
remove_outliers <- function(x) {
  q1 <- quantile(x, 0.25)
  q3 <- quantile(x, 0.75)
  iqr <- q3 - q1
  lower_bound <- q1 - 1.5 * iqr
  upper_bound <- q3 + 1.5 * iqr
  return(x[x >= lower_bound & x <= upper_bound])
}

# Удаляем выбросы
clean_data1 <- remove_outliers(data1)
clean_data2 <- remove_outliers(data2)

# Визуализация после удаления выбросов
par(mfrow = c(1, 2))
boxplot(clean_data1, main = "Group 1 - After Outlier Removal", ylab = "Values", col = "lightblue")
boxplot(clean_data2, main = "Group 2 - After Outlier Removal", ylab = "Values", col = "lightgreen")

par(mfrow = c(1, 1))

# Статистика
cat("\n=== СТАТИСТИКА УДАЛЕНИЯ ВЫБРОСОВ ===\n")
## 
## === СТАТИСТИКА УДАЛЕНИЯ ВЫБРОСОВ ===
cat("Group 1:\n")
## Group 1:
cat("  Исходно элементов:", length(data1), "\n")
##   Исходно элементов: 53
cat("  Удалено выбросов:", length(data1) - length(clean_data1), "\n")
##   Удалено выбросов: 3
cat("  Осталось элементов:", length(clean_data1), "\n")
##   Осталось элементов: 50
cat("Group 2:\n")
## Group 2:
cat("  Исходно элементов:", length(data2), "\n")
##   Исходно элементов: 54
cat("  Удалено выбросов:", length(data2) - length(clean_data2), "\n")
##   Удалено выбросов: 4
cat("  Осталось элементов:", length(clean_data2), "\n")
##   Осталось элементов: 50

Вывод: Boxplot эффективно визуализирует выбросы (точки за пределами усов). Выбросы можно удалить, используя межквартильный размах (IQR).

Задание 5: Удаление дубликатов строк (unique, duplicated)

# Генерация таблицы с дубликатами
df_duplicates <- data.frame(
  ID = c(1, 2, 3, 2, 4, 5, 3, 6, 1, 7),
  Name = c("John", "Mary", "Peter", "Mary", "Anna", "John", "Peter", "Mike", "John", "Lisa"),
  Age = c(25, 30, 35, 30, 28, 25, 35, 32, 25, 29),
  Score = c(85, 90, 88, 90, 92, 85, 88, 87, 85, 91)
)

cat("Исходная таблица с дубликатами:\n")
## Исходная таблица с дубликатами:
print(df_duplicates)
##    ID  Name Age Score
## 1   1  John  25    85
## 2   2  Mary  30    90
## 3   3 Peter  35    88
## 4   2  Mary  30    90
## 5   4  Anna  28    92
## 6   5  John  25    85
## 7   3 Peter  35    88
## 8   6  Mike  32    87
## 9   1  John  25    85
## 10  7  Lisa  29    91
# Поиск дубликатов
duplicated_rows <- duplicated(df_duplicates)
cat("\nДубликаты строк (TRUE - дубликат):\n")
## 
## Дубликаты строк (TRUE - дубликат):
print(duplicated_rows)
##  [1] FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE  TRUE FALSE
# Удаление дубликатов с помощью duplicated()
df_unique_duplicated <- df_duplicates[!duplicated(df_duplicates), ]
cat("\nУдаление дубликатов через duplicated():\n")
## 
## Удаление дубликатов через duplicated():
print(df_unique_duplicated)
##    ID  Name Age Score
## 1   1  John  25    85
## 2   2  Mary  30    90
## 3   3 Peter  35    88
## 5   4  Anna  28    92
## 6   5  John  25    85
## 8   6  Mike  32    87
## 10  7  Lisa  29    91
# Удаление дубликатов с помощью unique()
df_unique_function <- unique(df_duplicates)
cat("\nУдаление дубликатов через unique():\n")
## 
## Удаление дубликатов через unique():
print(df_unique_function)
##    ID  Name Age Score
## 1   1  John  25    85
## 2   2  Mary  30    90
## 3   3 Peter  35    88
## 5   4  Anna  28    92
## 6   5  John  25    85
## 8   6  Mike  32    87
## 10  7  Lisa  29    91
# Сравнение результатов
identical(df_unique_duplicated, df_unique_function)
## [1] TRUE
cat("\nРезультаты duplicated() и unique() совпадают:", 
    identical(df_unique_duplicated, df_unique_function), "\n")
## 
## Результаты duplicated() и unique() совпадают: TRUE
# Поиск дубликатов по конкретному столбцу
duplicated_by_id <- duplicated(df_duplicates$ID)
cat("\nДубликаты по ID:\n")
## 
## Дубликаты по ID:
print(df_duplicates[duplicated_by_id, ])
##   ID  Name Age Score
## 4  2  Mary  30    90
## 7  3 Peter  35    88
## 9  1  John  25    85
# Удаление дубликатов по ID (оставляем первое вхождение)
df_unique_id <- df_duplicates[!duplicated(df_duplicates$ID), ]
cat("\nУдаление дубликатов по ID (оставляем первое вхождение):\n")
## 
## Удаление дубликатов по ID (оставляем первое вхождение):
print(df_unique_id)
##    ID  Name Age Score
## 1   1  John  25    85
## 2   2  Mary  30    90
## 3   3 Peter  35    88
## 5   4  Anna  28    92
## 6   5  John  25    85
## 8   6  Mike  32    87
## 10  7  Lisa  29    91

Вывод: unique() и duplicated() эффективно удаляют дубликаты строк. duplicated() дает больше контроля (можно выбрать, какие дубликаты удалять).

Задание 6: Обработка пропусков с использованием пакета mice

# Создаем данные с пропусками
set.seed(123)
data_mice <- data.frame(
  Age = c(25, 30, NA, 35, 28, NA, 32, 29, 40, 27),
  Income = c(50000, 60000, 55000, NA, 65000, 58000, NA, 62000, 70000, 52000),
  Score = c(85, 90, 78, 88, NA, 92, 86, 85, 91, 89)
)

cat("Исходные данные с пропусками:\n")
## Исходные данные с пропусками:
print(data_mice)
##    Age Income Score
## 1   25  50000    85
## 2   30  60000    90
## 3   NA  55000    78
## 4   35     NA    88
## 5   28  65000    NA
## 6   NA  58000    92
## 7   32     NA    86
## 8   29  62000    85
## 9   40  70000    91
## 10  27  52000    89
# Визуализация пропусков (VIM закомментирован, используем базовую статистику)
cat("\nКоличество пропусков:\n")
## 
## Количество пропусков:
print(colSums(is.na(data_mice)))
##    Age Income  Score 
##      2      2      1
# Применяем mice для множественного восстановления
imputed_data <- mice(data_mice, m = 5, method = 'pmm', seed = 123, printFlag = FALSE)
cat("\nМетод импутации:", imputed_data$method, "\n")
## 
## Метод импутации: pmm pmm pmm
cat("Количество импутаций:", imputed_data$m, "\n")
## Количество импутаций: 5
# Получаем заполненные данные
completed_data <- complete(imputed_data, 1)
cat("\nЗаполненные данные (первая импутация):\n")
## 
## Заполненные данные (первая импутация):
print(completed_data)
##    Age Income Score
## 1   25  50000    85
## 2   30  60000    90
## 3   27  55000    78
## 4   35  62000    88
## 5   28  65000    92
## 6   35  58000    92
## 7   32  58000    86
## 8   29  62000    85
## 9   40  70000    91
## 10  27  52000    89
# Сравнение оригинальных и восстановленных значений
cat("\n=== СРАВНЕНИЕ ОРИГИНАЛЬНЫХ И ВОССТАНОВЛЕННЫХ ЗНАЧЕНИЙ ===\n")
## 
## === СРАВНЕНИЕ ОРИГИНАЛЬНЫХ И ВОССТАНОВЛЕННЫХ ЗНАЧЕНИЙ ===
for(i in 1:ncol(data_mice)) {
  na_pos <- is.na(data_mice[, i])
  if(sum(na_pos) > 0) {
    cat("\nПеременная:", names(data_mice)[i], "\n")
    cat("Пропуски в строках:", which(na_pos), "\n")
    cat("Восстановленные значения:", completed_data[na_pos, i], "\n")
  }
}
## 
## Переменная: Age 
## Пропуски в строках: 3 6 
## Восстановленные значения: 27 35 
## 
## Переменная: Income 
## Пропуски в строках: 4 7 
## Восстановленные значения: 62000 58000 
## 
## Переменная: Score 
## Пропуски в строках: 5 
## Восстановленные значения: 92
# Визуализация результатов
par(mfrow = c(1, 3))
for(i in 1:ncol(data_mice)) {
  plot(density(data_mice[, i], na.rm = TRUE), 
       main = paste("Плотность -", names(data_mice)[i]),
       col = "blue", lwd = 2)
  lines(density(completed_data[, i]), col = "red", lwd = 2, lty = 2)
  legend("topright", legend = c("Original", "Imputed"), 
         col = c("blue", "red"), lty = 1:2, lwd = 2)
}

par(mfrow = c(1, 1))

Вывод: Пакет mice (Multiple Imputation by Chained Equations) предоставляет продвинутые методы для множественного восстановления пропущенных значений.

Задание 7: Анализ мультиколлинеарности

# Создаем данные с мультиколлинеарностью
set.seed(123)
n <- 100
X1 <- rnorm(n, mean = 10, sd = 2)
X2 <- X1 * 0.8 + rnorm(n, mean = 0, sd = 0.5)  # Сильно коррелирует с X1
X3 <- X1 * 0.3 + X2 * 0.5 + rnorm(n, mean = 0, sd = 0.3)  # Комбинация X1 и X2
X4 <- rnorm(n, mean = 5, sd = 1)  # Независимая переменная
Y <- 2*X1 + 3*X2 + 1.5*X3 + 0.5*X4 + rnorm(n, mean = 0, sd = 2)

df_corr <- data.frame(Y = Y, X1 = X1, X2 = X2, X3 = X3, X4 = X4)

# Корреляционная матрица
cor_matrix <- cor(df_corr)
cat("Корреляционная матрица:\n")
## Корреляционная матрица:
print(round(cor_matrix, 3))
##        Y     X1     X2     X3     X4
## Y  1.000  0.965  0.966  0.961  0.011
## X1 0.965  1.000  0.948  0.956 -0.044
## X2 0.966  0.948  1.000  0.968 -0.028
## X3 0.961  0.956  0.968  1.000 -0.046
## X4 0.011 -0.044 -0.028 -0.046  1.000
# Визуализация корреляций
library(corrplot)
corrplot(cor_matrix, method = "color", type = "upper", 
         addCoef.col = "black", tl.col = "black", 
         title = "Корреляционная матрица признаков")

# Выявление мультиколлинеарности через VIF (Variance Inflation Factor)
library(car)
model <- lm(Y ~ X1 + X2 + X3 + X4, data = df_corr)
vif_values <- vif(model)
cat("\nVIF значения:\n")
## 
## VIF значения:
print(vif_values)
##        X1        X2        X3        X4 
## 12.868932 17.538239 20.761554  1.006429
# Интерпретация VIF
cat("\n=== ИНТЕРПРЕТАЦИЯ VIF ===\n")
## 
## === ИНТЕРПРЕТАЦИЯ VIF ===
for(i in 1:length(vif_values)) {
  vif_val <- vif_values[i]
  interpretation <- ifelse(vif_val < 5, "Нет мультиколлинеарности",
                    ifelse(vif_val < 10, "Умеренная мультиколлинеарность",
                    "Сильная мультиколлинеарность"))
  cat(names(vif_values)[i], ":", round(vif_val, 2), "-", interpretation, "\n")
}
## X1 : 12.87 - Сильная мультиколлинеарность 
## X2 : 17.54 - Сильная мультиколлинеарность 
## X3 : 20.76 - Сильная мультиколлинеарность 
## X4 : 1.01 - Нет мультиколлинеарности
# Создаем альтернативные данные без мультиколлинеарности
set.seed(456)
X1_ind <- rnorm(n, mean = 10, sd = 2)
X2_ind <- rnorm(n, mean = 15, sd = 3)
X3_ind <- rnorm(n, mean = 20, sd = 4)
X4_ind <- rnorm(n, mean = 5, sd = 1)
Y_ind <- 2*X1_ind + 3*X2_ind + 1.5*X3_ind + 0.5*X4_ind + rnorm(n, mean = 0, sd = 2)

df_no_corr <- data.frame(Y = Y_ind, X1 = X1_ind, X2 = X2_ind, X3 = X3_ind, X4 = X4_ind)

# Сравнение VIF
model_no_corr <- lm(Y ~ X1 + X2 + X3 + X4, data = df_no_corr)
vif_no_corr <- vif(model_no_corr)

cat("\n=== СРАВНЕНИЕ С ДАННЫМИ БЕЗ МУЛЬТИКОЛЛИНЕАРНОСТИ ===\n")
## 
## === СРАВНЕНИЕ С ДАННЫМИ БЕЗ МУЛЬТИКОЛЛИНЕАРНОСТИ ===
comparison <- data.frame(
  Variable = names(vif_values),
  With_Multicollinearity = round(vif_values, 2),
  Without_Multicollinearity = round(vif_no_corr, 2)
)
print(comparison)
##    Variable With_Multicollinearity Without_Multicollinearity
## X1       X1                  12.87                      1.02
## X2       X2                  17.54                      1.01
## X3       X3                  20.76                      1.02
## X4       X4                   1.01                      1.01
# Визуализация сравнения
comparison_long <- reshape2::melt(comparison, id.vars = "Variable")
ggplot(comparison_long, aes(x = Variable, y = value, fill = variable)) +
  geom_bar(stat = "identity", position = "dodge") +
  theme_minimal() +
  labs(title = "Сравнение VIF значений",
       x = "Переменные", y = "VIF значение") +
  geom_hline(yintercept = 5, linetype = "dashed", color = "red") +
  geom_hline(yintercept = 10, linetype = "dashed", color = "orange") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Решение проблемы - удаление коррелирующих признаков
df_corr_reduced <- df_corr[, !names(df_corr) %in% c("X3")]  # Удаляем X3
model_reduced <- lm(Y ~ X1 + X2 + X4, data = df_corr_reduced)
vif_reduced <- vif(model_reduced)

cat("\nVIF после удаления коррелирующего признака X3:\n")
## 
## VIF после удаления коррелирующего признака X3:
print(round(vif_reduced, 2))
##   X1   X2   X4 
## 9.88 9.87 1.00

Вывод: Мультиколлинеарность возникает при сильной корреляции между признаками. VIF (Variance Inflation Factor) > 5-10 указывает на проблему. Решение: удаление коррелирующих признаков или использование регуляризации (Ridge, Lasso).

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

  1. Обнаружение пропусков: is.na() и complete.cases() - базовые инструменты для выявления пропущенных значений.

  2. Заполнение пропусков: Методы включают удаление, замену на среднее/медиану (preProcess), множественную импутацию (mice).

  3. Выбросы: Boxplot и IQR метод эффективно выявляют и удаляют аномальные значения.

  4. Дубликаты: unique() и duplicated() удаляют повторяющиеся строки с разной степенью контроля.

  5. Мультиколлинеарность: VIF анализ помогает выявить сильные корреляции между признаками для улучшения моделей.

  6. Практические выводы: Комплексная очистка данных - критически важный этап перед любым анализом или моделированием. ```