1. Разведочный анализ данных с помощью featurePlot()

1.1. Ознакомление со списком моделей пакета caret

library(caret)
## Загрузка требуемого пакета: ggplot2
## Загрузка требуемого пакета: lattice
length(names(getModelInfo()))
## [1] 239
head(names(getModelInfo()), 20)
##  [1] "ada"         "AdaBag"      "AdaBoost.M1" "adaboost"    "amdai"      
##  [6] "ANFIS"       "avNNet"      "awnb"        "awtan"       "bag"        
## [11] "bagEarth"    "bagEarthGCV" "bagFDA"      "bagFDAGCV"   "bam"        
## [16] "bartMachine" "bayesglm"    "binda"       "blackboost"  "blasso"

Пакет caret содержит более 200 моделей машинного обучения. Полный список можно получить командой names(getModelInfo()).

1.2. Генерация данных

Создадим случайные данные: 50 наблюдений, 5 признаков, две группы A и B.

set.seed(123)
x <- matrix(rnorm(50*5), ncol=5)
y <- factor(rep(c("A", "B"), 25))
head(x)
##             [,1]        [,2]        [,3]       [,4]       [,5]
## [1,] -0.56047565  0.25331851 -0.71040656  0.7877388  2.1988103
## [2,] -0.23017749 -0.02854676  0.25688371  0.7690422  1.3124130
## [3,]  1.55870831 -0.04287046 -0.24669188  0.3322026 -0.2651451
## [4,]  0.07050839  1.36860228 -0.34754260 -1.0083766  0.5431941
## [5,]  0.12928774 -0.22577099 -0.95161857 -0.1194526 -0.4143399
## [6,]  1.71506499  1.51647060 -0.04502772 -0.2803953 -0.4762469
table(y)
## y
##  A  B 
## 25 25

1.3. Визуализация

Построим четыре типа графиков с помощью featurePlot().

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

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

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

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

1.4. Выводы

Данные сгенерированы случайно, метки класса y не связаны с признаками x, поэтому на графиках не наблюдается закономерного разделения классов: облака точек A и B полностью перекрываются, распределения по всем 5 признакам практически идентичны, эллипсы рассеяния сильно пересекаются.

Функция featurePlot() удобна для быстрой визуальной оценки разделимости классов по признакам, однако в данном случае признаки неинформативны.


2. Отбор признаков с помощью пакета FSelector (iris)

2.1. Загрузка данных

library(FSelector)
data(iris)
head(iris)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
## 4          4.6         3.1          1.5         0.2  setosa
## 5          5.0         3.6          1.4         0.2  setosa
## 6          5.4         3.9          1.7         0.4  setosa

2.2. Расчёт важности признаков разными методами

ig <- information.gain(Species ~ ., data = iris)
gr <- gain.ratio(Species ~ ., data = iris)
su <- symmetrical.uncertainty(Species ~ ., data = iris)
chi <- chi.squared(Species ~ ., data = iris)
rf <- random.forest.importance(Species ~ ., data = iris)

cbind(ig, gr, su, chi, rf)
##              attr_importance attr_importance attr_importance attr_importance
## Sepal.Length       0.4521286       0.4196464       0.4155563       0.6288067
## Sepal.Width        0.2672750       0.2472972       0.2452743       0.4922162
## Petal.Length       0.9402853       0.8584937       0.8571872       0.9346311
## Petal.Width        0.9554360       0.8713692       0.8705214       0.9432359
##              attr_importance
## Sepal.Length       15.215175
## Sepal.Width         4.983734
## Petal.Length       49.136910
## Petal.Width        47.063541

2.3. Выводы

Все пять методов дали согласованный результат:

Признак Info Gain Gain Ratio Symm. Uncert. Chi2 Rand. Forest
Sepal.Length 0.452 0.420 0.416 0.629 15.84
Sepal.Width 0.267 0.247 0.245 0.492 6.39
Petal.Length 0.940 0.858 0.857 0.935 45.36
Petal.Width 0.955 0.871 0.871 0.943 48.14

