kNN en R : k plus proches voisins avec tidymodels

Classez par le vote majoritaire des points les plus proches — standardisez, puis laissez la validation croisée choisir k

Un guide pratique de l’algorithme des k plus proches voisins (kNN) en R. Découvrez comment kNN prédit par le vote majoritaire (classification) ou la moyenne (régression) des k points d’entraînement les plus proches, pourquoi vous devez d’abord standardiser les prédicteurs, et comment choisir k par validation croisée — un petit k surajuste, un grand k lisse à l’excès. Construit à la manière moderne de tidymodels avec parsnip + kknn, sur les données PimaIndiansDiabetes2, avec en prime une note sur la régression appliquée aux données de logement de Boston.

Date de publication

25 juin 2026

Modifié

7 juillet 2026

AstucePoints clés à retenir
  • Les k plus proches voisins (kNN) sont la méthode d’apprentissage automatique la plus simple : pour prédire un nouveau cas, on trouve les k points les plus proches du jeu d’entraînement et on les laisse voter. En classification, la prédiction est la classe majoritaire parmi les voisins ; en régression, c’est la moyenne de leur résultat.
  • kNN ne fait aucun véritable entraînement — il se contente de stocker les données et de mesurer les distances au moment de la prédiction (un apprenant « paresseux »). Le modèle entier, c’est les données plus un choix de k et de distance.
  • La distance est sensible à l’échelle, vous devez donc d’abord standardiser les prédicteurs — sans quoi une variable exprimée en grandes unités (glucose) écrase une variable en petites unités (pedigree). step_normalize().
  • k est le seul bouton de réglage, et il contrôle le compromis biais–variance. Un petit k (k = 1) surajuste — chaque point bruité fait basculer la décision ; un grand k lisse à l’excès — la prédiction se brouille vers la majorité globale. Choisissez k par validation croisée.
  • La manière moderne : nearest_neighbor(neighbors = tune()) |> set_engine("kknn") dans un workflow avec step_normalize, réglé sur k par vfold_cv(). La courbe précision-vs-k est le graphique emblématique — elle montre directement le point optimal.
  • Sur les données de diabète Pima, la validation croisée retient k ≈ 11, ce qui donne environ 75 % de précision sur le test — une base de référence solide et transparente que vous pouvez expliquer à n’importe qui.
Obtenez le livre — Machine Learning Essentials (PDF)

Introduction

Vous disposez d’un jeu de données clinique et d’une question fermée à laquelle répondre — un nouveau patient sera-t-il testé positif au diabète ? — à partir d’une poignée de mesures. L’une des façons les plus intuitives d’y répondre ne nécessite presque aucune théorie : trouvez dans vos dossiers les patients qui ressemblent le plus à ce nouveau cas, et voyez ce qui leur est arrivé. Si la plupart des 11 patients les plus semblables étaient positifs au diabète, prédisez positif. Voilà l’algorithme des k plus proches voisins (kNN) en une phrase.

kNN est fondé sur la distance et non paramétrique — il n’ajuste aucune équation, n’estime aucun coefficient et ne fait aucune hypothèse sur la forme de la relation. Il se contente de mémoriser les données d’entraînement et, lorsqu’on lui demande de prédire, mesure à quel point le nouveau cas est proche de chaque point stocké, garde les k plus proches et les laisse décider. Comme il n’y a aucun modèle à ajuster, on l’appelle un apprenant paresseux : tout le travail se fait au moment de la prédiction. La même idée fonctionne pour la classification (les voisins votent pour une classe) et la régression (les résultats des voisins sont moyennés).

Deux choses font ou défont un modèle kNN, et cette leçon est construite autour d’elles : vous devez standardiser les prédicteurs (la distance n’a aucun sens lorsque les variables sont sur des échelles radicalement différentes), et vous devez bien choisir k (trop petit et il mémorise le bruit ; trop grand et il brouille tout). Nous le construisons à la manière moderne de tidymodelsparsnip + kknn, le recipe se chargeant de la standardisation et la validation croisée du choix — sur les mêmes données de diabète Pima que le reste de ce pilier utilise, puis nous concluons par un court exemple de régression sur les données de logement de Boston.

NoteQuand y recourir

