7  Partie 7 : Comparaison, Pistes et Conclusion

8 Comparaison des quatre modèles

8.1 Récapitulatif des performances

Code
kable(data.frame(
  Modele = c("Rég. Logistique (LASSO)", "Arbre de Décision",
             "Forêt Aléatoire", "kNN (k=51)"),
  AUC_Test = c(round(auc(roc_logistic), 4), round(auc(roc_tree), 4),
               round(auc(roc_rf), 4), round(auc(roc_knn), 4)),
  Accuracy = c(round(cm_logistic$overall["Accuracy"], 4),
               round(cm_tree$overall["Accuracy"], 4),
               round(cm_rf$overall["Accuracy"], 4),
               round(cm_knn$overall["Accuracy"], 4)),
  Sensibilite = c(round(cm_logistic$byClass["Sensitivity"], 4),
                  round(cm_tree$byClass["Sensitivity"], 4),
                  round(cm_rf$byClass["Sensitivity"], 4),
                  round(cm_knn$byClass["Sensitivity"], 4)),
  Specificite = c(round(cm_logistic$byClass["Specificity"], 4),
                  round(cm_tree$byClass["Specificity"], 4),
                  round(cm_rf$byClass["Specificity"], 4),
                  round(cm_knn$byClass["Specificity"], 4)),
  F1 = c(round(cm_logistic$byClass["F1"], 4),
         round(cm_tree$byClass["F1"], 4),
         round(cm_rf$byClass["F1"], 4),
         round(cm_knn$byClass["F1"], 4))
))
Table 8.1: Comparaison des quatre modèles supervisés
Modele AUC_Test Accuracy Sensibilite Specificite F1
Rég. Logistique (LASSO) 0.6693 0.6250 0.6354 0.6142 0.6323
Arbre de Décision 0.6488 0.6146 0.5939 0.6358 0.6099
Forêt Aléatoire 0.6926 0.6333 0.6551 0.6108 0.6445
kNN (k=51) 0.6541 0.6114 0.5871 0.6364 0.6052

La hiérarchie des performances est claire : Forêt Aléatoire > Régression Logistique > kNN > Arbre de Décision. L’écart entre la forêt aléatoire et le kNN (3.9 points d’AUC) est substantiel. Le gain de la forêt par rapport à l’arbre seul (4.4 points) illustre concrètement l’avantage du bagging.

8.2 Courbes ROC comparées

Code
draw_roc <- function(roc_obj, col, lty = 1, lwd = 2) {
  cc  <- coords(roc_obj, "all", ret = c("specificity", "sensitivity"))
  fpr <- 1 - cc$specificity
  ord <- order(fpr)
  lines(fpr[ord], cc$sensitivity[ord], col = col, lwd = lwd, lty = lty)
}
plot(NULL, xlim = c(0, 1), ylim = c(0, 1),
     xlab = "1 - Spécificité", ylab = "Sensibilité",
     main = "Courbes ROC comparées")
abline(a = 0, b = 1, lty = 2, col = "gray")
draw_roc(roc_logistic, COL_PRIMARY)
draw_roc(roc_tree,     COL_TREE, lty = 2)
draw_roc(roc_rf,       COL_POSITIVE)
draw_roc(roc_knn,      COL_KNN)
legend("bottomright",
       legend = c(
         paste0("Logistique (",   round(auc(roc_logistic), 3), ")"),
         paste0("Arbre (",        round(auc(roc_tree),     3), ")"),
         paste0("Forêt Aléat. (", round(auc(roc_rf),       3), ")"),
         paste0("kNN k=51 (",     round(auc(roc_knn),      3), ")")
       ),
       col = c(COL_PRIMARY, COL_TREE, COL_POSITIVE, COL_KNN),
       lwd = 2, lty = c(1, 2, 1, 1), cex = 0.7)
Figure 8.1: Courbes ROC comparées — quatre modèles supervisés

Les quatre courbes ROC (Figure 8.1) s’écartent de la diagonale (hasard pur), confirmant une discrimination réelle. La forêt aléatoire domine sur toute la plage de seuils. La comparaison arbre/forêt est particulièrement instructive : la courbe de l’arbre seul (pointillée) est plus irrégulière et systématiquement en-dessous de la forêt, illustrant le gain du bagging. L’AUC la plus élevée (forêt aléatoire, 0.693) signifie que le modèle classe correctement une paire aléatoire (satisfaisant, non-satisfaisant) dans 69 % des cas (Fawcett, 2006) — modérément supérieur au hasard (50 %), mais significatif sur 7218 observations test.

8.3 Analyse comparative

