Примечание: пакет FSelector требует установленной Java (JRE) и пакета rJava. Если при library(FSelector) возникает ошибка про Java — установите Java с https://www.java.com, затем в консоли R выполните install.packages("rJava") и library(rJava) перед library(FSelector).

1 Задание 1. Пакет caret: обзор моделей и featurePlot()

1.1 Список доступных моделей/методов caret

model_names <- names(getModelInfo())
length(model_names)
## [1] 239
head(model_names, 30)
##  [1] "ada"            "AdaBag"         "AdaBoost.M1"    "adaboost"      
##  [5] "amdai"          "ANFIS"          "avNNet"         "awnb"          
##  [9] "awtan"          "bag"            "bagEarth"       "bagEarthGCV"   
## [13] "bagFDA"         "bagFDAGCV"      "bam"            "bartMachine"   
## [17] "bayesglm"       "binda"          "blackboost"     "blasso"        
## [21] "blassoAveraged" "bridge"         "brnn"           "BstLm"         
## [25] "bstSm"          "bstTree"        "C5.0"           "C5.0Cost"      
## [29] "C5.0Rules"      "C5.0Tree"

Функция getModelInfo() возвращает полный список моделей (методов), которые поддерживает caret для обучения (train). Отдельно в caret есть функции именно для отбора признаков: rfe() (Recursive Feature Elimination), sbf() (Selection By Filtering), а также varImp() для оценки важности признаков уже обученной модели.

1.2 Графический разведочный анализ: featurePlot()

set.seed(1)
x <- matrix(rnorm(50 * 5), ncol = 5)
colnames(x) <- paste0("V", 1:5)
y <- factor(rep(c("A", "B"), 25))
# 1. Boxplot по каждому признаку в разрезе класса
jpeg("featurePlot_box.jpg", width = 1000, height = 700, quality = 100)
featurePlot(x = x, y = y, plot = "box",
            scales = list(y = list(relation = "free")),
            main = "Boxplot признаков по классам")
dev.off()
## quartz_off_screen 
##                 2
# 2. Плотности распределения признаков
jpeg("featurePlot_density.jpg", width = 1000, height = 700, quality = 100)
featurePlot(x = x, y = y, plot = "density",
            scales = list(x = list(relation = "free"), y = list(relation = "free")),
            auto.key = list(columns = 2))
dev.off()
## quartz_off_screen 
##                 2
# 3. Матрица рассеяния (pairs)
jpeg("featurePlot_pairs.jpg", width = 1000, height = 1000, quality = 100)
featurePlot(x = x, y = y, plot = "pairs", auto.key = list(columns = 2))
dev.off()
## quartz_off_screen 
##                 2
# Показать графики и в самом отчёте
featurePlot(x = x, y = y, plot = "box", scales = list(y = list(relation = "free")))

featurePlot(x = x, y = y, plot = "density", scales = list(x = list(relation = "free"), y = list(relation = "free")))

featurePlot(x = x, y = y, plot = "pairs")

Вывод: данные x сгенерированы функцией rnorm(), то есть представляют собой случайный шум, никак не связанный с меткой класса y (метки чередуются регулярно и не зависят от значений x). Поэтому на всех трёх графиках (boxplot, density, pairs) распределения признаков для классов A и B практически совпадают — визуально разделить классы по этим пяти признакам невозможно. Это ожидаемый результат для случайно сгенерированных данных и иллюстрирует, что featurePlot() полезен именно для того, чтобы на глаз отличить информативные признаки (с разделяющимися распределениями по классам) от неинформативных (с совпадающими распределениями), как в этом примере.

2 Задание 2. FSelector: важность признаков (data(iris))

data(iris)

# Оценка важности признаков разными методами
ig  <- information.gain(Species ~ ., data = iris)
gr  <- gain.ratio(Species ~ ., data = iris)
chi <- chi.squared(Species ~ ., data = iris)

