library(caret)
models <- names(getModelInfo())
length(models)
## [1] 239
head(models, 40)
## [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" "cforest" "chaid"
## [33] "CSimca" "ctree" "ctree2" "cubist"
## [37] "dda" "deepboost" "DENFIS" "dnn"
Методы выбора признаков в caret реализованы функциями
rfe() (рекурсивное исключение признаков),
sbf() (одномерный отбор, selection by filtering),
gafs() (генетический алгоритм) и safs()
(имитация отжига). Кроме того, у многих моделей есть встроенная оценка
важности признаков (varImp()). Модели, в метках которых
упоминается выбор признаков:
info <- getModelInfo()
fs <- sapply(info, function(m) any(grepl("Feature Selection|Implicit Feature", m$tags)))
names(info)[fs]
## [1] "ada" "AdaBag" "AdaBoost.M1" "adaboost"
## [5] "bagEarth" "bagEarthGCV" "bagFDA" "bagFDAGCV"
## [9] "bartMachine" "blasso" "BstLm" "bstSm"
## [13] "C5.0" "C5.0Cost" "C5.0Rules" "C5.0Tree"
## [17] "cforest" "chaid" "ctree" "ctree2"
## [21] "cubist" "deepboost" "earth" "enet"
## [25] "evtree" "extraTrees" "fda" "foba"
## [29] "gamboost" "gbm_h2o" "gbm" "gcvEarth"
## [33] "glmnet_h2o" "glmnet" "glmStepAIC" "J48"
## [37] "JRip" "lars" "lars2" "lasso"
## [41] "leapBackward" "leapForward" "leapSeq" "lmStepAIC"
## [45] "LMT" "LogitBoost" "M5" "M5Rules"
## [49] "msaenet" "nodeHarvest" "OneR" "ordinalNet"
## [53] "ordinalRF" "ORFlog" "ORFpls" "ORFridge"
## [57] "ORFsvm" "pam" "parRF" "PART"
## [61] "penalized" "PenalizedLDA" "qrf" "ranger"
## [65] "Rborist" "relaxo" "rf" "rFerns"
## [69] "rfRules" "rotationForest" "rotationForestCp" "rpart"
## [73] "rpart1SE" "rpart2" "rpartCost" "rpartScore"
## [77] "rqlasso" "rqnc" "RRF" "RRFglobal"
## [81] "sdwd" "smda" "sparseLDA" "spikeslab"
## [85] "stepLDA" "stepQDA" "wsrf" "xgbDART"
## [89] "xgbLinear" "xgbTree"
Данные из справочного файла функции featurePlot():
set.seed(2026)
x <- matrix(rnorm(50 * 5), ncol = 5)
y <- factor(rep(c("A", "B"), 25))
colnames(x) <- paste0("X", 1:5)
summary(x)
## X1 X2 X3 X4
## Min. :-2.54688 Min. :-1.9585 Min. :-1.7388 Min. :-1.8588
## 1st Qu.:-0.71448 1st Qu.:-0.9731 1st Qu.:-0.6693 1st Qu.:-0.5812
## Median : 0.05318 Median :-0.2460 Median : 0.2343 Median : 0.1027
## Mean :-0.01627 Mean :-0.1798 Mean : 0.1264 Mean : 0.1166
## 3rd Qu.: 0.61068 3rd Qu.: 0.4479 3rd Qu.: 0.8299 3rd Qu.: 0.6114
## Max. : 1.90808 Max. : 2.4144 Max. : 2.6387 Max. : 2.1805
## X5
## Min. :-2.53523
## 1st Qu.:-0.51553
## Median : 0.09044
## Mean : 0.10325
## 3rd Qu.: 0.72517
## Max. : 2.16165
table(y)
## y
## A B
## 25 25
types <- c("pairs", "ellipse", "density", "box", "strip")
for (tp in types) {
args <- list(x = x, y = y, plot = tp, auto.key = list(columns = 2),
main = paste0("featurePlot, plot = \"", tp, "\""))
if (tp %in% c("density", "box", "strip"))
args$scales <- list(x = list(relation = "free"), y = list(relation = "free"))
p <- do.call(featurePlot, args)
print(p)
jpeg(paste0("featurePlot_", tp, ".jpg"), width = 900, height = 700, quality = 95)
print(p)
dev.off()
}
list.files(pattern = "\\.jpg$")
## [1] "featurePlot_box.jpg" "featurePlot_density.jpg"
## [3] "featurePlot_ellipse.jpg" "featurePlot_pairs.jpg"
## [5] "featurePlot_strip.jpg"
# средние и p-значения t-теста по каждому признаку для двух классов
sapply(as.data.frame(x), function(v) c(mean_A = mean(v[y == "A"]), mean_B = mean(v[y == "B"]),
p_value = t.test(v ~ y)$p.value))
## X1 X2 X3 X4 X5
## mean_A -0.1761587 -0.48520428 0.1169766 0.1224162 -0.07499316
## mean_B 0.1436219 0.12555791 0.1358479 0.1107636 0.28148635
## p_value 0.2508621 0.03515359 0.9505285 0.9622096 0.20060046
Выводы. В caret доступно 239 моделей. Для выбора
признаков служат обёртки rfe(), sbf(),
gafs(), safs(), а многие модели (деревья,
бустинг, lasso/glmnet, MARS и др.) выполняют неявный отбор признаков,
что видно по их меткам. Графики featurePlot() сохранены в
пять файлов *.jpg. На всех графиках (матрица рассеяния, эллипсы,
плотности, boxplot, strip) классы A и B практически полностью
перекрываются: эллипсы совпадают, кривые плотности почти одинаковы,
медианы на boxplot близки. Это ожидаемо, потому что x – независимый
нормальный шум, никак не связанный с меткой y. Небольшое различие
средних по X2 (p = 0,035) случайно: при пяти проверках на уровне 0,05
одно ложное «значимое» различие вполне вероятно. Разведочный анализ
показывает, что ни один признак не разделяет классы.
library(FSelector)
data(iris)
w_ig <- information.gain(Species ~ ., iris)
w_gr <- gain.ratio(Species ~ ., iris)
w_su <- symmetrical.uncertainty(Species ~ ., iris)
w_chi <- chi.squared(Species ~ ., iris)
w_one <- oneR(Species ~ ., iris)
set.seed(2026)
w_rf <- random.forest.importance(Species ~ ., iris, importance.type = 1)
weights <- data.frame(information.gain = w_ig$attr_importance,
gain.ratio = w_gr$attr_importance,
symmetrical.uncertainty = w_su$attr_importance,
chi.squared = w_chi$attr_importance,
oneR = w_one$attr_importance,
random.forest = w_rf$attr_importance,
row.names = rownames(w_ig))
round(weights, 3)
## information.gain gain.ratio symmetrical.uncertainty chi.squared
## Sepal.Length 0.452 0.420 0.416 0.629
## Sepal.Width 0.267 0.247 0.245 0.492
## Petal.Length 0.940 0.858 0.857 0.935
## Petal.Width 0.955 0.871 0.871 0.943
## oneR random.forest
## Sepal.Length 0.173 14.984
## Sepal.Width 0.040 7.955
## Petal.Length 0.400 48.026
## Petal.Width 0.407 46.975
cutoff.k(w_ig, 2)
## [1] "Petal.Width" "Petal.Length"
f <- as.simple.formula(cutoff.k(w_ig, 2), "Species")
f
## Species ~ Petal.Width + Petal.Length
## <environment: 0x000002363b448738>
subset_cfs <- cfs(Species ~ ., iris)
subset_cfs
## [1] "Petal.Length" "Petal.Width"
op <- par(mar = c(5, 9, 3, 1))
barplot(sort(setNames(w_ig$attr_importance, rownames(w_ig))), horiz = TRUE, las = 1,
col = "steelblue", main = "Information gain признаков iris", xlab = "information gain")
par(op)
Выводы. Все критерии FSelector дают одинаковый
порядок важности: Petal.Width и Petal.Length (information gain 0,955 и
0,940) намного важнее, чем Sepal.Length (0,452) и особенно Sepal.Width
(0,267). Важность по случайному лесу у лепестковых признаков (около
47–48) тоже в 3–6 раз выше, чем у чашелистиков. Функция
cutoff.k() оставляет два лучших признака, по ним построена
формула Species ~ Petal.Width + Petal.Length; метод CFS
(корреляционный отбор подмножества) выбирает те же два признака. Значит,
для классификации ирисов достаточно размеров лепестка, признаки
чашелистика добавляют мало информации.
Дискретизируем длину лепестка Petal.Length набора iris
на 3 категории четырьмя способами.
library(arules)
x3 <- iris$Petal.Length
d_int <- discretize(x3, method = "interval", breaks = 3)
d_freq <- discretize(x3, method = "frequency", breaks = 3)
set.seed(2026)
d_clus <- discretize(x3, method = "cluster", breaks = 3)
d_fix <- discretize(x3, method = "fixed", breaks = c(-Inf, 2, 5, Inf),
labels = c("small", "medium", "large"))
table(d_int)
## d_int
## [1,2.97) [2.97,4.93) [4.93,6.9]
## 50 54 46
table(d_freq)
## d_freq
## [1,2.63) [2.63,4.9) [4.9,6.9]
## 50 49 51
table(d_clus)
## d_clus
## [1,2.85) [2.85,4.89) [4.89,6.9]
## 50 49 51
table(d_fix)
## d_fix
## small medium large
## 50 54 46
par(mfrow = c(2, 2))
show_cuts <- function(cuts, main) {
hist(x3, breaks = 30, col = "grey85", main = main, xlab = "Petal.Length")
abline(v = cuts, col = "red", lwd = 2)
}
show_cuts(discretize(x3, "interval", breaks = 3, onlycuts = TRUE), "interval (равная ширина)")
show_cuts(discretize(x3, "frequency", breaks = 3, onlycuts = TRUE), "frequency (равная частота)")
set.seed(2026)
show_cuts(discretize(x3, "cluster", breaks = 3, onlycuts = TRUE), "cluster (k-means)")
show_cuts(c(2, 5), "fixed (границы 2 и 5)")
par(mfrow = c(1, 1))
# насколько категории совпадают с видами ирисов
table(interval = d_int, Species = iris$Species)
## Species
## interval setosa versicolor virginica
## [1,2.97) 50 0 0
## [2.97,4.93) 0 48 6
## [4.93,6.9] 0 2 44
table(frequency = d_freq, Species = iris$Species)
## Species
## frequency setosa versicolor virginica
## [1,2.63) 50 0 0
## [2.63,4.9) 0 46 3
## [4.9,6.9] 0 4 47
table(cluster = d_clus, Species = iris$Species)
## Species
## cluster setosa versicolor virginica
## [1,2.85) 50 0 0
## [2.85,4.89) 0 46 3
## [4.89,6.9] 0 4 47
table(fixed = d_fix, Species = iris$Species)
## Species
## fixed setosa versicolor virginica
## small 50 0 0
## medium 0 48 6
## large 0 2 44
Выводы. Методы дают разные границы. «interval» делит диапазон 1–6,9 на три равных отрезка (границы 2,97 и 4,93), размеры групп 50/54/46. «frequency» подбирает границы 2,63 и 4,9 так, чтобы группы были почти равны (50/49/51). «cluster» (k-means) ставит границы 2,85 и 4,89 – в местах естественных разрывов распределения. «fixed» использует заданные вручную границы 2 и 5 и метки small/medium/large. Все методы полностью отделяют setosa (короткие лепестки); между versicolor и virginica ошибок 8 у «interval» и «fixed» и 7 у «frequency» и «cluster». Для этих данных лучше всего подходит кластерный метод: он сам находит естественные границы и не требует знаний о предметной области, как «fixed».
library(Boruta)
library(mlbench)
data("Ozone")
str(Ozone)
## 'data.frame': 366 obs. of 13 variables:
## $ V1 : Factor w/ 12 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ V2 : Factor w/ 31 levels "1","2","3","4",..: 1 2 3 4 5 6 7 8 9 10 ...
## $ V3 : Factor w/ 7 levels "1","2","3","4",..: 4 5 6 7 1 2 3 4 5 6 ...
## $ V4 : num 3 3 3 5 5 6 4 4 6 7 ...
## $ V5 : num 5480 5660 5710 5700 5760 5720 5790 5790 5700 5700 ...
## $ V6 : num 8 6 4 3 3 4 6 3 3 3 ...
## $ V7 : num 20 NA 28 37 51 69 19 25 73 59 ...
## $ V8 : num NA 38 40 45 54 35 45 55 41 44 ...
## $ V9 : num NA NA NA NA 45.3 ...
## $ V10: num 5000 NA 2693 590 1450 ...
## $ V11: num -15 -14 -25 -24 25 15 -33 -28 23 -2 ...
## $ V12: num 30.6 NA 47.7 55 57 ...
## $ V13: num 200 300 250 100 60 60 100 250 120 120 ...
ozo <- na.omit(Ozone)
dim(ozo)
## [1] 203 13
Целевая переменная V4 – суточный максимум концентрации озона, остальные 12 переменных – дата и метеоусловия.
set.seed(2026)
bor <- Boruta(V4 ~ ., data = ozo, doTrace = 0, maxRuns = 200)
print(bor)
## Boruta performed 18 iterations in 0.251807 secs.
## 9 attributes confirmed important: V1, V10, V11, V12, V13 and 4 more;
## 3 attributes confirmed unimportant: V2, V3, V6;
attStats(bor)
## meanImp medianImp minImp maxImp normHits decision
## V1 0.41337539 0.416004165 0.36060255 0.470450131 1.0000000 Confirmed
## V2 0.03219496 0.040810466 -0.05875336 0.087046950 0.1111111 Rejected
## V3 -0.04665987 -0.061703721 -0.09973038 0.008714154 0.0000000 Rejected
## V5 0.38597412 0.382870890 0.33205323 0.461353299 1.0000000 Confirmed
## V6 0.02058259 0.008694503 -0.04209242 0.120562317 0.0000000 Rejected
## V7 0.52198740 0.530254014 0.44189037 0.582055898 1.0000000 Confirmed
## V8 0.77954594 0.785294292 0.71113219 0.823817007 1.0000000 Confirmed
## V9 0.87277542 0.878108663 0.81552488 0.911687816 1.0000000 Confirmed
## V10 0.44083218 0.435506462 0.40015006 0.492082055 1.0000000 Confirmed
## V11 0.54475545 0.555525210 0.46111126 0.629740545 1.0000000 Confirmed
## V12 0.65540911 0.657749782 0.60911228 0.718395547 1.0000000 Confirmed
## V13 0.40269911 0.399089240 0.35516470 0.470107317 1.0000000 Confirmed
par(mar = c(8, 4, 3, 1))
plot(bor, las = 2, xlab = "", cex.axis = 0.9, main = "Boruta: важность признаков набора Ozone")
bor_fix <- TentativeRoughFix(bor)
print(bor_fix)
## Boruta performed 18 iterations in 0.251807 secs.
## 9 attributes confirmed important: V1, V10, V11, V12, V13 and 4 more;
## 3 attributes confirmed unimportant: V2, V3, V6;
getSelectedAttributes(bor_fix)
## [1] "V1" "V5" "V7" "V8" "V9" "V10" "V11" "V12" "V13"
getConfirmedFormula(bor_fix)
## V4 ~ V1 + V5 + V7 + V8 + V9 + V10 + V11 + V12 + V13
## <environment: 0x0000023633a8cd28>
plotImpHistory(bor, main = "История важности признаков по итерациям")
Выводы. В наборе Ozone 366 наблюдений, после
удаления пропусков осталось 203. Boruta за 18 итераций принял решение по
всем 12 признакам: 9 подтверждены как важные (V1 – месяц, V5 – высота
изобарической поверхности 500 мбар, V7 – влажность, V8 и V9 –
температура в Сандбурге и Эль-Монте, V10 – высота и V12 – температура
инверсионного слоя, V11 – градиент давления, V13 – видимость), 3
отклонены (V2 – день месяца, V3 – день недели, V6 – скорость ветра). На
boxplot зелёным показаны подтверждённые признаки, красным – отклонённые,
синим – «теневые» признаки (перемешанные копии): отклонённые признаки не
превосходят по важности shadowMax. Самые важные – температуры V9 и V8 и
температура инверсии V12, то есть концентрация озона определяется в
первую очередь нагревом воздуха. Признаков со статусом Tentative нет,
поэтому TentativeRoughFix() результат не меняет.
В работе использованы четыре подхода к анализу и отбору признаков в
R: графический разведочный анализ (caret::featurePlot),
фильтры на основе теории информации и статистических критериев
(FSelector), дискретизация непрерывных переменных (arules) и обёрточный
метод на основе случайного леса (Boruta). Фильтры FSelector быстро
ранжируют признаки, Boruta надёжнее, так как сравнивает каждый признак
со случайными «теневыми» копиями, а графики featurePlot помогают ещё до
моделирования увидеть, разделяют ли признаки классы.
sessionInfo()
## R version 4.6.1 (2026-06-24 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26300)
##
## Matrix products: default
## LAPACK version 3.12.1
##
## locale:
## [1] LC_COLLATE=Russian_Russia.utf8 LC_CTYPE=Russian_Russia.utf8
## [3] LC_MONETARY=Russian_Russia.utf8 LC_NUMERIC=C
## [5] LC_TIME=Russian_Russia.utf8
##
## time zone: Europe/Moscow
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] mlbench_2.1-11 Boruta_10.0.0 arules_1.7.15 Matrix_1.7-5 FSelector_0.34
## [6] caret_7.0-1 lattice_0.22-9 ggplot2_4.0.3
##
## loaded via a namespace (and not attached):
## [1] ellipse_0.5.0 gtable_0.3.6 xfun_0.61
## [4] bslib_0.12.0 recipes_1.4.0 rJava_1.0-18
## [7] vctrs_0.7.3 tools_4.6.1 generics_0.1.4
## [10] stats4_4.6.1 parallel_4.6.1 fru_1.0.0
## [13] tibble_3.3.1 pkgconfig_2.0.3 ModelMetrics_1.2.2.2
## [16] data.table_1.18.6.1 RColorBrewer_1.1-3 S7_0.2.2
## [19] lifecycle_1.0.5 compiler_4.6.1 farver_2.1.2
## [22] stringr_1.6.0 codetools_0.2-20 htmltools_0.5.9
## [25] class_7.3-23 sass_0.4.10 yaml_2.3.12
## [28] prodlim_2026.03.11 pillar_1.11.1 jquerylib_0.1.4
## [31] MASS_7.3-65 entropy_1.3.2 cachem_1.1.0
## [34] gower_1.0.2 iterators_1.0.14 rpart_4.1.27
## [37] foreach_1.5.2 nlme_3.1-169 parallelly_1.48.0
## [40] lava_1.9.3 tidyselect_1.2.1 digest_0.6.39
## [43] stringi_1.8.9 future_1.80.0 dplyr_1.2.1
## [46] reshape2_1.4.5 purrr_1.2.2 listenv_1.1.0
## [49] RWekajars_3.9.3-2 splines_4.6.1 fastmap_1.2.0
## [52] grid_4.6.1 cli_3.6.6 magrittr_2.0.5
## [55] randomForest_4.7-1.2 survival_3.8-6 future.apply_1.21.0
## [58] withr_3.0.3 scales_1.4.0 lubridate_1.9.5
## [61] timechange_0.4.0 rmarkdown_2.32 globals_0.20.0
## [64] otel_0.2.0 nnet_7.3-20 RWeka_0.4-50
## [67] timeDate_4052.112 evaluate_1.0.5 knitr_1.52
## [70] hardhat_1.4.3 rlang_1.3.0 Rcpp_1.1.2
## [73] glue_1.8.1 pROC_1.19.1 ipred_0.9-16
## [76] jsonlite_2.0.0 R6_2.6.1 plyr_1.8.9