Pourquoi la forêt aléatoire domine-t-elle ? Trois raisons : (1) elle capture les interactions non-linéaires entre variables sans les modéliser explicitement ; (2) le bagging et la restriction de mtry décorrèlent les arbres, offrant une régularisation naturelle absente du kNN ; (3) elle sélectionne implicitement les variables pertinentes à chaque nœud, contrairement au kNN qui utilise les 53 dimensions indistinctement. Le gain arbre → forêt (4.4 points d’AUC) illustre concrètement l’avantage du moyennage de 500 arbres décorrélés sur un arbre unique instable.

Le kNN échoue en raison de la malédiction de la dimensionnalité : avec ~50 features binaires, les distances euclidiennes deviennent peu discriminantes et le kNN nécessite un k=51 très large, perdant son avantage de classificateur local.

9 Pistes non explorées

Au-delà des quatre modèles supervisés retenus, nous avons exploré deux pistes complémentaires que nous avons choisi de ne pas intégrer comme modèles principaux. Cette section justifie ces choix.

9.1 Naive Bayes

Le classificateur Naive Bayes applique le théorème de Bayes avec l’hypothèse d’indépendance conditionnelle entre les features :

\[P(Y = c \mid X_1, \ldots, X_p) \propto P(Y = c) \prod_{j=1}^{p} P(X_j \mid Y = c)\]

Code
nb_file     <- here::here("output", "model_nb.rds")
cm_nb_file  <- here::here("output", "cm_nb.rds")
roc_nb_file <- here::here("output", "roc_nb.rds")

if (file.exists(nb_file) && file.exists(cm_nb_file) && file.exists(roc_nb_file)) {
  model_nb <- readRDS(nb_file)
  cm_nb    <- readRDS(cm_nb_file)
  roc_nb   <- readRDS(roc_nb_file)
} else {
  grid_nb <- expand.grid(
    laplace   = c(0, 0.5, 1, 2),
    usekernel = c(TRUE, FALSE),
    adjust    = c(0.5, 1, 1.5)
  )
  set.seed(42)
  model_nb <- train(
    Satisfaction ~ ., data = train_data, method = "naive_bayes",
    trControl = CV_CONTROL, tuneGrid = grid_nb, metric = "ROC",
    preProcess = c("center", "scale")
  )
  prob_nb <- predict(model_nb, test_data, type = "prob")
  cm_nb   <- confusionMatrix(predict(model_nb, test_data),
                             test_data$Satisfaction, positive = "Oui")
  roc_nb  <- roc(test_data$Satisfaction, prob_nb$Oui, quiet = TRUE)
  saveRDS(model_nb, nb_file)
  saveRDS(cm_nb,    cm_nb_file)
  saveRDS(roc_nb,   roc_nb_file)
}
Code
kable(data.frame(
  Metrique = c("AUC (CV)", "AUC (test)", "Accuracy", "Sensibilité", "Spécificité"),
  Valeur = c(
    round(max(model_nb$results$ROC), 4),
    round(auc(roc_nb), 4),
    round(cm_nb$overall["Accuracy"], 4),
    round(cm_nb$byClass["Sensitivity"], 4),
    round(cm_nb$byClass["Specificity"], 4)
  )
))
Table 9.1: Métriques du Naive Bayes — modèle non retenu
Metrique Valeur
AUC (CV) 0.6481
AUC (test) 0.6464
Accuracy 0.5540
Sensibilité 0.2002
Spécificité 0.9184

Raisons de la non-rétention :

  1. AUC la plus faible (0.646) : inférieure à tous les modèles retenus.
  2. Classificateur dégénéré : la sensibilité extrêmement basse (0.2) indique que le modèle prédit presque tous les parfums comme « Non satisfaisants ». Cela le rend inutilisable en pratique — un classificateur qui répond toujours « Non » est trivial.
  3. Violation de l’hypothèse d’indépendance : les familles olfactives sont corrélées entre elles, ce qui viole l’hypothèse fondamentale du Naive Bayes.
Code
fam_feat <- grep("^fam_", feature_cols, value = TRUE)
cor_fam  <- cor(train_data[, fam_feat])
colnames(cor_fam) <- gsub("^fam_", "", colnames(cor_fam))
rownames(cor_fam) <- gsub("^fam_", "", rownames(cor_fam))
corrplot(cor_fam, method = "color", type = "upper",
         addCoef.col = "black", tl.cex = 0.7, number.cex = 0.55,
         title = "Corrélations entre familles olfactives",
         mar = c(0, 0, 2, 0))
Figure 9.1: Corrélations entre familles olfactives — violation de l’hypothèse d’indépendance du Naive Bayes