print(ig)
##              attr_importance
## Sepal.Length       0.4521286
## Sepal.Width        0.2672750
## Petal.Length       0.9402853
## Petal.Width        0.9554360
print(gr)
##              attr_importance
## Sepal.Length       0.4196464
## Sepal.Width        0.2472972
## Petal.Length       0.8584937
## Petal.Width        0.8713692
print(chi)
##              attr_importance
## Sepal.Length       0.6288067
## Sepal.Width        0.4922162
## Petal.Length       0.9346311
## Petal.Width        0.9432359
# Отбор двух самых важных признаков по information gain
best2 <- cutoff.k(ig, 2)
best2
## [1] "Petal.Width"  "Petal.Length"

Вывод: все три метода (information gain, gain ratio, chi-squared) согласованно показывают, что наиболее информативными признаками для классификации вида ириса (Species) являются Petal.Length и Petal.Width — у них наибольшие значения важности. Признаки Sepal.Length и особенно Sepal.Width вносят заметно меньший вклад в разделение классов. Это согласуется с общеизвестным фактом об этом наборе данных: длина и ширина лепестка почти линейно разделяют виды ирисов, тогда как параметры чашелистика (sepal) более “перемешаны” между видами.

3 Задание 3. Дискретизация непрерывного признака (пакет arules)

library(arules)
data(iris)

x_cont <- iris$Sepal.Length

d_interval  <- discretize(x_cont, method = "interval",  breaks = 3)
d_frequency <- discretize(x_cont, method = "frequency", breaks = 3)
d_cluster   <- discretize(x_cont, method = "cluster",   breaks = 3)
d_fixed     <- discretize(x_cont, method = "fixed",
                           breaks = c(-Inf, 5.5, 6.5, Inf),
                           labels = c("low", "medium", "high"))

table(d_interval)
## d_interval
## [4.3,5.5) [5.5,6.7) [6.7,7.9] 
##        52        70        28
table(d_frequency)
## d_frequency
## [4.3,5.4) [5.4,6.3) [6.3,7.9] 
##        46        53        51
table(d_cluster)
## d_cluster
##  [4.3,5.53) [5.53,6.55)  [6.55,7.9] 
##          59          61          30
table(d_fixed)
## d_fixed
##    low medium   high 
##     52     63     35
par(mfrow = c(2, 2))
plot(table(d_interval),  main = "interval",  col = "steelblue")
plot(table(d_frequency), main = "frequency", col = "darkorange")
plot(table(d_cluster),   main = "cluster",   col = "forestgreen")
plot(table(d_fixed),     main = "fixed",     col = "tomato")

par(mfrow = c(1, 1))

Вывод: метод interval делит диапазон значений Sepal.Length на интервалы одинаковой ширины, из-за чего группы получаются неравномерными по числу наблюдений. Метод frequency строит интервалы так, чтобы в каждой группе было примерно одинаковое число наблюдений, но ширина интервалов при этом разная. Метод cluster использует кластеризацию (k-means) для поиска естественных группировок значений — границы интервалов подстраиваются под плотность данных. Метод fixed использует заранее заданные вручную границы, что удобно, когда пороги имеют содержательный смысл (например, агрономические нормы), но требует экспертного знания предметной области для выбора этих границ. Выбор метода дискретизации существенно влияет на дальнейший результат анализа (например, для алгоритмов ассоциативных правил).

4 Задание 4 (в задании ошибочно тоже под номером 3). Boruta: отбор признаков (data(“Ozone”))

library(Boruta)
library(mlbench)

data("Ozone", package = "mlbench")
Ozone <- na.omit(Ozone)          # Boruta не работает с пропусками
colnames(Ozone) <- paste0("V", 1:ncol(Ozone))  # у Ozone нет "говорящих" имён столбцов