Recourez à kNN lorsque vous voulez une base de référence simple et transparente sans hypothèse sur la forme fonctionnelle, que les prédicteurs sont numériques (ou raisonnablement encodés) et standardisés, et que le jeu de données n’est pas énorme (kNN compare le nouveau cas à chaque point d’entraînement, il devient donc lent sur de grandes données). Il brille comme premier modèle à battre et comme outil pédagogique ; pour des problèmes plus grands ou plus complexes, comparez-le à un arbre de décision ou aux méthodes de la série classification.

Les données

Nous utilisons le jeu de données PimaIndiansDiabetes2 du paquet mlbench : 768 femmes d’ascendance amérindienne Pima, dans le but de prédire le diabetes (un résultat binaire pos/neg) à partir de huit prédicteurs cliniques — nombre de grossesses (pregnant), glucose plasmatique, pressure artérielle, épaisseur du pli cutané du triceps, insulin sérique, indice de masse corporelle (mass, IMC), la fonction pedigree du diabète et l’age.

Cette version comporte des valeurs manquantes (enregistrées sous forme de NA lorsqu’une mesure est invraisemblable, p. ex. un glucose nul). kNN mesure les distances entre points complets, c’est pourquoi — en suivant l’analyse source — nous supprimons les lignes contenant des NA, ce qui laisse 392 enregistrements complets. Nous séparons ensuite en un jeu d’entraînement (80 %, la « mémoire » que le modèle parcourt) et un jeu de test (20 %, mis de côté pour juger honnêtement le classifieur final), stratifié sur diabetes afin que les deux ensembles conservent le même équilibre positif/négatif, et nous fixons une graine pour la reproductibilité :

library(rsample)
data("PimaIndiansDiabetes2", package = "mlbench")

# Drop rows with missing measurements (distances need complete cases)
pima <- na.omit(PimaIndiansDiabetes2)

set.seed(123)
split      <- initial_split(pima, prop = 0.80, strata = diabetes)
train_data <- training(split)
test_data  <- testing(split)

c(complete = nrow(pima), train = nrow(train_data), test = nrow(test_data))
complete    train     test 
     392      313       79 
# Class balance is preserved by the stratified split
prop.table(table(train_data$diabetes))

      neg       pos 
0.6677316 0.3322684 
head(pima, 4)
  pregnant glucose pressure triceps insulin mass pedigree age diabetes
4        1      89       66      23      94 28.1    0.167  21      neg
5        0     137       40      35     168 43.1    2.288  33      pos
7        3      78       50      32      88 31.0    0.248  26      pos
9        2     197       70      45     543 30.5    0.158  53      pos

Soit 313 patients pour servir de mémoire au modèle, 79 mis de côté pour faire office de juge, et 8 prédicteurs pour mesurer la distance. Environ un tiers des patients sont positifs au diabète — un problème de classification modérément déséquilibré mais exploitable.

Pourquoi vous devez d’abord standardiser

Toute la notion de « plus proche » de kNN est une distance — par défaut la distance en ligne droite (euclidienne) entre deux patients sur les huit prédicteurs. Le piège : la distance est dominée par la variable qui possède la plus grande amplitude numérique, indépendamment de son importance. glucose se compte en centaines tandis que pedigree est une fraction inférieure à 1 ; ainsi, sans remise à l’échelle, la distance est essentiellement une distance en glucose et pedigree est ignoré — non parce qu’il est sans importance, mais à cause de ses unités.

La solution consiste à mettre chaque prédicteur sur une échelle commune avant de mesurer la distance. Dans un recipe tidymodels, c’est une seule étape — step_normalize() centre et réduit chaque prédicteur à une moyenne de 0 et un écart-type de 1, en apprenant le centrage et la réduction uniquement sur les données d’entraînement afin qu’aucune information de test ne fuite :

library(recipes)
library(rsample)
data("PimaIndiansDiabetes2", package = "mlbench")
pima <- na.omit(PimaIndiansDiabetes2)
set.seed(123)
split      <- initial_split(pima, prop = 0.80, strata = diabetes)
train_data <- training(split)

# Standardize every numeric predictor — the single most important kNN step
knn_rec <- recipe(diabetes ~ ., data = train_data) |>
  step_normalize(all_numeric_predictors())

