1 Цель и содержание работы

Цель работы — выполнить графический разведочный анализ, сравнить оценки важности признаков для классификации, изучить способы дискретизации и применить алгоритм Boruta для поиска значимых признаков. Все таблицы и рисунки ниже получены выполнением кода R. Начальное состояние генератора случайных чисел задано через set.seed.

В работе используются три набора данных: случайные признаки двух классов, iris и Ozone из пакета mlbench. Для iris решается задача классификации вида растения. В Ozone целевая переменная V4 числовая, поэтому Boruta применяется к задаче регрессии.

2 Подготовка программной среды

Для повторного выполнения необходимы R и следующие пакеты. Пакет FSelector использует RWeka и rJava; требуется 64-разрядная Java, совместимая с установленной R. Для сборки HTML также требуется Pandoc (он поставляется вместе с RStudio).

install.packages(c("caret", "FSelector", "arules", "Boruta",
                   "mlbench", "randomForest", "ellipse", "rmarkdown", "knitr"))
if (.Platform$OS.type == "windows") Sys.setlocale("LC_CTYPE", ".UTF-8")
rmarkdown::render("lab2.Rmd", encoding="UTF-8")

3 Пакет CARET и графический разведочный анализ

3.1 Доступные методы

Выполнена команда, указанная в задании.

model_names <- names(getModelInfo())
length(model_names)
## [1] 239
model_names
##   [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"                
##  [41] "dwdLinear"           "dwdPoly"             "dwdRadial"           "earth"              
##  [45] "elm"                 "enet"                "evtree"              "extraTrees"         
##  [49] "fda"                 "FH.GBML"             "FIR.DM"              "foba"               
##  [53] "FRBCS.CHI"           "FRBCS.W"             "FS.HGD"              "gam"                
##  [57] "gamboost"            "gamLoess"            "gamSpline"           "gaussprLinear"      
##  [61] "gaussprPoly"         "gaussprRadial"       "gbm_h2o"             "gbm"                
##  [65] "gcvEarth"            "GFS.FR.MOGUL"        "GFS.LT.RS"           "GFS.THRIFT"         
##  [69] "glm.nb"              "glm"                 "glmboost"            "glmnet_h2o"         
##  [73] "glmnet"              "glmStepAIC"          "gpls"                "hda"                
##  [77] "hdda"                "hdrda"               "HYFIS"               "icr"                
##  [81] "J48"                 "JRip"                "kernelpls"           "kknn"               
##  [85] "knn"                 "krlsPoly"            "krlsRadial"          "lars"               
##  [89] "lars2"               "lasso"               "lda"                 "lda2"               
##  [93] "leapBackward"        "leapForward"         "leapSeq"             "Linda"              
##  [97] "lm"                  "lmStepAIC"           "LMT"                 "loclda"             
## [101] "logicBag"            "LogitBoost"          "logreg"              "lssvmLinear"        
## [105] "lssvmPoly"           "lssvmRadial"         "lvq"                 "M5"                 
## [109] "M5Rules"             "manb"                "mda"                 "Mlda"               
## [113] "mlp"                 "mlpKerasDecay"       "mlpKerasDecayCost"   "mlpKerasDropout"    
## [117] "mlpKerasDropoutCost" "mlpML"               "mlpSGD"              "mlpWeightDecay"     
## [121] "mlpWeightDecayML"    "monmlp"              "msaenet"             "multinom"           
## [125] "mxnet"               "mxnetAdam"           "naive_bayes"         "nb"                 
## [129] "nbDiscrete"          "nbSearch"            "neuralnet"           "nnet"               
## [133] "nnls"                "nodeHarvest"         "null"                "OneR"               
## [137] "ordinalNet"          "ordinalRF"           "ORFlog"              "ORFpls"             
## [141] "ORFridge"            "ORFsvm"              "ownn"                "pam"                
## [145] "parRF"               "PART"                "partDSA"             "pcaNNet"            
## [149] "pcr"                 "pda"                 "pda2"                "penalized"          
## [153] "PenalizedLDA"        "plr"                 "pls"                 "plsRglm"            
## [157] "polr"                "ppr"                 "pre"                 "PRIM"               
## [161] "protoclass"          "qda"                 "QdaCov"              "qrf"                
## [165] "qrnn"                "randomGLM"           "ranger"              "rbf"                
## [169] "rbfDDA"              "Rborist"             "rda"                 "regLogistic"        
## [173] "relaxo"              "rf"                  "rFerns"              "RFlda"              
## [177] "rfRules"             "ridge"               "rlda"                "rlm"                
## [181] "rmda"                "rocc"                "rotationForest"      "rotationForestCp"   
## [185] "rpart"               "rpart1SE"            "rpart2"              "rpartCost"          
## [189] "rpartScore"          "rqlasso"             "rqnc"                "RRF"                
## [193] "RRFglobal"           "rrlda"               "RSimca"              "rvmLinear"          
## [197] "rvmPoly"             "rvmRadial"           "SBC"                 "sda"                
## [201] "sdwd"                "simpls"              "SLAVE"               "slda"               
## [205] "smda"                "snn"                 "sparseLDA"           "spikeslab"          
## [209] "spls"                "stepLDA"             "stepQDA"             "superpc"            
## [213] "svmBoundrangeString" "svmExpoString"       "svmLinear"           "svmLinear2"         
## [217] "svmLinear3"          "svmLinearWeights"    "svmLinearWeights2"   "svmPoly"            
## [221] "svmRadial"           "svmRadialCost"       "svmRadialSigma"      "svmRadialWeights"   
## [225] "svmSpectrumString"   "tan"                 "tanSearch"           "treebag"            
## [229] "vbmpRadial"          "vglmAdjCat"          "vglmContRatio"       "vglmCumulative"     
## [233] "widekernelpls"       "WM"                  "wsrf"                "xgbDART"            
## [237] "xgbLinear"           "xgbTree"             "xyf"