Plusieurs paires de familles présentent des corrélations non négligeables : herbal/sweet (\(r \approx -0.44\), la plus forte anti-corrélation — les compositions aromatiques excluent les accords gourmands), floral/herbal (\(r \approx -0.27\)) et fruity/spicy (\(r \approx -0.27\)). Ces anti-corrélations traduisent la spécialisation des compositions : les familles ne sont pas indépendantes mais structurées par les conventions de la parfumerie. Ces dépendances violent l’hypothèse d’indépendance conditionnelle du Naive Bayes, dégradant l’estimation des probabilités jointes et expliquant la mauvaise performance du modèle.

9.2 Clustering K-means

En complément de l’approche supervisée, nous avons réalisé un clustering non-supervisé K-means sur les 10 familles olfactives, afin de découvrir d’éventuels profils-types de parfums.

Code
data_rated <- map_accords_to_families(
  data_rated,
  c("mainaccord1", "mainaccord2", "mainaccord3", "mainaccord4", "mainaccord5")
)
fam_cols_all <- grep("^fam_", names(data_rated), value = TRUE)
X_fam <- data_rated[, fam_cols_all]

wss <- numeric(8)
for (k in 1:8) {
  set.seed(42)
  km <- kmeans(X_fam, centers = k, nstart = 25, iter.max = 100)
  wss[k] <- km$tot.withinss
}
Code
elbow_df <- data.frame(k = 1:8, WCSS = wss)
ggplot(elbow_df, aes(x = k, y = WCSS)) +
  geom_line(color = COL_PRIMARY, linewidth = 1) +
  geom_point(color = COL_PRIMARY, size = 3) +
  geom_vline(xintercept = 3, linetype = "dashed", color = COL_NEGATIVE) +
  annotate("text", x = 3.3, y = max(wss) * 0.92, label = "Coude (k=3)",
           color = COL_NEGATIVE, size = 3.5, hjust = 0) +
  scale_x_continuous(breaks = 1:8) +
  labs(x = "Nombre de clusters (K)", y = "Inertie intra-cluster (WCSS)",
       title = "Méthode du coude") +
  THEME_REPORT
Figure 9.2: Méthode du coude — choix du nombre de clusters

La méthode du coude suggère k=3 clusters : la réduction d’inertie chute fortement après k=3.

Code
set.seed(42)
km3 <- kmeans(X_fam, centers = 3, nstart = 25, iter.max = 100)
idx_herbal   <- which.max(km3$centers[, "fam_herbal"])
idx_floral   <- which.max(km3$centers[, "fam_floral"])
idx_oriental <- setdiff(1:3, c(idx_herbal, idx_floral))
cluster_labels <- character(3)
cluster_labels[idx_herbal]   <- "Aromatique-Frais"
cluster_labels[idx_floral]   <- "Floral-Gourmand"
cluster_labels[idx_oriental] <- "Boisé-Oriental"
data_rated$cluster_k3    <- km3$cluster
data_rated$cluster_label <- cluster_labels[km3$cluster]
Code
centers_df <- as.data.frame(km3$centers) %>%
  mutate(Cluster = cluster_labels) %>%
  pivot_longer(-Cluster, names_to = "Famille", values_to = "Proportion")
centers_df$Famille <- gsub("^fam_", "", centers_df$Famille)

ggplot(centers_df, aes(x = Famille, y = Proportion, fill = Cluster)) +
  geom_bar(stat = "identity", position = position_dodge(width = 0.8), alpha = 0.85) +
  scale_fill_manual(values = c("#4CAF50", "#E91E63", "#FF9800")) +
  scale_y_continuous(labels = percent) +
  labs(x = NULL, y = "Proportion moyenne",
       title = "Profils olfactifs des 3 clusters") +
  THEME_REPORT +
  theme(axis.text.x = element_text(angle = 35, hjust = 1, size = 9),
        legend.position = "top")
Figure 9.3: Profils olfactifs des 3 clusters K-means

Trois profils-types émergent clairement :

Code
sat_cluster <- data_rated %>%
  group_by(cluster_label) %>%
  summarise(
    n = n(),
    taux_satisfaction = round(mean(Satisfaction == "Oui") * 100, 1),
    rating_moyen = round(mean(Rating_Value, na.rm = TRUE), 2),
    .groups = "drop"
  )
kable(sat_cluster[, c("cluster_label", "n", "taux_satisfaction", "rating_moyen")],
      col.names = c("Profil", "Effectif", "Satisfaction (%)", "Rating moyen"))