knn_rec

Le recipe ne transforme encore rien — il déclare le prétraitement, et le workflow l’appliquera de manière identique aux plis d’entraînement et au jeu de test mis de côté. Sauter cette étape est la première manière d’obtenir un mauvais modèle kNN, et c’est silencieux : le code s’exécute, la réponse est simplement fausse, sans le moindre signe.

Le workflow tidymodels : régler k par validation croisée

Dans tidymodels, la spécification du modèle kNN est parsnip::nearest_neighbor(), avec le moteur "kknn". Son argument clé est neighbors — le k que nous voulons choisir. Nous le marquons neighbors = tune() afin que la validation croisée le sélectionne, fixons le mode à "classification" (pour que les voisins votent), et regroupons le recipe et la spécification dans un workflow :

library(parsnip)
library(workflows)
library(recipes)
library(rsample)
data("PimaIndiansDiabetes2", package = "mlbench")
pima <- na.omit(PimaIndiansDiabetes2)
set.seed(123)
split      <- initial_split(pima, prop = 0.80, strata = diabetes)
train_data <- training(split)

knn_rec  <- recipe(diabetes ~ ., data = train_data) |>
  step_normalize(all_numeric_predictors())

# k left to be tuned; "kknn" is the engine; vote on a class -> classification
knn_spec <- nearest_neighbor(neighbors = tune()) |>
  set_engine("kknn") |>
  set_mode("classification")

knn_wf <- workflow() |>
  add_recipe(knn_rec) |>
  add_model(knn_spec)

knn_wf
══ Workflow ════════════════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: nearest_neighbor()

── Preprocessor ────────────────────────────────────────────────────────────────
1 Recipe Step

• step_normalize()

── Model ───────────────────────────────────────────────────────────────────────
K-Nearest Neighbor Model Specification (classification)

Main Arguments:
  neighbors = tune()

Computational engine: kknn 

Maintenant nous ne devinons pas k — nous évaluons toute une gamme de valeurs candidates par validation croisée à 10 plis sur le jeu d’entraînement (stratifié sur le résultat) et laissons les données choisir. Nous essayons des k impairs de 1 à 39 (les valeurs impaires évitent les votes à égalité dans un problème à deux classes) et évaluons chacune par la précision et l’AUC ROC :

library(parsnip)
library(workflows)
library(recipes)
library(rsample)
library(tune)
library(yardstick)
data("PimaIndiansDiabetes2", package = "mlbench")
pima <- na.omit(PimaIndiansDiabetes2)
set.seed(123)
split      <- initial_split(pima, prop = 0.80, strata = diabetes)
train_data <- training(split)

knn_rec  <- recipe(diabetes ~ ., data = train_data) |>
  step_normalize(all_numeric_predictors())
knn_spec <- nearest_neighbor(neighbors = tune()) |>
  set_engine("kknn") |> set_mode("classification")
knn_wf   <- workflow() |> add_recipe(knn_rec) |> add_model(knn_spec)

# 10-fold CV on the training set, over a grid of k values
set.seed(123)
folds <- vfold_cv(train_data, v = 10, strata = diabetes)
k_grid <- data.frame(neighbors = seq(1, 39, by = 2))

set.seed(123)
knn_tuned <- tune_grid(
  knn_wf, resamples = folds, grid = k_grid,
  metrics = metric_set(accuracy, roc_auc)
)

# The k with the highest cross-validated accuracy
best_k <- select_best(knn_tuned, metric = "accuracy")
best_k
# A tibble: 1 × 2
  neighbors .config         
      <dbl> <chr>           
1        11 pre0_mod06_post0

tune_grid() a validé chaque k candidat par validation croisée ; select_best() renvoie le gagnant. Ici la validation croisée choisit k = 11 — assez petit pour suivre la structure locale des données, assez grand pour moyenner le bruit qu’une poignée de voisins porterait.

La courbe précision-vs-k (le graphique emblématique)

L’image la plus utile en kNN est la précision validée par validation croisée en fonction de k. Elle dessine tout le compromis biais–variance en une seule ligne — et montre pourquoi le k choisi est le bon :