В данной версии CARET доступно 239 описаний моделей. getModelInfo() возвращает сведения о методах обучения моделей, а не отдельный перечень алгоритмов отбора признаков. Для отбора признаков CARET также предоставляет rfe, sbf и модельные оценки varImp. Примеры в списке: rf — случайный лес, rpart — дерево решений, svmRadial — метод опорных векторов и glm — обобщённая линейная модель.

3.2 Исходные данные

set.seed(20261014)
x <- matrix(rnorm(50 * 5), ncol=5)
colnames(x) <- paste0("X", 1:5)
y <- factor(rep(c("A", "B"), 25))
dim(x)
## [1] 50  5
table(y)
## y
##  A  B 
## 25 25
summary(x)
##        X1                X2                  X3                 X4                X5          
##  Min.   :-1.6478   Min.   :-1.691880   Min.   :-3.30960   Min.   :-2.3553   Min.   :-3.02618  
##  1st Qu.:-0.7297   1st Qu.:-0.887315   1st Qu.:-0.47271   1st Qu.:-0.8309   1st Qu.:-0.78187  
##  Median :-0.1968   Median : 0.001592   Median : 0.22525   Median :-0.3323   Median : 0.07242  
##  Mean   :-0.1221   Mean   :-0.125107   Mean   : 0.04861   Mean   :-0.2615   Mean   :-0.11343  
##  3rd Qu.: 0.5399   3rd Qu.: 0.613635   3rd Qu.: 0.73305   3rd Qu.: 0.4550   3rd Qu.: 0.51617  
##  Max.   : 1.8898   Max.   : 1.786271   Max.   : 2.24731   Max.   : 1.3352   Max.   : 2.23997

Создано 50 наблюдений и пять непрерывных признаков. В каждом классе по 25 наблюдений. Метки классов заданы независимо от генерации признаков; систематическая связь между ними не заложена.

3.3 Построение и сохранение графиков JPG

save_feature_plot <- function(kind, file, ...) {
  jpeg(file, width=1800, height=1400, res=180, quality=95)
  on.exit(dev.off())
  result <- featurePlot(x=x, y=y, plot=kind, ...)
  if (inherits(result, "trellis") || inherits(result, "ggplot")) print(result)
  invisible(file)
}
save_feature_plot("box", "figures/caret_box.jpg")
save_feature_plot("density", "figures/caret_density.jpg",
                  auto.key=list(space="bottom", columns=2),
                  scales=list(x=list(relation="free"),
                              y=list(relation="free")))
save_feature_plot("pairs", "figures/caret_pairs.jpg",
                  auto.key=list(space="bottom", columns=2))
save_feature_plot("ellipse", "figures/caret_ellipse.jpg",
                  auto.key=list(space="bottom", columns=2))
Диаграммы размаха признаков по классам

Диаграммы размаха признаков по классам

Плотности распределений по классам