Table 9.2: Taux de satisfaction par profil olfactif
Profil Effectif Satisfaction (%) Rating moyen
Aromatique-Frais 7937 51.2 3.96
Boisé-Oriental 6006 62.0 4.03
Floral-Gourmand 10120 43.7 3.92
Code
ggplot(sat_cluster, aes(x = cluster_label, y = taux_satisfaction, fill = cluster_label)) +
  geom_bar(stat = "identity", alpha = 0.85) +
  geom_text(aes(label = paste0(taux_satisfaction, "%")), vjust = -0.3, size = 4) +
  geom_hline(yintercept = mean(data_rated$Satisfaction == "Oui") * 100,
             linetype = "dashed", color = "gray40") +
  scale_fill_manual(values = c("Aromatique-Frais" = "#4CAF50",
                                "Floral-Gourmand"  = "#E91E63",
                                "Boisé-Oriental"   = "#FF9800")) +
  labs(x = NULL, y = "Satisfaction (%)",
       title = "Satisfaction par profil olfactif") +
  THEME_REPORT + theme(legend.position = "none") + ylim(0, 70)
Figure 9.4: Taux de satisfaction par profil olfactif
  • Aromatique-Frais (7937 parfums) : dominé par les familles herbal, fruity et woody. Ce profil correspond aux fougères masculines classiques (lavande, bergamote, cèdre). Satisfaction : 51.2 %.
  • Floral-Gourmand (10120 parfums) : dominé par sweet, floral et fruity. Compositions féminines accessibles (rose, vanille, fruits rouges). Satisfaction : 43.7 % — le taux le plus bas.
  • Boisé-Oriental (6006 parfums) : dominé par woody, oriental et sweet. Parfums de niche (oud, santal, ambre). Satisfaction : 62 % — le taux le plus élevé.

L’écart de satisfaction entre le profil Boisé-Oriental (62 %) et le Floral-Gourmand (43.7 %) est de 18.3 points. Comment l’expliquer ? Les parfums boisés-orientaux (oud, santal, ambre) sont des parfums de niche, souvent plus chers et moins connus du grand public. Les personnes qui les achètent sont donc des passionnés qui savent ce qu’ils veulent — ils choisissent avec soin et notent généreusement les parfums qu’ils apprécient. À l’inverse, les parfums floraux-gourmands (rose, vanille, fruits rouges) sont les plus grand public : ils attirent un large éventail d’acheteurs, y compris des consommateurs occasionnels qui peuvent être déçus. Ce mélange d’avis enthousiastes et mitigés tire la moyenne vers le bas. C’est un biais de sélection classique : ce n’est pas que les parfums orientaux sont objectivement meilleurs, mais leur public est plus sélectif. Cet écart est statistiquement significatif (test du \(\chi^2\), \(p < 0.001\)), mais la taille d’effet reste faible (V de Cramér \(\approx 0.15\)), confirmant que le profil olfactif n’explique qu’une part modeste de la satisfaction. De plus, l’ajout du cluster comme variable dans la forêt aléatoire ne modifie pas l’AUC, ce qui signifie que les familles individuelles capturent déjà cette information.

10 Regard critique et limites

  1. Biais de sélection : Rating_Count domine les prédicteurs dans les quatre modèles. Ce n’est pas le nombre d’avis qui cause la satisfaction, mais les parfums populaires attirent plus d’évaluateurs et ces évaluateurs notent préférentiellement les parfums qu’ils apprécient. Nos modèles capturent cette corrélation sans pouvoir établir de causalité.
  2. Multicolinéarité des familles : les familles olfactives ne sont pas mutuellement exclusives (un parfum peut être à la fois « fruity » et « sweet »), ce qui peut biaiser l’estimation des coefficients LASSO. La forêt aléatoire et le kNN sont moins sensibles à ce problème.
  3. Validité externe : les données Fragrantica représentent un public auto-sélectionné de passionnés en ligne, non représentatif de la population générale des consommateurs.
  4. Perte d’information : la binarisation à la médiane perd le gradient de satisfaction (un parfum à 4.8 et un à 4.0 sont tous deux « Oui »).
  5. Malédiction de la dimensionnalité : le kNN illustre concrètement comment les performances d’un classificateur basé sur les distances se dégradent en haute dimension (\(p = 53\)), un phénomène théorique étudié en Séance 5 que nous avons pu vérifier empiriquement.

11 Synthèse et conclusion

11.1 Retour sur la problématique