set.seed(1)
boruta_result <- Boruta(V4 ~ ., data = Ozone, doTrace = 0)
print(boruta_result)
## Boruta performed 24 iterations in 0.463366 secs.
##  9 attributes confirmed important: V1, V10, V11, V12, V13 and 4 more;
##  3 attributes confirmed unimportant: V2, V3, V6;
# Итоговое решение по каждому признаку
final_decision <- TentativeRoughFix(boruta_result)
print(final_decision)
## Boruta performed 24 iterations in 0.463366 secs.
##  9 attributes confirmed important: V1, V10, V11, V12, V13 and 4 more;
##  3 attributes confirmed unimportant: V2, V3, V6;
attStats(boruta_result)
##         meanImp   medianImp      minImp      maxImp  normHits  decision
## V1   0.41193902  0.40874574  0.34596313  0.47748700 1.0000000 Confirmed
## V2   0.04400055  0.03685704 -0.04866888  0.13111930 0.1666667  Rejected
## V3  -0.07047045 -0.07645198 -0.13945444 -0.02468737 0.0000000  Rejected
## V5   0.39047635  0.38898201  0.31980785  0.45176242 1.0000000 Confirmed
## V6   0.01227390  0.01008201 -0.05960559  0.08967574 0.0000000  Rejected
## V7   0.52451495  0.52579776  0.45444201  0.57880602 1.0000000 Confirmed
## V8   0.77608962  0.78131286  0.71917639  0.83519238 1.0000000 Confirmed
## V9   0.87502711  0.86707260  0.83054602  0.96504062 1.0000000 Confirmed
## V10  0.43160816  0.42959427  0.37462118  0.50877226 1.0000000 Confirmed
## V11  0.54131356  0.54091540  0.48309934  0.60971909 1.0000000 Confirmed
## V12  0.65892496  0.65796083  0.58947681  0.71773702 1.0000000 Confirmed
## V13  0.40542650  0.39918574  0.35147274  0.45302205 1.0000000 Confirmed
jpeg("boruta_boxplot.jpg", width = 1200, height = 700, quality = 100)
plot(boruta_result, las = 2, cex.axis = 0.7,
     main = "Важность признаков по Boruta (Ozone)")
dev.off()
## quartz_off_screen 
##                 2
plot(boruta_result, las = 2, cex.axis = 0.7,
     main = "Важность признаков по Boruta (Ozone)")

Вывод: Boruta сравнивает важность каждого реального признака с важностью его “теневых” случайных копий (shadow features) и по результатам множества итераций случайного леса помечает признаки как Confirmed (значимые), Rejected (незначимые) или Tentative (неопределённые). На boxplot-графике зелёным обычно выделяются подтверждённые признаки, красным — отклонённые, жёлтым — неопределённые, синим — статистика теневых признаков (min/mean/max), которая служит порогом сравнения. В наборе Ozone (целевая переменная — среднесуточная концентрация озона, V4) наиболее значимыми, как правило, оказываются признаки, связанные с температурой и давлением (например, V8, V9, V10 — температура на разных станциях), тогда как признаки вроде дня недели/месяца часто попадают в Rejected или Tentative, поскольку не несут прямой физической связи с уровнем озона.

5 Общий вывод

В ходе работы были опробованы четыре разных подхода к разведочному анализу и отбору признаков:

  1. caret::featurePlot() — визуальная (графическая) оценка того, различимы ли классы по отдельным признакам.
  2. FSelector (information gain, gain ratio, chi-squared) — количественная оценка важности признаков на основе теории информации/статистики.
  3. arules::discretize() — преобразование непрерывного признака в категориальный четырьмя разными способами, что нужно, например, для последующего применения алгоритмов ассоциативных правил или деревьев решений, требующих категориальных предикторов.
  4. Boruta — обёрточный (wrapper) метод отбора признаков на основе случайного леса, статистически строго разделяющий признаки на значимые и незначимые.

Совместное использование этих методов позволяет как визуально, так и статистически обоснованно определить, какие признаки действительно важны для решаемой задачи, а какие можно исключить без потери качества модели.