Плотности распределений по классам

Попарные диаграммы рассеяния

Попарные диаграммы рассеяния

Попарные диаграммы с эллипсами

Попарные диаграммы с эллипсами

3.4 Вывод по CARET

Диаграммы размаха и плотности позволяют сравнить положение и разброс распределений классов; матрицы рассеяния — проверить попарные связи и возможное разделение классов. На рисунках распределения A и B существенно перекрываются. Отдельные отличия средних или медиан в малой случайной выборке не означают, что признак имеет устойчивую предсказательную ценность. Для проверки качества классификации потребовалась бы независимая проверочная выборка или перекрёстная проверка. Все четыре рисунка сохранены в файлы JPG.

4 Оценка важности признаков iris с FSelector

4.1 Данные и методы

data(iris)
str(iris)
## 'data.frame':    150 obs. of  5 variables:
##  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
##  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
##  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
##  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
table(iris$Species)
## 
##     setosa versicolor  virginica 
##         50         50         50
colSums(is.na(iris))
## Sepal.Length  Sepal.Width Petal.Length  Petal.Width      Species 
##            0            0            0            0            0

Набор содержит 150 наблюдений: по 50 растений каждого из трёх видов. Используются четыре числовых признака; пропущенных значений нет. Целевая переменная Species — фактор.

Сравниваются четыре фильтра из FSelector: прирост информации, отношение прироста информации, симметричная неопределённость и важность случайного леса. Первые три оценивают связь признака с классом на основе энтропии; последний использует модельный подход. Для непрерывных признаков энтропийные фильтры применяют внутреннюю дискретизацию, предусмотренную пакетом.

set.seed(20261014)
ig <- information.gain(Species ~ ., iris)
gr <- gain.ratio(Species ~ ., iris)
su <- symmetrical.uncertainty(Species ~ ., iris)
set.seed(20261014)
rf <- random.forest.importance(Species ~ ., iris)
importance <- data.frame(
  Feature=rownames(ig),
  InformationGain=ig[, 1],
  GainRatio=gr[rownames(ig), 1],
  SymmetricalUncertainty=su[rownames(ig), 1],
  RandomForest=rf[rownames(ig), 1], row.names=NULL
)
importance <- importance[order(-importance$InformationGain), ]
rownames(importance) <- NULL
knitr::kable(importance, digits=5,
             caption="Оценки важности четырьмя методами FSelector")
Оценки важности четырьмя методами FSelector
Feature InformationGain GainRatio SymmetricalUncertainty RandomForest
Petal.Width 0.95544 0.87137 0.87052 47.53636
Petal.Length 0.94029 0.85849 0.85719 48.18096
Sepal.Length 0.45213 0.41965 0.41556 15.04162
Sepal.Width 0.26728 0.24730 0.24527 7.31904
ranks <- importance
ranks[-1] <- lapply(ranks[-1], function(z) rank(-z, ties.method="min"))
knitr::kable(ranks, caption="Ранги признаков внутри каждого метода 1 соответствует лучшему")
Ранги признаков внутри каждого метода 1 соответствует лучшему
Feature InformationGain GainRatio SymmetricalUncertainty RandomForest
Petal.Width 1 1 1 2
Petal.Length 2 2 2 1
Sepal.Length 3 3 3 3
Sepal.Width 4 4 4 4
selected_ig <- cutoff.k(ig, 2)
selected_rf <- cutoff.k(rf, 2)
selected_ig
## [1] "Petal.Width"  "Petal.Length"
selected_rf
## [1] "Petal.Length" "Petal.Width"
par(mar=c(5, 10, 3, 1))
barplot(setNames(importance$InformationGain, importance$Feature),
        horiz=TRUE, las=1, col="#4477AA", xlab="Information gain",
        main="Iris: information gain")
Важность признаков iris по приросту информации

Важность признаков iris по приросту информации

4.2 Вывод по FSelector

Два лучших признака по приросту информации: Petal.Width, Petal.Length; по случайному лесу: Petal.Length, Petal.Width. Размеры лепестка обычно лучше различают виды iris, чем размеры чашелистика; это отражено в полученных оценках и рангах. Числа разных методов имеют разные шкалы, поэтому сравнивать их абсолютные значения между столбцами нельзя. Отбор по важности не заменяет проверку точности модели. Если отбор используется в оценке качества классификатора, его необходимо выполнять внутри обучающих частей перекрёстной проверки, чтобы избежать утечки информации.