Les caractéristiques olfactives permettent-elles de prédire la satisfaction ? Partiellement. Les familles olfactives contribuent à la prédiction (AUC > 0.50), mais l’AUC plafonne à ~0.69, dominée par les variables non-olfactives. Ce projet illustre les concepts clés du cours :

  • La validation croisée (TP3) pour une estimation honnête de la performance.
  • La régularisation (TP5) pour gérer un espace de 53 prédicteurs — le LASSO a éliminé les variables redondantes.
  • La comparaison de modèles (TP6–7) : le gain de la forêt aléatoire est réel mais modéré.
  • La classification par voisinage (TP5) : le kNN échoue en haute dimension, confirmant les mises en garde du cours.
  • Le regard critique : nous avons testé et écarté le Naive Bayes (Séance 6), et exploré le clustering K-means (Séance 10) pour un éclairage complémentaire.
  • Une performance modérée reflète la subjectivité de la variable cible, pas un échec méthodologique.

11.2 Bilan des méthodes

Table 11.1: Bilan des méthodes explorées
Méthode AUC test Statut Justification
Forêt Aléatoire 0.693 Retenu Meilleure performance, interactions non-linéaires
Rég. Logistique 0.669 Retenu Interprétable, sélection de variables
Arbre de Décision 0.649 Retenu Interprétable, justifie le passage à la forêt
kNN (k=51) 0.654 Retenu Illustration malédiction dimensionnalité
Naive Bayes 0.646 Non retenu Hypothèse violée, classificateur dégénéré
K-means (k=3) Exploratoire 3 profils significatifs (\(\chi^2\), \(p < 0.001\)), effet faible (V = 0.15), pas de gain prédictif

11.3 Ouverture et perspectives

Ce travail ouvre plusieurs pistes. L’ingénierie de features pourrait être enrichie par un encodage TF-IDF sur les notes individuelles (traitement analogue au NLP), pondérant chaque note par sa rareté plutôt que de regrouper en 10 familles binaires. L’ajout de la concentration du parfum (Eau de Toilette, Eau de Parfum, Extrait) comme variable prédictive serait particulièrement pertinent : la concentration influence directement la tenue, la projection et donc la satisfaction perçue, mais cette information est absente du dataset Fragrantica. Les méthodes d’ensemble hybrides (stacking) ou les réseaux de neurones pourraient exploiter des interactions complexes entre prédicteurs. Le clustering K-means suggère qu’un modèle entraîné séparément par cluster pourrait capturer des relations différentes selon le segment de marché. Enfin, une régression ordinale préserverait le gradient de satisfaction perdu par la binarisation.

12 Utilisation de l’IA

Nous avons utilisé Claude et GitHub Copilot pour :

  1. Debugging du code R (~15 % du temps) : résolution d’erreurs de syntaxe et de compatibilité entre packages (ex. : l’IA avait suggéré randomForest au lieu de ranger, corrigé après des temps d’exécution 10× supérieurs).
  2. Mise en forme du rapport (~10 %) : aide à la structuration LaTeX (gestion des marges via geometry, espacement avec setstretch et parskip, placement des figures avec float et \floatplacement{figure}{H}, table des matières automatique).

Toutes les sorties ont été systématiquement vérifiées. L’analyse des données, le choix des hyperparamètres, l’interprétation des résultats et le regard critique sont entièrement le fruit de notre travail.

12.1 Contributions individuelles

Table 12.1: Contributions individuelles des membres de l’équipe
Membre Contributions
ANZID Keltoum Analyse exploratoire, interprétation des résultats, rédaction des sections contextuelles, présentation orale
EL KORAICHI Mohamed Yassine Feature engineering (familles olfactives, notes), kNN et pistes non explorées, réécriture du rapport, validation et relecture critique
HAMLIL Mohamed Code R et modélisation (régression logistique, arbre de décision, forêt aléatoire), feature engineering, pipeline anti data leakage
PARDO TERAN German Recherche des données (Fragrantica), visualisations, aide à l’interprétation, présentation orale

Références

  1. Breiman, L. (2001). Random Forests. Machine Learning, 45(1), 5–32.
  2. Zou, H. & Hastie, T. (2005). Regularization and Variable Selection via the Elastic Net. JRSS-B, 67(2), 301–320.
  3. Fawcett, T. (2006). An Introduction to ROC Analysis. Pattern Recognition Letters, 27(8), 861–874.
  4. Kohavi, R. (1995). A Study of Cross-Validation and Bootstrap for Accuracy Estimation and Model Selection. IJCAI.
  5. Ellena, J.-C. (2007). Que sais-je ? Le parfum. Presses Universitaires de France.
  6. Fragrantica Fragrance Dataset. Kaggle. https://www.kaggle.com/datasets/olgagmiufana1/fragrantica-com-fragrance-dataset
  7. Cover, T. & Hart, P. (1967). Nearest Neighbor Pattern Classification. IEEE Transactions on Information Theory, 13(1), 21–27.