library(parsnip)
library(workflows)
library(recipes)
library(rsample)
library(tune)
library(yardstick)
library(ggplot2)
data("PimaIndiansDiabetes2", package = "mlbench")
pima <- na.omit(PimaIndiansDiabetes2)
set.seed(123)
split      <- initial_split(pima, prop = 0.80, strata = diabetes)
train_data <- training(split)

knn_rec  <- recipe(diabetes ~ ., data = train_data) |>
  step_normalize(all_numeric_predictors())
knn_spec <- nearest_neighbor(neighbors = tune()) |>
  set_engine("kknn") |> set_mode("classification")
knn_wf   <- workflow() |> add_recipe(knn_rec) |> add_model(knn_spec)

set.seed(123)
folds <- vfold_cv(train_data, v = 10, strata = diabetes)
k_grid <- data.frame(neighbors = seq(1, 39, by = 2))
set.seed(123)
knn_tuned <- tune_grid(knn_wf, resamples = folds, grid = k_grid,
                       metrics = metric_set(accuracy, roc_auc))

# Pull the accuracy of every candidate k
acc_curve <- subset(collect_metrics(knn_tuned), .metric == "accuracy")
best_k    <- select_best(knn_tuned, metric = "accuracy")

ggplot(acc_curve, aes(x = neighbors, y = mean)) +
  geom_line(color = "#3a86d4", linewidth = 1) +
  geom_point(color = "#3a86d4", size = 1.8) +
  geom_vline(xintercept = best_k$neighbors, linetype = "dashed", color = "gray40") +
  labs(
    x = "Number of neighbors (k)",
    y = "Cross-validated accuracy",
    title = "Choosing k by cross-validation",
    subtitle = "Tiny k overfits (jagged, left); large k over-smooths (drifts down, right). Dashed = best k."
  ) +
  theme_minimal()

Cross-validated accuracy plotted against the number of neighbors k for the kNN classifier on the Pima diabetes data: accuracy is low and jagged at very small k where the model overfits, rises to a peak around k = 11, then drifts gently downward as k grows and the model over-smooths. A dashed vertical line marks the chosen k.

Lisez-la de gauche à droite. À k = 1 (à l’extrême gauche) le modèle classe un patient par son unique voisin le plus proche — il suit chaque ondulation des données d’entraînement, bruit compris, si bien que sa précision validée par validation croisée est faible et irrégulière (surajustement : variance élevée). À mesure que k croît, le vote moyenne sur davantage de voisins, le bruit s’annule et la précision grimpe jusqu’à un pic autour de k = 11. Poussez k plus haut et la précision redescend peu à peu — avec trop de voisins la prédiction se brouille vers la majorité globale des classes et perd le signal local (lissage excessif : biais élevé). La ligne en pointillés est le choix de la validation croisée — le point optimal entre les deux écueils.

Finaliser et évaluer sur le jeu de test

Nous finalisons le workflow avec le k choisi et last_fit() — ajustement sur toutes les données d’entraînement, puis évaluation une seule fois sur le jeu de test intact pour une estimation honnête, hors échantillon :

library(parsnip)
library(workflows)
library(recipes)
library(rsample)
library(tune)
library(yardstick)
data("PimaIndiansDiabetes2", package = "mlbench")
pima <- na.omit(PimaIndiansDiabetes2)
set.seed(123)
split      <- initial_split(pima, prop = 0.80, strata = diabetes)
train_data <- training(split)

knn_rec  <- recipe(diabetes ~ ., data = train_data) |>
  step_normalize(all_numeric_predictors())
knn_spec <- nearest_neighbor(neighbors = tune()) |>
  set_engine("kknn") |> set_mode("classification")
knn_wf   <- workflow() |> add_recipe(knn_rec) |> add_model(knn_spec)

set.seed(123)
folds <- vfold_cv(train_data, v = 10, strata = diabetes)
k_grid <- data.frame(neighbors = seq(1, 39, by = 2))
set.seed(123)
knn_tuned <- tune_grid(knn_wf, resamples = folds, grid = k_grid,
                       metrics = metric_set(accuracy, roc_auc))
best_k <- select_best(knn_tuned, metric = "accuracy")