5 Дискретизация iris с arules

5.1 Четыре метода

Преобразованы все четыре непрерывных признака iris. Для методов interval, frequency и cluster задано по три категории. Метод fixed использует заранее указанные границы. Species не участвует в определении границ.

features <- names(iris)[1:4]
fixed_breaks <- list(
  Sepal.Length=c(-Inf, 5.5, 6.5, Inf),
  Sepal.Width=c(-Inf, 2.8, 3.5, Inf),
  Petal.Length=c(-Inf, 2, 5, Inf),
  Petal.Width=c(-Inf, 0.6, 1.8, Inf)
)
methods <- c("interval", "frequency", "cluster", "fixed")
disc <- setNames(vector("list", length(methods)), methods)
for (method in methods) {
  set.seed(20261014)
  disc[[method]] <- setNames(lapply(features, function(feature) {
    if (method == "fixed") {
      discretize(iris[[feature]], method="fixed",
                 breaks=fixed_breaks[[feature]])
    } else {
      discretize(iris[[feature]], method=method, breaks=3)
    }
  }), features)
}
disc_tables <- do.call(rbind, lapply(methods, function(method) {
  do.call(rbind, lapply(features, function(feature) {
    freq <- table(disc[[method]][[feature]])
    data.frame(Method=method, Feature=feature,
               Interval=names(freq), Count=as.integer(freq))
  }))
}))
stopifnot(!anyNA(disc_tables),
          all(aggregate(Count ~ Method + Feature, disc_tables, sum)$Count == 150))
knitr::kable(disc_tables, caption="Границы категорий и число наблюдений")
Границы категорий и число наблюдений
Method Feature Interval Count
interval Sepal.Length [4.3,5.5) 52
interval Sepal.Length [5.5,6.7) 70
interval Sepal.Length [6.7,7.9] 28
interval Sepal.Width [2,2.8) 47
interval Sepal.Width [2.8,3.6) 88
interval Sepal.Width [3.6,4.4] 15
interval Petal.Length [1,2.97) 50
interval Petal.Length [2.97,4.93) 54
interval Petal.Length [4.93,6.9] 46
interval Petal.Width [0.1,0.9) 50
interval Petal.Width [0.9,1.7) 52
interval Petal.Width [1.7,2.5] 48
frequency Sepal.Length [4.3,5.4) 46
frequency Sepal.Length [5.4,6.3) 53
frequency Sepal.Length [6.3,7.9] 51
frequency Sepal.Width [2,2.9) 47
frequency Sepal.Width [2.9,3.2) 47
frequency Sepal.Width [3.2,4.4] 56
frequency Petal.Length [1,2.63) 50
frequency Petal.Length [2.63,4.9) 49
frequency Petal.Length [4.9,6.9] 51
frequency Petal.Width [0.1,0.867) 50
frequency Petal.Width [0.867,1.6) 48
frequency Petal.Width [1.6,2.5] 52
cluster Sepal.Length [4.3,5.33) 46
cluster Sepal.Length [5.33,6.27) 53
cluster Sepal.Length [6.27,7.9] 51
cluster Sepal.Width [2,2.75) 33
cluster Sepal.Width [2.75,3.29) 74
cluster Sepal.Width [3.29,4.4] 43
cluster Petal.Length [1,2.88) 50
cluster Petal.Length [2.88,4.96) 54
cluster Petal.Length [4.96,6.9] 46
cluster Petal.Width [0.1,0.785) 50
cluster Petal.Width [0.785,1.69) 52
cluster Petal.Width [1.69,2.5] 48
fixed Sepal.Length [-Inf,5.5) 52
fixed Sepal.Length [5.5,6.5) 63
fixed Sepal.Length [6.5, Inf] 35
fixed Sepal.Width [-Inf,2.8) 33
fixed Sepal.Width [2.8,3.5) 92
fixed Sepal.Width [3.5, Inf] 25
fixed Petal.Length [-Inf,2) 50
fixed Petal.Length [2,5) 54
fixed Petal.Length [5, Inf] 46
fixed Petal.Width [-Inf,0.6) 49
fixed Petal.Width [0.6,1.8) 55
fixed Petal.Width [1.8, Inf] 46
example <- data.frame(Petal.Length=iris$Petal.Length)
for (method in methods) example[[method]] <- disc[[method]]$Petal.Length
knitr::kable(head(example, 12), caption="Пример преобразования длины лепестка")
Пример преобразования длины лепестка
Petal.Length interval frequency cluster fixed
1.4 [1,2.97) [1,2.63) [1,2.88) [-Inf,2)
1.4 [1,2.97) [1,2.63) [1,2.88) [-Inf,2)
1.3 [1,2.97) [1,2.63) [1,2.88) [-Inf,2)
1.5 [1,2.97) [1,2.63) [1,2.88) [-Inf,2)
1.4 [1,2.97) [1,2.63) [1,2.88) [-Inf,2)
1.7 [1,2.97) [1,2.63) [1,2.88) [-Inf,2)
1.4 [1,2.97) [1,2.63) [1,2.88) [-Inf,2)
1.5 [1,2.97) [1,2.63) [1,2.88) [-Inf,2)
1.4 [1,2.97) [1,2.63) [1,2.88) [-Inf,2)
1.5 [1,2.97) [1,2.63) [1,2.88) [-Inf,2)
1.5 [1,2.97) [1,2.63) [1,2.88) [-Inf,2)
1.6 [1,2.97) [1,2.63) [1,2.88) [-Inf,2)
for (method in methods) {
  cat("\nMethod:", method, "\n")
  print(table(Category=disc[[method]]$Petal.Length, Species=iris$Species))
}
## 
## Method: interval 
##              Species
## Category      setosa versicolor virginica
##   [1,2.97)        50          0         0
##   [2.97,4.93)      0         48         6
##   [4.93,6.9]       0          2        44
## 
## Method: frequency 
##             Species
## Category     setosa versicolor virginica
##   [1,2.63)       50          0         0
##   [2.63,4.9)      0         46         3
##   [4.9,6.9]       0          4        47
## 
## Method: cluster 
##              Species
## Category      setosa versicolor virginica
##   [1,2.88)        50          0         0
##   [2.88,4.96)      0         48         6
##   [4.96,6.9]       0          2        44
## 
## Method: fixed 
##           Species
## Category   setosa versicolor virginica
##   [-Inf,2)     50          0         0
##   [2,5)         0         48         6
##   [5, Inf]      0          2        44
par(mfrow=c(2, 2), mar=c(5, 4, 3, 1))
for (method in methods) {
  barplot(table(disc[[method]]$Petal.Length), col="#66AABB",
          main=method, ylab="Number of observations", las=1, cex.names=0.85)
}
Численность категорий длины лепестка для четырёх способов дискретизации

