В пакете caret доступно множество методов. Функция
names(getModelInfo()) выводит список всех моделей, многие
из которых могут быть использованы для оценки важности признаков.
# Получаем список всех доступных методов
all_models <- names(getModelInfo())
# Выведем первые 20 для ознакомления
cat("Всего доступно методов:", length(all_models), "\n")
## Всего доступно методов: 239
cat("Первые 20 методов:", paste(head(all_models, 20), collapse = ", "))
## Первые 20 методов: ada, AdaBag, AdaBoost.M1, adaboost, amdai, ANFIS, avNNet, awnb, awtan, bag, bagEarth, bagEarthGCV, bagFDA, bagFDAGCV, bam, bartMachine, bayesglm, binda, blackboost, blasso
featurePlot()Создадим синтетический набор данных и визуализируем взаимосвязь признаков с целевой переменной.
# Генерация данных
set.seed(123)
x <- matrix(rnorm(50*5), ncol=5)
colnames(x) <- paste0("Feature", 1:5)
y <- factor(rep(c("A", "B"), 25))
# Объединяем в data.frame для featurePlot
data_caret <- as.data.frame(x)
data_caret$Class <- y
# Создаем точечные графики (scatter plots)
featurePlot(x = data_caret[,1:5], y = data_caret$Class, plot = "scatter",
main = "Scatter Plots: Признаки по классам")
## NULL
# Создаем ящики с усами (box plots)
featurePlot(x = data_caret[,1:5], y = data_caret$Class, plot = "box",
main = "Box Plots: Распределение признаков по классам")
# Создаем графики плотности (density plots)
featurePlot(x = data_caret[,1:5], y = data_caret$Class, plot = "density",
auto.key = list(columns = 2),
main = "Density Plots: Плотность распределения признаков по классам")
**Вывод по визуализации:**
* **Scatter plot:** На точечных графиках пары признаков точки классов "A" и "B" сильно перемешаны. Это говорит о том, что линейная комбинация любых двух признаков плохо разделяет классы, что ожидаемо для случайных данных.
* **Box plot:** Ящики с усами для классов "A" и "B" по каждому признаку имеют практически одинаковые медианы и интерквартильные размахи. Это указывает на то, что каждый признак по отдельности не обладает дискриминативной силой.
* **Density plot:** Графики плотности для обоих классов практически полностью перекрываются, подтверждая вывод, сделанный из boxplot'ов. Ни один из признаков не позволяет надежно отличить класс "A" от класса "B".
## 2. Пакет Fselector: Оценка важности признаков (iris)
Пакет `FSelector` предлагает простые фильтры для отбора признаков. Используем данные `iris` для задачи классификации вида цветов.
``` r
data(iris)
# 1. Метод "information.gain" (прирост информации)
# Оценивает, сколько информации о целевой переменной дает каждый признак.
ig_weights <- information.gain(Species ~ ., iris)
print("Information Gain:")
## [1] "Information Gain:"
print(ig_weights)
## attr_importance
## Sepal.Length 0.4521286
## Sepal.Width 0.2672750
## Petal.Length 0.9402853
## Petal.Width 0.9554360
# 2. Метод "chi.squared" (Хи-квадрат)
# Оценивает статистическую зависимость между признаком и классом.
chi_weights <- chi.squared(Species ~ ., iris)
print("Chi-squared:")
## [1] "Chi-squared:"
print(chi_weights)
## attr_importance
## Sepal.Length 0.6288067
## Sepal.Width 0.4922162
## Petal.Length 0.9346311
## Petal.Width 0.9432359
# 3. Метод "gain.ratio" (коэффициент прироста)
# Нормализованная версия Information Gain.
gr_weights <- gain.ratio(Species ~ ., iris)
print("Gain Ratio:")
## [1] "Gain Ratio:"
print(gr_weights)
## attr_importance
## Sepal.Length 0.4196464
## Sepal.Width 0.2472972
## Petal.Length 0.8584937
## Petal.Width 0.8713692
# Ранжирование признаков по сумме полученных оценок (для наглядности)
# Можно составить рейтинг признаков, например, по Information Gain.
ranked_features <- cutoff.k(ig_weights, 4) # запрашиваем топ-4 признака (их всего 4)
cat("Рейтинг признаков (по Information Gain):", paste(ranked_features, collapse = " > "), "\n")
## Рейтинг признаков (по Information Gain): Petal.Width > Petal.Length > Sepal.Length > Sepal.Width
Вывод по Fselector: Все три метода
(information.gain, chi.squared,
gain.ratio) дают схожую картину. Наибольшую важность для
предсказания вида ириса имеют признаки Petal.Width и
Petal.Length. Признаки Sepal.Length и
Sepal.Width имеют значительно меньший вес. Это
согласуется с известными свойствами данных Iris: виды цветов лучше всего
различаются по параметрам лепестков (petal), а не чашелистиков
(sepal).
arules::discretize()Преобразуем непрерывные переменные в категориальные разными методами.
data(iris)
# Возьмем для примера признак Sepal.Length
sepal_length <- iris$Sepal.Length
# 1. Метод "interval" (равная ширина)
# Делит диапазон данных на интервалы одинаковой длины.
disc_interval <- discretize(sepal_length, method = "interval", categories = 3)
table(disc_interval)
## disc_interval
## [4.3,5.5) [5.5,6.7) [6.7,7.9]
## 52 70 28
# 2. Метод "frequency" (равная частота)
# Делит данные так, чтобы в каждом интервале было примерно одинаковое количество наблюдений.
disc_frequency <- discretize(sepal_length, method = "frequency", categories = 3)
table(disc_frequency)
## disc_frequency
## [4.3,5.4) [5.4,6.3) [6.3,7.9]
## 46 53 51
# 3. Метод "cluster" (кластеризация k-means)
# Использует k-means для нахождения естественных групп.
disc_cluster <- discretize(sepal_length, method = "cluster", categories = 3)
table(disc_cluster)
## disc_cluster
## [4.3,5.42) [5.42,6.39) [6.39,7.9]
## 52 56 42
# 4. Метод "fixed" (фиксированные границы)
# Задаем границы интервалов вручную.
disc_fixed <- discretize(sepal_length, method = "fixed",
breaks = c(4, 5, 6, 8), # границы: <4, [4-5), [5-6), >=6
labels = c("small", "medium", "large"))
table(disc_fixed)
## disc_fixed
## small medium large
## 22 61 67
# Визуализация для сравнения
par(mfrow = c(2,2))
plot(disc_interval, main = "Interval (Equal Width)", ylab = "Frequency")
plot(disc_frequency, main = "Frequency (Equal Frequency)", ylab = "Frequency")
plot(disc_cluster, main = "Cluster (k-means)", ylab = "Frequency")
plot(disc_fixed, main = "Fixed (Custom Breaks)", ylab = "Frequency")
par(mfrow = c(1,1))
Вывод по дискретизации: *
interval (равная ширина): Интервалы имеют
одинаковую длину (примерно 1.33). Количество объектов в них разное, так
как данные распределены неравномерно. * frequency
(равная частота): Количество объектов в каждом интервале
примерно одинаково (~50). Границы интервалов при этом подстраиваются под
квантили данных. * cluster
(кластеризация): Метод пытается найти естественные скопления
данных. Границы интервалов проходят в “разрывах” между кластерами. *
fixed (фиксированные): Позволяет задать
содержательные интервалы, например, на основе экспертных знаний
(small/medium/large).
Выбор метода зависит от задачи. interval прост для
интерпретации, frequency полезен, когда нужно
сбалансировать категории, cluster помогает выявить
внутреннюю структуру, а fixed — использовать предметные
знания.
Boruta — это мощный алгоритм, который находит все признаки, релевантные для целевой переменной.
data("Ozone", package = "mlbench")
# ?Ozone - справка по данным
# Удаляем пропущенные значения для простоты примера
ozone_data <- na.omit(Ozone)
# Целевая переменная - четвертый уровень озона? По справке, это V4?
# Но обычно в примерах используют V4 как целевую.
# В справке mlbench: V1-V3 - метеоданные, V4 - значение озона?
# Возьмем V1-V3 как признаки, а V4 - как целевую, преобразовав её в фактор для классификации.
# Создадим бинарную целевую переменную: высокий/низкий уровень озона (выше/ниже медианы)
set.seed(123)
ozone_df <- ozone_data[, c("V1", "V2", "V3", "V4")]
ozone_df$OzoneLevel <- as.factor(ifelse(ozone_df$V4 > median(ozone_df$V4), "High", "Low"))
ozone_df$V4 <- NULL # Убираем исходную переменную
# Запускаем Boruta
boruta_result <- Boruta(OzoneLevel ~ ., data = ozone_df, doTrace = 2) # doTrace для логов
print(boruta_result)
## Boruta performed 99 iterations in 3.263776 secs.
## 1 attributes confirmed important: V1;
## 1 attributes confirmed unimportant: V3;
## 1 tentative attributes left: V2;
# Принимаем решение по всем признакам
boruta_final <- TentativeRoughFix(boruta_result)
print(boruta_final)
## Boruta performed 99 iterations in 3.263776 secs.
## Tentatives roughfixed over the last 99 iterations.
## 2 attributes confirmed important: V1, V2;
## 1 attributes confirmed unimportant: V3;
# Строим boxplot важности признаков
plot(boruta_final, las = 2, cex.axis = 0.7, main = "Важность признаков (Boruta)")
Вывод по Boruta: Алгоритм Boruta сравнивает реальные признаки с их “теневыми” копиями (случайно перемешанными). Признаки, важность которых статистически значимо выше теневых, считаются Confirmed (подтвержденными). Те, что ниже — Rejected (отвергнутыми). Результат зависит от конкретной выборки и настроек.
В данном примере на графике видно, какие из метеорологических переменных (V1, V2, V3) действительно связаны с уровнем озона. (Обычно V1, V2, V3 все оказываются важными, но возможны вариации). Заключение: Boruta подтверждает релевантность всех или части исходных признаков для предсказания порогового уровня озона.
В ходе лабораторной работы были изучены и применены на практике
различные подходы к анализу и отбору признаков: 1. Визуализация
данных (caret::featurePlot) позволила быстро
оценить разделимость классов и выявить непригодность случайных данных
для классификации. 2. Фильтрующие методы
(Fselector) дали количественную оценку важности
каждого признака, ранжировав их по информативности для задачи
классификации ирисов. 3. Дискретизация
(arules::discretize) продемонстрировала способы
преобразования непрерывных переменных в категориальные, что может быть
полезно для некоторых моделей или для упрощения интерпретации. 4.
Оберточный метод (Boruta) был применен к
более сложной задаче (ozone), позволив идентифицировать набор признаков,
действительно релевантных для целевой переменной, с учетом
взаимодействий между ними.
Каждый из инструментов решает свои задачи на разных этапах анализа данных, от первичного ознакомления до финального построения модели. ```