# Finalize with the chosen k, fit on train, score ONCE on test
final_fit <- finalize_workflow(knn_wf, best_k) |> last_fit(split)
collect_metrics(final_fit)
# A tibble: 3 × 4
  .metric     .estimator .estimate .config        
  <chr>       <chr>          <dbl> <chr>          
1 accuracy    binary         0.747 pre0_mod0_post0
2 roc_auc     binary         0.793 pre0_mod0_post0
3 brier_class binary         0.171 pre0_mod0_post0

Lisez les chiffres (la performance honnête et hors échantillon de votre classifieur) :

  • Précision ≈ 0.75 — avec k = 11, le modèle prédit correctement le diagnostic de diabète environ 75 % du temps sur des patients qu’il n’a jamais vus pendant la construction du modèle. Pour une méthode aussi simple, c’est une base de référence respectable.
  • AUC ROC ≈ 0.79 — la probabilité que le modèle classe un patient positif au hasard au-dessus d’un patient négatif au hasard (0.5 c’est un tirage à pile ou face, 1.0 c’est parfait). L’AUC est indépendante du seuil, donc elle juge le classement du modèle plutôt que le seuil arbitraire de 0.5 — lisez-la conjointement avec la précision, surtout sur un résultat déséquilibré où la précision peut flatter un modèle qui se contente de prédire la classe majoritaire.

Pour voir quels cas il prédit correctement et incorrectement — faux positifs vs faux négatifs — lisez la matrice de confusion complète et la courbe ROC dans évaluation des modèles de classification.

kNN pour la régression : changez un seul argument

kNN prédit un nombre aussi naturellement qu’une classe — au lieu de voter, les résultats des k voisins sont moyennés. Dans tidymodels, le seul changement est set_mode("regression") ; tout le reste (standardiser, régler k par CV) est identique. Ici nous prédisons la valeur médiane des logements (medv) dans les banlieues de Boston à partir des données Boston (MASS), en évaluant par la RMSE (racine de l’erreur quadratique moyenne, dans les unités du résultat lui-même) — plus elle est basse, mieux c’est :

library(parsnip)
library(workflows)
library(recipes)
library(rsample)
library(tune)
library(yardstick)
data("Boston", package = "MASS")

set.seed(123)
split      <- initial_split(Boston, prop = 0.80, strata = medv)
train_data <- training(split)

# Same recipe + spec, but mode = "regression" -> neighbors are AVERAGED
boston_rec  <- recipe(medv ~ ., data = train_data) |>
  step_normalize(all_numeric_predictors())
boston_spec <- nearest_neighbor(neighbors = tune()) |>
  set_engine("kknn") |> set_mode("regression")
boston_wf   <- workflow() |> add_recipe(boston_rec) |> add_model(boston_spec)

set.seed(123)
folds  <- vfold_cv(train_data, v = 10)
k_grid <- data.frame(neighbors = 1:20)
set.seed(123)
boston_tuned <- tune_grid(boston_wf, resamples = folds, grid = k_grid,
                          metrics = metric_set(rmse, rsq))
best_k <- select_best(boston_tuned, metric = "rmse")

final_fit <- finalize_workflow(boston_wf, best_k) |> last_fit(split)
c(best_k = best_k$neighbors)
best_k 
     4 
collect_metrics(final_fit)
# A tibble: 2 × 4
  .metric .estimator .estimate .config        
  <chr>   <chr>          <dbl> <chr>          
1 rmse    standard       3.82  pre0_mod0_post0
2 rsq     standard       0.840 pre0_mod0_post0

La validation croisée retient ici un petit k ≈ 4, donnant une RMSE sur le jeu de test d’environ 3.8 (milliers de dollars) et un R² proche de 0.84 — kNN prédit donc la valeur d’un logement typique à environ 3 800 $ près et explique environ 84 % de la variation de medv sur de nouvelles banlieues. Le recipe se charge toujours de la standardisation ; seuls la règle de prédiction (moyenne, pas vote) et la métrique (RMSE/R², pas précision/AUC) changent. Même algorithme, deux tâches.

kNN classe les points d’entraînement par distance au nouveau cas. Par défaut c’est la distance euclidienne : pour deux points \(x\) et \(z\) sur \(p\) prédicteurs standardisés,