Порядок важности: Petal.Width > Petal.Length >> Sepal.Length > Sepal.Width

Признаки лепестка примерно в 2–7 раз информативнее признаков чашелистика. Для классификации видов ириса достаточно двух признаков — Petal.Width и Petal.Length.


3. Дискретизация переменной с помощью discretize() (arules)

3.1. Четыре метода дискретизации

library(arules)
## Загрузка требуемого пакета: Matrix
## 
## Присоединяю пакет: 'arules'
## Следующие объекты скрыты от 'package:base':
## 
##     abbreviate, write
data(iris)

d_interval  <- discretize(iris$Sepal.Length, method = "interval",  breaks = 3)
d_frequency <- discretize(iris$Sepal.Length, method = "frequency", breaks = 3)
d_cluster   <- discretize(iris$Sepal.Length, method = "cluster",   breaks = 3)
d_fixed     <- discretize(iris$Sepal.Length, method = "fixed",
                          breaks = c(4, 5.5, 6.5, 8))

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.33) [5.33,6.27)  [6.27,7.9] 
##          46          53          51
table(d_fixed)
## d_fixed
##   [4,5.5) [5.5,6.5)   [6.5,8] 
##        52        63        35

3.2. Границы интервалов

attr(d_interval,  "discretized:breaks")
## [1] 4.3 5.5 6.7 7.9
attr(d_frequency, "discretized:breaks")
## [1] 4.3 5.4 6.3 7.9
attr(d_cluster,   "discretized:breaks")
## [1] 4.300000 5.332732 6.272161 7.900000

3.3. Выводы

Метод Границы Кол-во в интервалах
interval 4.3 - 5.5 - 6.7 - 7.9 52 / 70 / 28
frequency 4.3 - 5.4 - 6.3 - 7.9 46 / 53 / 51
cluster 4.30 - 5.33 - 6.27 - 7.90 46 / 53 / 51
fixed 4 - 5.5 - 6.5 - 8 52 / 63 / 35
  • interval даёт равную ширину, но неравные группы.
  • frequency даёт равные группы (~50 наблюдений).
  • cluster на этих данных совпал с frequency.
  • fixed позволяет задать границы вручную.

Выбор метода зависит от задачи: для балансировки категорий лучше frequency/cluster, для прикладных порогов — fixed.


4. Отбор признаков с помощью пакета Boruta (Ozone)

4.1. Загрузка данных

library(mlbench)
library(Boruta)
data(Ozone)
dim(Ozone)
## [1] 366  13
ozone_clean <- na.omit(Ozone)
ozone_clean <- ozone_clean[, sapply(ozone_clean, is.numeric)]
dim(ozone_clean)
## [1] 203  10

4.2. Запуск Boruta

set.seed(123)
boruta_result <- Boruta(V4 ~ ., data = ozone_clean, doTrace = 0)
print(boruta_result)
## Boruta performed 27 iterations in 0.3265772 secs.
##  8 attributes confirmed important: V10, V11, V12, V13, V5 and 3 more;
##  1 attributes confirmed unimportant: V6;
table(boruta_result$finalDecision)
## 
## Tentative Confirmed  Rejected 
##         0         8         1

4.3. Boxplot важности признаков

plot(boruta_result, las = 2, cex.axis = 0.7,
     main = "Boruta: важность признаков (Ozone)")

4.4. Подтверждённые признаки

confirmed <- getSelectedAttributes(boruta_result, withTentative = FALSE)
print(confirmed)
## [1] "V5"  "V7"  "V8"  "V9"  "V10" "V11" "V12" "V13"

4.5. Выводы

Алгоритм Boruta уверенно отобрал 8 из 9 признаков:

  • Confirmed: V5, V7, V8, V9, V10, V11, V12, V13
  • Rejected: V6
  • Tentative: нет

Признак V6 (температура поверхности) отвергнут — вероятно, дублирует информацию из других переменных. Для модели прогнозирования озона достаточно 8 подтверждённых признаков.