Численность категорий длины лепестка для четырёх способов дискретизации

par(mfrow=c(1, 1))

5.2 Вывод по дискретизации

  • interval делит диапазон на интервалы одинаковой ширины. Количество наблюдений в них зависит от формы распределения и может различаться.
  • frequency определяет границы по квантилям и стремится получить группы одинаковой численности. Из-за совпадающих значений границы не обязаны обеспечивать ровно по 50 наблюдений.
  • cluster использует кластеризацию k-means. Границы отражают структуру числовых значений, но ширина и численность групп не фиксированы.
  • fixed позволяет задать содержательные пороги. Значения -Inf и Inf обеспечивают покрытие всей числовой шкалы; внутренние границы нужно выбирать исходя из задачи.

Таблицы показывают, что методы могут присваивать одному значению разные категории. Дискретизация облегчает использование категориальных методов, но теряет информацию о различиях внутри интервала. При обучении модели границы следует определять на обучающих данных и затем применять без пересчёта к проверочным наблюдениям.

6 Выбор признаков Ozone с Boruta

6.1 Подготовка данных

data("Ozone", package="mlbench")
dim(Ozone)
## [1] 366  13
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 ...
missing_by_column <- colSums(is.na(Ozone))
knitr::kable(data.frame(Feature=names(missing_by_column),
                        Missing=as.integer(missing_by_column)),
              caption="Пропуски в исходном Ozone")
Пропуски в исходном Ozone
Feature Missing
V1 0
V2 0
V3 0
V4 5
V5 12
V6 0
V7 15
V8 2
V9 139
V10 15
V11 1
V12 14
V13 0
ozo <- na.omit(Ozone)
data.frame(OriginalRows=nrow(Ozone), CompleteRows=nrow(ozo),
           RemovedRows=nrow(Ozone)-nrow(ozo), Predictors=ncol(ozo)-1)
##   OriginalRows CompleteRows RemovedRows Predictors
## 1          366          203         163         12
stopifnot(is.numeric(ozo$V4), !anyNA(ozo))