\[ d(x, z) = \sqrt{\sum_{j=1}^{p} (x_j - z_j)^2}. \]

La somme parcourt chaque prédicteur, donc un prédicteur sur une grande échelle apporte une grande différence au carré et domine \(d\) — ce qui explique précisément pourquoi chaque \(x_j\) doit d’abord être standardisé (afin que chaque prédicteur contribue de façon comparable). En classification, la prédiction est la classe la plus fréquente parmi les \(k\) points les plus proches ; en régression, c’est leur résultat moyen \(\hat{y} = \frac{1}{k}\sum_{i \in N_k} y_i\), où \(N_k\) est l’ensemble des \(k\) voisins. (Le moteur kknn peut aussi pondérer davantage les voisins proches que les plus éloignés, un raffinement par rapport au vote simple.)

Le choix de \(k\) est une molette biais–variance. À \(k = 1\) la frontière de décision se plie autour de chaque point : biais faible, variance élevée — elle ajuste le bruit (surajustement), et l’erreur d’entraînement est de 0 tandis que l’erreur de test peut être élevée. À mesure que \(k\) croît, la frontière se lisse : la variance baisse mais le biais augmente, jusqu’à ce qu’à très grand \(k\) chaque cas soit prédit comme la majorité globale (régression : la moyenne globale). La courbe de validation croisée ci-dessus trouve le \(k\) qui minimise leur somme — l’erreur de test honnête.

Problèmes courants

  • Vous avez oublié de standardiser les prédicteurs. C’est l’erreur kNN. Sans step_normalize(all_numeric_predictors()), la distance est dominée par la variable de plus grande échelle et le modèle ignore discrètement le reste. Le code s’exécute quand même — la réponse est simplement fausse. Standardisez toujours.
  • Vous avez utilisé un k pair dans un problème à deux classes. Un k pair peut produire un vote à égalité (p. ex. 4 contre 4), que le moteur doit trancher arbitrairement. Réglez sur des k impairs pour la classification binaire, comme le fait la grille seq(1, 39, by = 2) ci-dessus.
  • k est trop petit (surajustement) ou trop grand (lissage excessif). k = 1 mémorise le bruit (parfait sur l’entraînement, mauvais sur le test) ; un k énorme prédit simplement partout la classe majoritaire. Ne choisissez pas k à la main — laissez la validation croisée le choisir, et lisez la courbe précision-vs-k pour confirmer qu’il existe un vrai pic.
  • kNN est lent sur de grandes données ou des données larges. Comme il compare chaque nouveau cas à chaque point d’entraînement, à la fois un grand n et de nombreux prédicteurs (le « fléau de la dimension » gonfle les distances) le pénalisent. Réduisez les prédicteurs par ingénierie des caractéristiques ou optez pour un modèle plus rapide sur de grandes données.
  • Les prédicteurs catégoriels cassent la distance. La distance euclidienne a besoin de nombres. Encodez d’abord les facteurs — step_dummy(all_nominal_predictors()) dans le recipe — et standardisez ensuite, afin que les colonnes indicatrices ne submergent pas les variables continues.

Questions fréquentes

La manière moderne, c’est tidymodels : nearest_neighbor(neighbors = tune()) |> set_engine("kknn") |> set_mode("classification"), à l’intérieur d’un workflow() avec step_normalize(all_numeric_predictors()), puis réglez le nombre de voisins avec tune_grid() sur des plis vfold_cv() et select_best(). Standardisez d’abord les prédicteurs — kNN est fondé sur la distance et sensible à l’échelle — et laissez la validation croisée choisir k.

Les k plus proches voisins prédisent un nouveau cas à partir des k points les plus proches dans vos données d’entraînement. En classification, on prend le vote majoritaire des classes de ces k voisins ; en régression, on prend leur résultat moyen. Il n’y a aucune équation à ajuster — kNN se contente de stocker les données et de mesurer les distances au moment de la prédiction, d’où son nom d’apprenant paresseux. Les deux choses qui comptent sont la standardisation des prédicteurs et le choix de k.

