1 Задание 2 — классификация k-ближайших соседей (kNN)

Цель: выполнить классификацию методом kNN (class::knn()) на наборе данных iris, нормализовать признаки, разделить выборку на обучающую и тестовую, оценить модель с помощью CrossTable() (пакет gmodels), построить матрицу ошибок и диагональную оценку качества прогноза.

1.1 Исследование данных

library(class)    # knn(), knn.cv()
library(gmodels)  # CrossTable()
library(caret)    # createDataPartition(), confusionMatrix()

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 ...
summary(iris)
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species  
##  setosa    :50  
##  versicolor:50  
##  virginica :50  
##                 
##                 
## 
table(iris$Species)
## 
##     setosa versicolor  virginica 
##         50         50         50

Набор iris содержит 150 цветков трёх видов (по 50 каждого) и 4 числовых признака. Признаки измерены в сантиметрах, но имеют разный разброс (например, Petal.Length изменяется от 1 до 6.9, а Sepal.Width — от 2 до 4.4).

1.2 Разделение на обучающую и тестовую выборки

Метод kNN основан на расстояниях, поэтому признаки необходимо привести к одному масштабу. Чтобы информация о тестовых данных не «просачивалась» в модель, сначала выполняется разбиение, а параметры min-max нормализации (минимум и максимум каждого признака) вычисляются только по обучающей выборке и затем применяются к тестовой.

Разбиение стратифицированное (createDataPartition): доли видов в обеих выборках одинаковы.

set.seed(123)  # воспроизводимость

train_idx <- createDataPartition(iris$Species, p = 0.7, list = FALSE)

train_raw    <- iris[train_idx, 1:4]
test_raw     <- iris[-train_idx, 1:4]
train_labels <- iris$Species[train_idx]
test_labels  <- iris$Species[-train_idx]

cat("Обучающая выборка:", nrow(train_raw), "наблюдений\n")
## Обучающая выборка: 105 наблюдений
cat("Тестовая выборка: ", nrow(test_raw),  "наблюдений\n")
## Тестовая выборка:  45 наблюдений
table(Обучающая = train_labels)
## Обучающая
##     setosa versicolor  virginica 
##         35         35         35
table(Тестовая  = test_labels)
## Тестовая
##     setosa versicolor  virginica 
##         15         15         15

1.3 Нормализация (min-max)