V4 — максимальное за день среднее часовое значение озона. Другие столбцы — календарные и метеорологические признаки. Их смысл приведён по справке mlbench::Ozone.

Столбец Переменная
V1 Месяц
V2 День месяца
V3 День недели
V5 Высота поверхности давления 500 мбар, Vandenberg, м
V6 Скорость ветра, LAX, мили в час
V7 Влажность, LAX, проценты
V8 Температура, Sandburg, градусы F
V9 Температура, El Monte, градусы F
V10 Высота основания инверсии, LAX, футы
V11 Градиент давления между LAX и Daggett, мм ртутного столба
V12 Температура основания инверсии, LAX, градусы F
V13 Видимость, LAX, мили

После исключения строк с пропусками остаётся 203 наблюдений из 366, удалено 163 строк. Используются 12 предикторов; V4 исключена из их состава с помощью формулы V4 ~ .. Удаление неполных строк сокращает выборку и может влиять на представительность результата.

6.2 Алгоритм и настройки

Boruta сравнивает важности исходных признаков с важностями их случайно перемешанных копий — теневых признаков. Он ищет все релевантные признаки, а не обязательно минимальный набор. Confirmed означает подтверждённую важность, Rejected — отклонение, Tentative — неопределённый результат. Использован адаптер getImpLegacyRfZ на основе randomForest: 500 деревьев на итерацию, не более 300 итераций, уровень 0,01 с поправкой на множественные сравнения. История важностей сохранена для построения boxplot.

set.seed(20261014)
bor <- Boruta(V4 ~ ., data=ozo, maxRuns=300, pValue=0.01,
              mcAdj=TRUE, doTrace=0, holdHistory=TRUE,
              getImp=getImpLegacyRfZ, ntree=500)
print(bor)
## Boruta performed 18 iterations in 5.248606 secs.
##  9 attributes confirmed important: V1, V10, V11, V12, V13 and 4 more;
##  3 attributes confirmed unimportant: V2, V3, V6;
original_decisions <- table(bor$finalDecision)
original_decisions
## 
## Tentative Confirmed  Rejected 
##         0         9         3
# Если остались Tentative, применяется отдельный приближённый шаг.
bor_final <- if (any(bor$finalDecision == "Tentative")) {
  TentativeRoughFix(bor)
} else bor
stats <- attStats(bor_final)
stats <- data.frame(Feature=rownames(stats), stats, row.names=NULL)
stats <- stats[order(-stats$meanImp), ]
rownames(stats) <- NULL
knitr::kable(stats, digits=4, caption="Статистика важности и решения Boruta")
Статистика важности и решения Boruta
Feature meanImp medianImp minImp maxImp normHits decision
V9 19.8807 19.9803 18.3050 21.3997 1.0000 Confirmed
V8 17.7926 17.7609 16.8414 19.6099 1.0000 Confirmed
V12 13.7231 13.4673 12.5726 16.1614 1.0000 Confirmed
V1 12.3035 11.8965 10.3993 15.3417 1.0000 Confirmed
V11 9.3628 8.9674 6.5233 12.3176 1.0000 Confirmed
V10 8.5730 8.7061 5.9855 10.1287 1.0000 Confirmed
V7 8.4847 8.2226 6.4653 11.1574 1.0000 Confirmed
V5 7.0131 6.9439 5.4010 9.4069 1.0000 Confirmed
V13 6.6610 6.7154 5.0898 8.1693 1.0000 Confirmed
V6 0.1705 -0.0408 -1.5071 2.4544 0.1111 Rejected
V3 -0.9016 -0.8440 -2.2833 0.0810 0.0000 Rejected
V2 -2.3523 -2.3670 -3.2686 -0.8004 0.0000 Rejected
selected <- getSelectedAttributes(bor_final, withTentative=FALSE)
selected
## [1] "V1"  "V5"  "V7"  "V8"  "V9"  "V10" "V11" "V12" "V13"
par(mar=c(8, 4, 3, 1))
plot(bor, las=2, cex.axis=0.85, xlab="", ylab="Importance Z score",
     main="Boruta: Ozone feature importance")
Boxplot распределений важности Boruta и теневых признаков

Boxplot распределений важности Boruta и теневых признаков