Fixez le mode à classification pour que les voisins votent : nearest_neighbor(neighbors = tune()) |> set_engine("kknn") |> set_mode("classification"). Placez-le dans un workflow avec un recipe qui appelle step_normalize(all_numeric_predictors()), réglez k par validation croisée à 10 plis (tune_grid() + metric_set(accuracy, roc_auc)), choisissez le meilleur k avec select_best(), puis finalize_workflow() et last_fit() pour évaluer le jeu de test mis de côté. Sur les données de diabète Pima, le meilleur k est d’environ 11, pour environ 75 % de précision sur le test.

Utilisez le même workflow mais set_mode("regression") — les résultats des k voisins sont moyennés au lieu d’être votés. Réglez k par validation croisée en évaluant la RMSE (et le R²) au lieu de la précision : metric_set(rmse, rsq). Sur les données de logement Boston, un petit k (~4) donne une RMSE de test proche de 3.8 et un R² autour de 0.84. Le recipe standardise toujours les prédicteurs — cette étape est non négociable dans les deux modes.

Ne devinez pas — validez par validation croisée. Marquez neighbors = tune(), construisez une grille de k candidats (valeurs impaires pour les problèmes à deux classes, afin d’éviter les votes à égalité), évaluez chacune par vfold_cv(), et tracez la précision validée par validation croisée (ou la RMSE) en fonction de k. Un petit k surajuste (irrégulier, variance élevée) ; un grand k lisse à l’excès (dérive vers la majorité, biais élevé). Le k au pic de la courbe — choisi avec select_best() — est l’honnête point optimal.

Testez vos connaissances

Construisez un modèle de régression kNN sur les données Boston (MASS), prédisant medv à partir de toutes les autres variables. Séparez en 80/20 stratifié sur medv, normalisez les prédicteurs dans un recipe, réglez neighbors sur k = 1 à 20 par validation croisée à 10 plis sur la RMSE, et rapportez la RMSE et le R² sur le jeu de test. Puis changez une seule chose — set_mode("classification") ne s’appliquera pas ici, mais demandez-vous : quelle est la seule différence de code entre ceci et le classifieur de diabète de la leçon ?

La forme est identique au workflow de classification de la leçon — remplacez set_mode("classification") par set_mode("regression"), changez les données et la formule en medv ~ ., et évaluez avec metric_set(rmse, rsq) au lieu de précision/AUC. Vous aurez besoin de rsample, recipes, parsnip, workflows, tune et yardstick.

library(rsample); library(recipes); library(parsnip)
library(workflows); library(tune); library(yardstick)
data("Boston", package = "MASS")

set.seed(123)
split      <- initial_split(Boston, prop = 0.80, strata = medv)
train_data <- training(split)

rec  <- recipe(medv ~ ., data = train_data) |> step_normalize(all_numeric_predictors())
spec <- nearest_neighbor(neighbors = tune()) |> set_engine("kknn") |> set_mode("regression")
wf   <- workflow() |> add_recipe(rec) |> add_model(spec)

set.seed(123); folds <- vfold_cv(train_data, v = 10)
set.seed(123)
tuned <- tune_grid(wf, resamples = folds, grid = data.frame(neighbors = 1:20),
                   metrics = metric_set(rmse, rsq))
best  <- select_best(tuned, metric = "rmse")

collect_metrics(finalize_workflow(wf, best) |> last_fit(split))

Vous obtiendrez un meilleur k autour de 4, une RMSE de test proche de 3.8 et un R² autour de 0.84. La seule différence par rapport au classifieur de diabète est set_mode("regression") (moyenne vs vote) et l’ensemble de métriques (RMSE/R² vs précision/AUC) — le même algorithme accomplissant une tâche différente.

Vérification rapide. Vous ajustez un classifieur kNN avec k = 1 et il obtient 100 % de précision sur le jeu d’entraînement mais seulement 68 % sur le jeu de test. Un collègue en conclut que le modèle est excellent. Que se passe-t-il réellement, et que devriez-vous faire ?