\[x' = \frac{x - x_{\min}}{x_{\max} - x_{\min}}\]

mins <- sapply(train_raw, min)
maxs <- sapply(train_raw, max)

minmax <- function(df, lo, hi) {
  as.data.frame(mapply(function(x, a, b) (x - a) / (b - a),
                       df, lo, hi, SIMPLIFY = FALSE))
}

train_data <- minmax(train_raw, mins, maxs)
test_data  <- minmax(test_raw,  mins, maxs)

# Проверка: обучающая выборка целиком в [0, 1]
rbind(min = sapply(train_data, min), max = sapply(train_data, max))
##     Sepal.Length Sepal.Width Petal.Length Petal.Width
## min            0           0            0           0
## max            1           1            1           1
head(train_data)

Тестовые значения при такой схеме могут незначительно выходить за пределы \([0, 1]\), если в тесте встретились значения вне диапазона обучающей выборки, — это нормально.

1.4 Подбор числа соседей k

Значение k выбирается по обучающей выборке с помощью перекрёстной проверки «исключай по одному» (knn.cv), а не по тестовой — иначе тестовая выборка перестаёт быть независимой.

k_values <- seq(1, 25, by = 2)

cv_acc <- sapply(k_values, function(k) {
  mean(knn.cv(train = train_data, cl = train_labels, k = k) == train_labels)
})

k_table <- data.frame(k = k_values, cv_accuracy_pct = round(cv_acc * 100, 1))
k_table
# среди k с максимальной точностью берём наименьшее (более простая модель)
k_best <- k_values[which.max(cv_acc)]
cat("Выбрано k =", k_best, "\n")
## Выбрано k = 7
plot(k_values, cv_acc * 100, type = "b", pch = 19, col = "steelblue",
     xlab = "k (число соседей)", ylab = "Точность на обучающей выборке, LOO (%)",
     main = "Подбор k для kNN")
abline(v = k_best, col = "red", lty = 2)
legend("bottomright", legend = paste("k =", k_best),
       col = "red", lty = 2, bty = "n")

1.5 Классификация

Алгоритм knn() не строит модель в явном виде: для каждого объекта тестовой выборки он находит k ближайших обучающих объектов и назначает класс голосованием.

knn_pred <- knn(train = train_data,
                test  = test_data,
                cl    = train_labels,
                k     = k_best)
knn_pred
##  [1] setosa     setosa     setosa     setosa     setosa     setosa    
##  [7] setosa     setosa     setosa     setosa     setosa     setosa    
## [13] setosa     setosa     setosa     versicolor versicolor versicolor
## [19] versicolor versicolor versicolor versicolor versicolor versicolor
## [25] versicolor versicolor versicolor versicolor versicolor versicolor
## [31] virginica  virginica  virginica  virginica  virginica  virginica 
## [37] versicolor virginica  virginica  virginica  versicolor virginica 
## [43] virginica  virginica  virginica 
## Levels: setosa versicolor virginica

1.6 Оценка модели: CrossTable()

Строки — реальные классы, столбцы — предсказанные. Идеальный результат — все объекты на главной диагонали; всё, что вне диагонали, — ошибки.

CrossTable(x = test_labels, y = knn_pred,
           prop.chisq = FALSE,
           dnn = c("Реальный класс", "Предсказанный класс"))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |           N / Row Total |
## |           N / Col Total |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  45 
## 
##  
##                | Предсказанный класс 
## Реальный класс |     setosa | versicolor |  virginica |  Row Total | 
## ---------------|------------|------------|------------|------------|
##         setosa |         15 |          0 |          0 |         15 | 
##                |      1.000 |      0.000 |      0.000 |      0.333 | 
##                |      1.000 |      0.000 |      0.000 |            | 
##                |      0.333 |      0.000 |      0.000 |            | 
## ---------------|------------|------------|------------|------------|
##     versicolor |          0 |         15 |          0 |         15 | 
##                |      0.000 |      1.000 |      0.000 |      0.333 | 
##                |      0.000 |      0.882 |      0.000 |            | 
##                |      0.000 |      0.333 |      0.000 |            | 
## ---------------|------------|------------|------------|------------|
##      virginica |          0 |          2 |         13 |         15 | 
##                |      0.000 |      0.133 |      0.867 |      0.333 | 
##                |      0.000 |      0.118 |      1.000 |            | 
##                |      0.000 |      0.044 |      0.289 |            | 
## ---------------|------------|------------|------------|------------|
##   Column Total |         15 |         17 |         13 |         45 | 
##                |      0.333 |      0.378 |      0.289 |            | 
## ---------------|------------|------------|------------|------------|
## 
## 

1.7 Матрица ошибок и диагональная оценка качества

cm_knn <- confusionMatrix(knn_pred, test_labels)
cm_knn
## Confusion Matrix and Statistics
## 
##             Reference
## Prediction   setosa versicolor virginica
##   setosa         15          0         0
##   versicolor      0         15         2
##   virginica       0          0        13
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9556          
##                  95% CI : (0.8485, 0.9946)
##     No Information Rate : 0.3333          
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.9333          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: setosa Class: versicolor Class: virginica
## Sensitivity                 1.0000            1.0000           0.8667
## Specificity                 1.0000            0.9333           1.0000
## Pos Pred Value              1.0000            0.8824           1.0000
## Neg Pred Value              1.0000            1.0000           0.9375
## Prevalence                  0.3333            0.3333           0.3333
## Detection Rate              0.3333            0.3333           0.2889
## Detection Prevalence        0.3333            0.3778           0.2889
## Balanced Accuracy           1.0000            0.9667           0.9333

Диагональная оценка качества прогноза: на главной диагонали матрицы ошибок стоят верно классифицированные объекты.

tab <- cm_knn$table   # строки — предсказание, столбцы — реальный класс

diag_quality <- data.frame(
  `Верно классифицировано` = diag(tab),
  `Всего в классе`         = colSums(tab),
  `Полнота, % (recall)`    = round(diag(tab) / colSums(tab) * 100, 1),
  `Точность, % (precision)`= round(diag(tab) / rowSums(tab) * 100, 1),
  check.names = FALSE
)
diag_quality
cat("\nОбщая точность (сумма диагонали / все объекты):",
    sum(diag(tab)), "/", sum(tab), "=",
    round(sum(diag(tab)) / sum(tab) * 100, 2), "%\n")
## 
## Общая точность (сумма диагонали / все объекты): 43 / 45 = 95.56 %

Вывод. Модель kNN с k = 7 правильно классифицировала 43 из 45 тестовых объектов (точность 95.6 %, каппа Коэна 0.933). Вид setosa отделяется от остальных без ошибок; возможные ошибки сосредоточены между versicolor и virginica, чьи области в пространстве признаков частично перекрываются. Так как тестовая выборка небольшая (45 объектов), одна ошибка меняет точность примерно на 2.2 %, поэтому доверительный интервал оценки достаточно широк (84.9 – 99.5 %).

2 Задание 3 — метод опорных векторов (SVM)

Цель: реализовать SVM через e1071::svm(), построить линейный классификатор и подобрать параметры с помощью перекрёстной проверки с делением выборки на 10 частей (cross = 10).

Идея метода: SVM строит разделяющую гиперплоскость с максимальным зазором между классами; объекты, лежащие на границе зазора или нарушающие его, — опорные векторы. Параметр cost задаёт штраф за нарушения: малое значение — «мягкая» граница с большим зазором, большое — «жёсткая», более подверженная переобучению. Для трёх классов svm() использует схему «каждый против каждого».

2.1 Обучающая и тестовая выборки

library(e1071)

set.seed(42)
idx_svm   <- createDataPartition(iris$Species, p = 0.7, list = FALSE)
train_svm <- iris[idx_svm, ]
test_svm  <- iris[-idx_svm, ]

cat("Обучающая:", nrow(train_svm), " Тестовая:", nrow(test_svm), "\n")
## Обучающая: 105  Тестовая: 45

2.2 Линейный SVM и 10-кратная перекрёстная проверка

При cross = 10 обучающая выборка делится на 10 равных частей: модель обучается на 9 частях и проверяется на оставшейся; процедура повторяется 10 раз. Признаки стандартизуются автоматически (scale = TRUE).

set.seed(42)
svm_linear <- svm(Species ~ ., data = train_svm,
                  kernel = "linear", cost = 1, scale = TRUE,
                  cross = 10)

print(svm_linear)
## 
## Call:
## svm(formula = Species ~ ., data = train_svm, kernel = "linear", cost = 1, 
##     cross = 10, scale = TRUE)
## 
## 
## Parameters:
##    SVM-Type:  C-classification 
##  SVM-Kernel:  linear 
##        cost:  1 
## 
## Number of Support Vectors:  22
cat("\nЧисло опорных векторов по классам:\n")
## 
## Число опорных векторов по классам:
print(setNames(svm_linear$nSV, levels(train_svm$Species)))
##     setosa versicolor  virginica 
##          2         11          9
cat("\nТочность по каждому из 10 блоков, %:\n")
## 
## Точность по каждому из 10 блоков, %:
print(round(svm_linear$accuracies, 2))
##  [1] 100.00 100.00 100.00 100.00 100.00  81.82 100.00 100.00  90.00  90.91
cat("\nОбщая точность перекрёстной проверки (tot.acc):",
    round(svm_linear$tot.acc, 2), "%\n")
## 
## Общая точность перекрёстной проверки (tot.acc): 96.19 %

2.3 Подбор параметра cost

Параметр cost подбирается перебором по логарифмической сетке с помощью tune(); для каждого значения качество оценивается 10-кратной перекрёстной проверкой.

set.seed(42)
tune_lin <- tune(svm, Species ~ ., data = train_svm,
                 kernel = "linear", scale = TRUE,
                 ranges = list(cost = 10^seq(-3, 3)),
                 tunecontrol = tune.control(cross = 10))

# таблица ошибок по сетке
transform(tune_lin$performances,
          error_pct = round(error * 100, 2),
          dispersion_pct = round(dispersion * 100, 2))[, c("cost", "error_pct", "dispersion_pct")]
cat("\nЛучшее значение cost:", tune_lin$best.parameters$cost, "\n")
## 
## Лучшее значение cost: 1
cat("Ошибка перекрёстной проверки лучшей модели:",
    round(tune_lin$best.performance * 100, 2), "%\n")
## Ошибка перекрёстной проверки лучшей модели: 3.82 %
plot(tune_lin, main = "SVM (linear): ошибка перекрёстной проверки в зависимости от cost")

Если несколько значений cost дают одинаковую ошибку, tune() возвращает первое из них (наименьшее).

2.4 Оценка на тестовой выборке

best_lin  <- tune_lin$best.model
svm_pred  <- predict(best_lin, test_svm)

cm_svm <- confusionMatrix(svm_pred, test_svm$Species)
cm_svm$table
##             Reference
## Prediction   setosa versicolor virginica
##   setosa         15          0         0
##   versicolor      0         14         1
##   virginica       0          1        14
tab_svm <- cm_svm$table
cat("\nДиагональ (верно классифицировано по классам):\n")
## 
## Диагональ (верно классифицировано по классам):
print(diag(tab_svm))
##     setosa versicolor  virginica 
##         15         14         14
cat("\nОбщая точность SVM на тестовой выборке:",
    round(cm_svm$overall["Accuracy"] * 100, 2), "%\n")
## 
## Общая точность SVM на тестовой выборке: 95.56 %

2.5 Визуализация линейной границы

Модель по четырём признакам нельзя нарисовать на плоскости, поэтому для иллюстрации строится линейный SVM на двух самых информативных признаках — Petal.Length и Petal.Width. Крестики — опорные векторы, круги — прочие объекты обучающей выборки; цвет фона — предсказанный класс.

train_2d <- train_svm[, c("Petal.Length", "Petal.Width", "Species")]

svm_2d <- svm(Species ~ ., data = train_2d,
              kernel = "linear", cost = tune_lin$best.parameters$cost,
              scale = TRUE)

plot(svm_2d, train_2d, formula = Petal.Width ~ Petal.Length)

acc_2d <- mean(predict(svm_2d, test_svm[, c("Petal.Length", "Petal.Width")]) ==
                 test_svm$Species)
cat("Точность двухпризнаковой модели на тесте:", round(acc_2d * 100, 2), "%\n")
## Точность двухпризнаковой модели на тесте: 93.33 %

2.6 Дополнение: сравнение с нелинейным ядром

Для сравнения — SVM с радиальным ядром (RBF), у которого подбираются два параметра: cost и gamma.

set.seed(42)
tune_rad <- tune(svm, Species ~ ., data = train_svm, kernel = "radial",
                 ranges = list(cost = c(0.1, 1, 10, 100),
                               gamma = c(0.01, 0.1, 1)),
                 tunecontrol = tune.control(cross = 10))

cat("Лучшие параметры RBF-модели:\n")
## Лучшие параметры RBF-модели:
print(tune_rad$best.parameters)
##   cost gamma
## 3   10  0.01
cat("Ошибка перекрёстной проверки:", round(tune_rad$best.performance * 100, 2), "%\n")
## Ошибка перекрёстной проверки: 2.91 %
rad_acc <- mean(predict(tune_rad$best.model, test_svm) == test_svm$Species)

data.frame(
  Модель = c("Линейный SVM", "SVM с RBF-ядром"),
  `CV-ошибка, %` = round(c(tune_lin$best.performance,
                           tune_rad$best.performance) * 100, 2),
  `Точность на тесте, %` = round(c(cm_svm$overall["Accuracy"],
                                   rad_acc) * 100, 2),
  check.names = FALSE
)

Вывод. Линейный SVM (cost = 1) достигает точности 95.6 % на тестовой выборке; ошибка 10-кратной перекрёстной проверки — 3.8 %. Модель с радиальным ядром даёт CV-ошибку 2.9 % и точность на тесте 93.3 %. Различие невелико (при 45 тестовых объектах один объект = 2.2 %), поэтому переходить к нелинейному ядру нет веских оснований: классы iris почти линейно разделимы. Setosa отделяется идеально, а немногочисленные ошибки относятся к перекрывающимся versicolor и virginica.

3 Задание 4 — метод главных компонент (PCA)

Цель: рассчитать главные компоненты с помощью функции rda() пакета vegan, построить ординационную диаграмму и сделать выводы.

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

library(vegan)

iris_num <- iris[, 1:4]

round(cor(iris_num), 2)
##              Sepal.Length Sepal.Width Petal.Length Petal.Width
## Sepal.Length         1.00       -0.12         0.87        0.82
## Sepal.Width         -0.12        1.00        -0.43       -0.37
## Petal.Length         0.87       -0.43         1.00        0.96
## Petal.Width          0.82       -0.37         0.96        1.00

Между Petal.Length, Petal.Width и Sepal.Length наблюдается сильная корреляция (r = 0.82–0.96), то есть признаки во многом дублируют друг друга — это хорошая предпосылка для снижения размерности.

3.2 Расчёт главных компонент

rda() без правой части формулы выполняет PCA. Аргумент scale = TRUE приводит признаки к единичной дисперсии (PCA на матрице корреляций) и обязателен, если признаки имеют разный разброс.

pca <- rda(iris_num, scale = TRUE)
summary(pca, display = NULL)   # только собственные числа, без 150 координат объектов
## 
## Call:
## rda(X = iris_num, scale = TRUE) 
## 
## Partitioning of correlations:
##               Inertia Proportion
## Total               4          1
## Unconstrained       4          1
## 
## Eigenvalues, and their contribution to the correlations 
## 
## Importance of components:
##                          PC1    PC2     PC3      PC4
## Eigenvalue            2.9185 0.9140 0.14676 0.020715
## Proportion Explained  0.7296 0.2285 0.03669 0.005179
## Cumulative Proportion 0.7296 0.9581 0.99482 1.000000

Проверка результата независимой реализацией (prcomp): собственные числа должны совпасть.

all.equal(as.numeric(eigenvals(pca)),
          prcomp(iris_num, scale. = TRUE)$sdev^2)
## [1] TRUE

3.3 Объяснённая дисперсия

eig       <- as.numeric(eigenvals(pca))
prop_var  <- eig / sum(eig) * 100

var_table <- data.frame(
  PC = paste0("PC", seq_along(eig)),
  Eigenvalue = round(eig, 3),
  `Дисперсия, %` = round(prop_var, 2),
  `Накопленная, %` = round(cumsum(prop_var), 2),
  `Broken stick, %` = round(bstick(pca) / sum(eig) * 100, 2),
  check.names = FALSE
)
var_table
screeplot(pca, bstick = TRUE, type = "barplot", legend = FALSE,
          main = "Scree plot: собственные числа и модель «сломанной палки»",
          ylab = "Собственное число")
abline(h = 1, col = "blue", lty = 2, lwd = 2)
legend("topright",
       legend = c("Порог Кайзера (λ = 1)", "Модель «сломанной палки»"),
       col = c("blue", "red"), lty = c(2, 1), pch = c(NA, 1), bty = "n")

Для выбора числа компонент применяются два критерия: критерий Кайзера (оставляем компоненты с λ > 1) и модель «сломанной палки» (оставляем компоненты, у которых собственное число больше ожидаемого при случайном разбиении общей дисперсии). Первая компонента (λ = 2.92) проходит оба критерия с большим запасом. Вторая компонента (λ = 0.91) формально находится чуть ниже обоих порогов (1 по Кайзеру и 1.08 по «сломанной палке»), то есть является пограничной. Тем не менее для ординационной диаграммы сохраняются две компоненты: PC2 несёт 22.9 % дисперсии и связана с отдельным признаком (Sepal.Width), а две оси необходимы для построения плоского биплота. Вместе PC1 и PC2 объясняют 95.8 % общей дисперсии.

3.4 Ординационная диаграмма (биплот)

Точки — объекты (цветки), цвет — вид; эллипсы — области рассеяния видов (стандартное отклонение); стрелки — исходные признаки. Используется scaling = 2 (корреляционный биплот): углы между стрелками отражают корреляции между признаками. Длины стрелок пропорционально увеличены для читаемости — важны их направления и относительные длины.

sp_colors <- c(setosa = "#E74C3C", versicolor = "#27AE60", virginica = "#2980B9")
pt_colors <- sp_colors[as.character(iris$Species)]

sites <- scores(pca, display = "sites",   scaling = 2, choices = 1:2)
vars  <- scores(pca, display = "species", scaling = 2, choices = 1:2)

# коэффициент, вписывающий стрелки в область объектов
mult <- 0.9 * min(max(abs(sites[, 1])) / max(abs(vars[, 1])),
                  max(abs(sites[, 2])) / max(abs(vars[, 2])))

lab <- sprintf("PC%d (%.1f %%)", 1:2, prop_var[1:2])

plot(sites, type = "n", asp = 1,
     xlab = lab[1], ylab = lab[2],
     xlim = range(c(sites[, 1], vars[, 1] * mult)) * 1.15,
     ylim = range(c(sites[, 2], vars[, 2] * mult)) * 1.15,
     main = "PCA (rda): ординационная диаграмма iris")
abline(h = 0, v = 0, lty = 3, col = "gray50")

points(sites, col = pt_colors, pch = 19, cex = 0.9)
ordiellipse(pca, groups = iris$Species, kind = "sd", scaling = 2,
            col = sp_colors, lwd = 2, label = FALSE)

arrows(0, 0, vars[, 1] * mult, vars[, 2] * mult,
       length = 0.1, col = "navy", lwd = 1.5)
label_pos <- c(Sepal.Length = 4, Sepal.Width = 1, Petal.Length = 3, Petal.Width = 1)
text(vars[, 1] * mult, vars[, 2] * mult, labels = rownames(vars),
     pos = label_pos[rownames(vars)], col = "navy", cex = 0.9, font = 2)

legend("topleft", legend = names(sp_colors), col = sp_colors,
       pch = 19, bty = "n", title = "Вид")

3.5 Нагрузки признаков

Нагрузки — вклад исходных признаков в главные компоненты (собственные векторы корреляционной матрицы, scaling = 0).

loadings_mat <- scores(pca, display = "species", scaling = 0, choices = 1:2)
round(loadings_mat, 3)
##                 PC1    PC2
## Sepal.Length  0.521 -0.377
## Sepal.Width  -0.269 -0.923
## Petal.Length  0.580 -0.024
## Petal.Width   0.565 -0.067
## attr(,"const")
## [1] 4.940963
cat("\nВклад признаков в PC1 (по убыванию |нагрузки|):\n")
## 
## Вклад признаков в PC1 (по убыванию |нагрузки|):
print(round(sort(abs(loadings_mat[, 1]), decreasing = TRUE), 3))
## Petal.Length  Petal.Width Sepal.Length  Sepal.Width 
##        0.580        0.565        0.521        0.269
cat("\nВклад признаков в PC2 (по убыванию |нагрузки|):\n")
## 
## Вклад признаков в PC2 (по убыванию |нагрузки|):
print(round(sort(abs(loadings_mat[, 2]), decreasing = TRUE), 3))
##  Sepal.Width Sepal.Length  Petal.Width Petal.Length 
##        0.923        0.377        0.067        0.024

3.6 Выводы по PCA

  1. Объяснённая дисперсия. Первая компонента объясняет 73 % вариации, вторая — 22.9 %; вместе 95.8 %. Четыре исходных признака можно заменить двумя компонентами почти без потери информации.
  2. Интерпретация PC1 — «общий размер цветка»: Petal.Length, Petal.Width и Sepal.Length имеют близкие по величине положительные нагрузки, поэтому чем больше значение PC1, тем крупнее цветок. Sepal.Width вносит в PC1 небольшой отрицательный вклад.
  3. Интерпретация PC2 определяется главным образом шириной чашелистика (Sepal.Width, |нагрузка| ≈ 0.92; знак нагрузок в PCA условен) — это компонента «формы» цветка, слабо связанная с общим размером.
  4. Разделение видов. Setosa полностью отделена от двух других видов вдоль PC1 (мелкие лепестки). Versicolor и virginica частично перекрываются, но virginica смещена в сторону больших значений PC1 (более крупные цветки).
  5. Стрелки. Petal.Length и Petal.Width почти параллельны и направлены в одну сторону — эти признаки сильно положительно коррелируют (r ≈ 0.96). Sepal.Width образует с остальными стрелками тупой угол — это отрицательная (и заметно более слабая) корреляция, например с Petal.Length r ≈ -0.43; этот признак несёт информацию, не сводимую к общему размеру цветка.

4 Информация о среде

sessionInfo()
## R version 4.6.1 (2026-06-24 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
## 
## 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] vegan_2.7-6    permute_0.9-10 e1071_1.7-17   caret_7.0-1    lattice_0.22-9
## [6] ggplot2_4.0.3  gmodels_2.19.1 class_7.3-23  
## 
## loaded via a namespace (and not attached):
##  [1] gtable_0.3.6         xfun_0.60            bslib_0.12.0        
##  [4] recipes_1.4.0        vctrs_0.7.3          tools_4.6.1         
##  [7] generics_0.1.4       stats4_4.6.1         parallel_4.6.1      
## [10] proxy_0.4-29         tibble_3.3.1         cluster_2.1.8.2     
## [13] ModelMetrics_1.2.2.2 pkgconfig_2.0.3      Matrix_1.7-5        
## [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] sass_0.4.10          yaml_2.3.12          prodlim_2026.03.11  
## [28] pillar_1.11.1        jquerylib_0.1.4      MASS_7.3-65         
## [31] gdata_3.0.1          cachem_1.1.0         gower_1.0.2         
## [34] 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          
## [40] gtools_3.9.5         tidyselect_1.2.1     digest_0.6.39       
## [43] stringi_1.8.9        future_1.76.0        dplyr_1.2.1         
## [46] reshape2_1.4.5       purrr_1.2.2          listenv_1.0.0       
## [49] splines_4.6.1        fastmap_1.2.0        grid_4.6.1          
## [52] cli_3.6.6            magrittr_2.0.5       survival_3.8-6      
## [55] future.apply_1.20.2  withr_3.0.3          scales_1.4.0        
## [58] lubridate_1.9.5      timechange_0.4.0     rmarkdown_2.32      
## [61] globals_0.19.1       otel_0.2.0           nnet_7.3-20         
## [64] timeDate_4052.112    evaluate_1.0.5       knitr_1.52          
## [67] hardhat_1.4.3        mgcv_1.9-4           rlang_1.3.0         
## [70] 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      
## [76] R6_2.6.1             plyr_1.8.9

5 Литература

  1. Classification using K-Nearest Neighbors in R. https://en.proft.me/2017/01/22/classification-using-k-nearest-neighbors-r/
  2. Открытый курс машинного обучения (ODS), метрики качества классификации. https://habr.com/ru/company/ods/blog/328372/
  3. Шитиков В.К., Мастицкий С.Э. Классификация, регрессия и другие алгоритмы Data Mining с использованием R. 2017. — 351 с. https://github.com/ranalytics/data-mining