На рисунке зелёным отмечены подтверждённые признаки, красным — отклонённые, жёлтым — неопределённые; синим показаны теневые признаки. Рисунок показывает исходное решение Boruta до возможного TentativeRoughFix. Приближённое разрешение неопределённости не тождественно полноценному статистическому подтверждению.

6.3 Вывод по Boruta

Подтверждённые признаки после окончательной обработки: V1, V5, V7, V8, V9, V10, V11, V12, V13. Число подтверждённых признаков — 9. Исходное число неопределённых признаков — 0. Дополнительный TentativeRoughFix не потребовался. Самая высокая средняя важность получена у V9. В данном запуске это температура в El Monte. Отклонены V2, V3 и V6: день месяца, день недели и скорость ветра. Это решение относится к данной выборке и настройкам алгоритма, а не означает, что ветер вообще не связан с загрязнением воздуха. Релевантность в Boruta означает, что признак даёт информацию сверх случайного теневого контроля в выбранной модели. Она не доказывает причинного влияния на уровень озона и не гарантирует качество прогноза на будущих датах. Для такого прогноза нужна отдельная проверка с учётом временного порядка наблюдений.

7 Общий вывод

Выполнены все четыре вычислительных раздела работы. CARET использован для сравнения графических представлений признаков; четыре рисунка сохранены в JPG. Для iris получены оценки и ранги признаков четырьмя функциями FSelector. Применены четыре способа дискретизации ко всем непрерывным признакам iris и проверено сохранение числа наблюдений. Для Ozone выполнен отбор Boruta с учётом пропусков, построен boxplot и приведён список выбранных признаков.

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

8 Источники

  1. Задание на лабораторную работу 2.
  2. Документация CARET.
  3. Документация FSelector.
  4. Дискретизация arules.
  5. Алгоритм Boruta.
  6. Набор Ozone.

9 Сведения о программной среде

sessionInfo()
## R version 4.6.1 (2026-06-24 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 10 x64 (build 19045)
## 
## 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] knitr_1.52     mlbench_2.1-11 Boruta_10.0.0  arules_1.7.15  Matrix_1.7-5   FSelector_0.34
## [7] caret_7.0-1    lattice_0.23-1 ggplot2_4.0.3 
## 
## loaded via a namespace (and not attached):
##  [1] ellipse_0.5.0        gtable_0.3.6         xfun_0.61            bslib_0.12.0        
##  [5] recipes_1.4.0        rJava_1.0-18         vctrs_0.7.3          tools_4.6.1         
##  [9] generics_0.1.4       stats4_4.6.1         parallel_4.6.1       tibble_3.3.1        
## [13] pkgconfig_2.0.3      ModelMetrics_1.2.2.2 data.table_1.18.6.1  RColorBrewer_1.1-3  
## [17] S7_0.2.2             lifecycle_1.0.5      compiler_4.6.1       farver_2.1.2        
## [21] stringr_1.6.0        codetools_0.2-20     htmltools_0.5.9      class_7.3-23        
## [25] sass_0.4.10          yaml_2.3.12          prodlim_2026.03.11   pillar_1.11.1       
## [29] jquerylib_0.1.4      MASS_7.3-65          entropy_1.3.2        cachem_1.1.0        
## [33] gower_1.0.2          iterators_1.0.14     rpart_4.1.27         foreach_1.5.2       
## [37] nlme_3.1-169         parallelly_1.48.0    lava_1.9.3           tidyselect_1.2.1    
## [41] digest_0.6.39        stringi_1.8.9        future_1.76.0        dplyr_1.2.1         
## [45] reshape2_1.4.5       purrr_1.2.2          listenv_1.1.0        RWekajars_3.9.3-2   
## [49] splines_4.6.1        fastmap_1.2.0        grid_4.6.1           cli_3.6.6           
## [53] magrittr_2.0.5       randomForest_4.7-1.2 survival_3.8-6       future.apply_1.20.2 
## [57] withr_3.0.3          scales_1.4.0         lubridate_1.9.5      timechange_0.4.0    
## [61] rmarkdown_2.32       globals_0.20.0       otel_0.2.0           nnet_7.3-20         
## [65] RWeka_0.4-50         timeDate_4052.112    evaluate_1.0.5       hardhat_1.4.3       
## [69] rlang_1.3.0          Rcpp_1.1.2           glue_1.8.1           pROC_1.19.1         
## [73] ipred_0.9-16         rstudioapi_0.19.0    jsonlite_2.0.0       R6_2.6.1            
## [77] plyr_1.8.9