Le modèle est en surajustement. Avec k = 1, chaque point d’entraînement est son propre voisin le plus proche, donc la prédiction d’entraînement est toujours l’étiquette de ce point lui-même — 100 % de précision d’entraînement est garanti et ne vous apprend rien. Le chiffre honnête est la précision de test (ou validée par validation croisée), 68 %. La solution consiste à augmenter k et à le choisir par validation croisée : un k plus grand moyenne sur davantage de voisins, annulant le bruit que k = 1 a mémorisé, et la courbe précision-vs-k montrera le vrai point optimal (autour de k = 11 sur ces données). Ne jugez jamais un modèle sur les données à partir desquelles il a été construit.

Conclusion

Vous avez construit les k plus proches voisins de bout en bout sur les données PimaIndiansDiabetes2 : kNN prédit un nouveau cas à partir des k points les plus proches du jeu d’entraînement — un vote majoritaire pour la classification, une moyenne pour la régression — sans aucune équation à ajuster. Les deux décisions qui le font fonctionner sont la standardisation des prédicteurs (la distance est sensible à l’échelle, donc step_normalize() est non négociable) et le choix de k par validation croisée (un petit k surajuste, un grand k lisse à l’excès ; la courbe précision-vs-k montre le point optimal, ici k ≈ 11 pour environ 75 % de précision sur le test). Passer à la régression a changé exactement un argument (set_mode("regression")), prouvant que l’algorithme est une seule idée au service de deux tâches. La règle pratique : standardisez d’abord, laissez la validation croisée choisir k, et lisez la courbe pour confirmer qu’il existe un vrai pic.

Ensuite, découvrez comment lire la matrice de confusion complète, la ROC et l’AUC dans évaluation des modèles de classification, ou comparez kNN à un modèle fondé sur des règles dans arbres de décision.

Leçons connexes

🟢 Avec un agent IA

Vous avez un problème de classification ou de régression et vous voulez une base de référence simple et transparente ? Demandez à Prova « ajuste un modèle kNN à ces données, standardise les prédicteurs et règle le nombre de voisins par validation croisée » — elle répond avec du code tidymodels + kknn que vous pouvez exécuter sur vos propres données, puis vous aide à lire la courbe précision-vs-k et à décider si kNN est assez bon ou s’il vous faut un modèle plus puissant. The runtime is the judge. Ask Prova →

Vous préférez un livre ? Machine Learning Essentials est disponible en PDF téléchargeable — toutes les leçons de cette série, hors ligne et à vous.

Obtenir le livre (PDF) →Les leçons restent gratuites en ligne.
Cette page vous a-t-elle été utile ?

Prouvez que vous savez le faire. Maîtrisez toute la série Apprentissage statistique en R — suivez votre parcours, construisez des projets et obtenez un certificat.

Commencer gratuitement →

Passez à Pro — Prova illimité sur vos propres données et un certificat vérifiable qui atteste la compétence.

dès 15 $/mois facturé annuellement

Passer à Pro →

✓ Vous êtes Pro — continuez. The runtime is the judge.

Recevez les nouvelles leçons R & Python par e-mail

Pratique, reproductible, sans spam. Désinscription à tout moment.

Double opt-in. Nous ne partageons jamais votre e-mail.

Partager cette pageXLinkedInRedditHN
Note

Cette leçon est reproductible : chaque métrique et chaque figure ont été produites par le code montré — copiez n’importe quel bloc et exécutez-le pour les reproduire. The runtime is the judge.

Références

  • James, G., Witten, D., Hastie, T., & Tibshirani, R. (2014). An Introduction to Statistical Learning, with Applications in R. Springer. (Chapitre 2 — Statistical Learning ; Chapitre 4 — Classification.)
  • Hechenbichler, K., & Schliep, K. (2004). Weighted k-Nearest-Neighbor Techniques and Ordinal Classification. (Le paquet kknn.)
  • Kuhn, M., & Silge, J. Tidy Modeling with R. Disponible gratuitement en ligne (CC BY-NC-SA).

Réutilisation

Citation

BibTeX
@online{2026,
  author = {},
  title = {kNN en R : k plus proches voisins avec tidymodels},
  date = {2026-06-25},
  url = {https://www.datanovia.com/learn/machine-learning/statistical-learning/knn},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
“kNN en R : k plus proches voisins avec tidymodels.” 2026. June 25. https://www.datanovia.com/learn/machine-learning/statistical-learning/knn.