1  Partie 1 : Données et Analyse Exploratoire

2 Données

2.1 Présentation du jeu de données

Le jeu de données Fragrantica contient les informations suivantes pour chaque parfum :

Table 2.1: Description des variables du dataset Fragrantica
Variable Type Description
Rating_Value Numérique Note moyenne attribuée par les utilisateurs (1–5)
Rating_Count Numérique Nombre d’évaluations reçues
Year Numérique Année de sortie du parfum
Gender Catégorielle Genre cible (men, women, unisex)
Country Catégorielle Pays d’origine de la marque
Top / Middle / Base Texte Notes olfactives par phase de la pyramide
mainaccord15 Texte Les 5 accords principaux du parfum

2.2 Chargement des données

Code
data_raw <- read.csv(
  here::here("data", "fra_cleaned.csv"),
  sep = ";",
  stringsAsFactors = FALSE,
  na.strings = c("NA", ""),
  fileEncoding = "latin1"
)

data_raw <- data_raw %>%
  rename(
    Name = Perfume,
    Rating_Value = Rating.Value,
    Rating_Count = Rating.Count
  )

data_raw$Rating_Value <- as.numeric(gsub(",", ".", data_raw$Rating_Value))

cat("Dimensions :", nrow(data_raw), "lignes,", ncol(data_raw), "colonnes\n")
Dimensions : 24063 lignes, 18 colonnes

Les données brutes comprennent 24063 parfums et 18 variables. Nous excluons Name, Brand, url, Perfumer1 et Perfumer2 car ce sont des identifiants à cardinalité trop élevée pour être directement intégrés dans un modèle de classification.

2.3 Valeurs manquantes

Code
na_pct <- round(100 * sapply(data_raw, function(x) sum(is.na(x))) / nrow(data_raw), 1)
na_df <- data.frame(Variable = names(na_pct), Pourcentage = na_pct) %>%
  arrange(desc(Pourcentage)) %>%
  filter(Pourcentage > 0)
ggplot(na_df, aes(x = reorder(Variable, Pourcentage), y = Pourcentage)) +
  geom_bar(stat = "identity", fill = COL_PRIMARY) +
  coord_flip() +
  labs(x = NULL, y = "% manquantes") +
  THEME_REPORT
Figure 2.1: Pourcentage de valeurs manquantes par variable

Perfumer2 est absent dans plus de 80 % des cas et sera exclu. Year manque dans environ 8 % des cas — ces valeurs seront imputées par la médiane. Les accords secondaires (mainaccord35) présentent 5–15 % de valeurs manquantes, ce qui est attendu car tous les parfums n’ont pas 5 accords. La variable Rating_Value est quasi-complète, permettant la construction fiable de la variable cible.

3 Analyse Exploratoire

3.1 Construction de la variable cible

La variable Rating_Value (continue, 1–5) est transformée en variable binaire Satisfaction en utilisant la médiane comme seuil. Ce choix pragmatique maximise l’équilibre des classes et évite les problèmes liés au déséquilibre. Un seuil fixe (ex. 4.0) produirait des classes déséquilibrées.

Code
data_rated <- data_raw %>% filter(!is.na(Rating_Value))
mediane_rating <- median(data_rated$Rating_Value, na.rm = TRUE)
data_rated <- data_rated %>%
  mutate(Satisfaction = factor(
    ifelse(Rating_Value >= mediane_rating, "Oui", "Non"),
    levels = c("Non", "Oui")
  ))
ggplot(data_rated, aes(x = Satisfaction, fill = Satisfaction)) +
  geom_bar() +
  FILL_SATISFACTION +
  labs(x = "Satisfaction", y = "Nombre de parfums") +
  THEME_REPORT +
  theme(legend.position = "none")

Distribution de la variable Satisfaction

La médiane (3.97) produit une répartition quasi-équilibrée (50.7 % Oui), favorable pour l’apprentissage statistique. Notons que ce seuil mesure une satisfaction relative (par rapport aux autres parfums) et non absolue.

3.2 Distribution des variables numériques

Le jeu de données contient deux variables numériques clés qu’il ne faut pas confondre :

  • Rating_Value : la note moyenne attribuée au parfum par les utilisateurs (échelle 1–5). C’est une mesure de qualité perçue. Sa distribution est quasi-symétrique (pas besoin de transformation).
  • Rating_Count : le nombre total d’évaluations reçues par le parfum. C’est une mesure de popularité. Sa distribution est très asymétrique à droite (quelques parfums vedettes cumulent des milliers d’avis, la majorité en a peu), d’où l’application d’une transformation logarithmique \(\log(1 + x)\) pour réduire cette asymétrie.

Rating_Value sert à construire la variable cible (Satisfaction) et est donc exclue des prédicteurs. Rating_Count est conservé comme prédicteur car il mesure la popularité, pas la satisfaction elle-même.

Code
par(mfrow = c(1, 2))
hist(data_rated$Rating_Value, breaks = 30, col = COL_PRIMARY, border = "white",
     main = "Rating_Value (note moyenne)", xlab = "Note", ylab = "Fréquence")
abline(v = mediane_rating, col = COL_NEGATIVE, lwd = 2, lty = 2)
legend("topleft", paste("Médiane =", round(mediane_rating, 2)),
       col = COL_NEGATIVE, lty = 2, lwd = 2, cex = 0.8)
rc <- data_rated$Rating_Count[!is.na(data_rated$Rating_Count) & data_rated$Rating_Count > 0]
hist(log1p(rc), breaks = 30, col = COL_PRIMARY, border = "white",
     main = "log(1 + Rating_Count) (popularité)",
     xlab = "log(1 + Rating_Count)", ylab = "Fréquence")
par(mfrow = c(1, 1))
Figure 3.1: Distribution de Rating_Value et log(1+Rating_Count)

Les notes (Rating_Value) sont concentrées autour de 3.5–4.0, avec une légère asymétrie gauche. La popularité brute est extrêmement asymétrique : la majorité des parfums ont moins de 50 évaluations, tandis que quelques parfums iconiques (Sauvage de Dior, Bleu de Chanel) en cumulent des milliers. Après transformation logarithmique, la distribution devient quasi-normale.

3.3 Distribution par année

Code
data_rated %>%
  filter(!is.na(Year), Year >= 1900) %>%
  ggplot(aes(x = Year)) +
  geom_histogram(binwidth = 2, fill = COL_PRIMARY, color = "white") +
  labs(x = "Année", y = "Nombre de parfums") +
  THEME_REPORT
Figure 3.2: Nombre de parfums par année de sortie

On observe une croissance exponentielle des sorties à partir des années 2000, liée à la démocratisation de la parfumerie et à la multiplication des marques de niche. Les parfums anciens (avant 1990) sont sous-représentés, ce qui introduit un effet de survivance : seuls les parfums ayant survécu commercialement restent référencés sur Fragrantica, et ces parfums « survivants » sont généralement mieux notés.

3.4 Variables catégoriques

Code
p1 <- data_rated %>% filter(!is.na(Gender)) %>% count(Gender, sort = TRUE) %>%
  ggplot(aes(x = reorder(Gender, n), y = n)) +
  geom_bar(stat = "identity", fill = COL_PRIMARY) +
  coord_flip() + labs(x = NULL, y = "Effectif", title = "Genre") + THEME_REPORT
p2 <- data_rated %>% filter(!is.na(Country)) %>% count(Country, sort = TRUE) %>% head(8) %>%
  ggplot(aes(x = reorder(Country, n), y = n)) +
  geom_bar(stat = "identity", fill = COL_PRIMARY) +
  coord_flip() + labs(x = NULL, y = "Effectif", title = "Pays (Top 8)") + THEME_REPORT
gridExtra::grid.arrange(p1, p2, ncol = 2)
Figure 3.3: Distribution par genre et par pays d’origine

Les parfums pour femmes et unisex dominent le dataset. La France arrive en tête (~30 %), ce qui s’explique par son héritage historique : Grasse (Alpes-Maritimes) est le berceau mondial de la parfumerie depuis le XVIIe siècle. Les États-Unis (~19 %) reflètent le dynamisme des marques designer et celebrity, tandis que l’Italie (~14 %) s’appuie sur sa tradition de la mode de luxe (Versace, Dolce & Gabbana, Acqua di Parma). Les pays représentant moins de 5 % seront regroupés en « Other ».

3.5 Accords et notes olfactives

Code
accord_cols <- c("mainaccord1", "mainaccord2", "mainaccord3", "mainaccord4", "mainaccord5")
accords_all <- data_rated %>% select(all_of(accord_cols)) %>% unlist() %>% str_trim()
accords_all <- accords_all[!is.na(accords_all) & accords_all != ""]
accords_freq <- as.data.frame(table(accords_all), stringsAsFactors = FALSE)
colnames(accords_freq) <- c("Accord", "Freq")
accords_freq <- accords_freq %>%
  mutate(Pct = round(Freq / nrow(data_rated) * 100, 1)) %>%
  arrange(desc(Freq))
accords_freq %>% head(15) %>%
  ggplot(aes(x = reorder(Accord, Freq), y = Freq)) +
  geom_bar(stat = "identity", fill = COL_PRIMARY) +
  coord_flip() +
  labs(x = NULL, y = "Fréquence",
       title = paste0("Top 15 accords (", nrow(accords_freq), " distincts)")) +
  THEME_REPORT
Figure 3.4: Accords olfactifs les plus fréquents

Les accords woody (boisé), citrus (agrumes) et aromatic (aromatique) dominent, suivis de sweet et fruity. Cette répartition reflète les tendances actuelles du marché. Les 84 accords distincts seront regroupés en 10 familles olfactives au chapitre suivant.

Code
top_n <- as.data.frame(sort(table(parse_notes(data_rated$Top)), decreasing = TRUE)[1:10])
mid_n <- as.data.frame(sort(table(parse_notes(data_rated$Middle)), decreasing = TRUE)[1:10])
bas_n <- as.data.frame(sort(table(parse_notes(data_rated$Base)), decreasing = TRUE)[1:10])
notes_df <- bind_rows(
  data.frame(Phase = "Tete",  Note = top_n$Var1, Freq = top_n$Freq),
  data.frame(Phase = "Coeur", Note = mid_n$Var1, Freq = mid_n$Freq),
  data.frame(Phase = "Fond",  Note = bas_n$Var1, Freq = bas_n$Freq)
)
ggplot(notes_df, aes(x = reorder(Note, Freq), y = Freq)) +
  geom_bar(stat = "identity", fill = COL_PRIMARY) +
  coord_flip() +
  facet_wrap(~Phase, scales = "free") +
  labs(x = NULL, y = "Fréquence") +
  THEME_REPORT
Figure 3.5: Notes olfactives les plus fréquentes par phase

La répartition des notes confirme la pyramide olfactive classique : les notes de tête sont dominées par les agrumes volatils (bergamote, citron, poivre rose), les notes de cœur par les floraux (jasmin, rose, iris), et les notes de fond par les ingrédients tenaces (musc, santal, cèdre, vanille).

3.6 Analyse bivariée : satisfaction et prédicteurs

Code
p1 <- data_rated %>% filter(!is.na(Rating_Count)) %>%
  ggplot(aes(x = Satisfaction, y = log1p(Rating_Count), fill = Satisfaction)) +
  geom_boxplot(alpha = 0.7) + FILL_SATISFACTION +
  labs(y = "log(1 + Rating_Count)", title = "Biais de popularité") +
  THEME_REPORT + theme(legend.position = "none")
p2 <- data_rated %>% filter(!is.na(Year), Year >= 1900) %>%
  ggplot(aes(x = Satisfaction, y = Year, fill = Satisfaction)) +
  geom_boxplot(alpha = 0.7) + FILL_SATISFACTION +
  labs(y = "Année", title = "Effet de survivance") +
  THEME_REPORT + theme(legend.position = "none")
gridExtra::grid.arrange(p1, p2, ncol = 2)
Figure 3.6: Variables numériques selon la satisfaction

Les parfums satisfaisants ont significativement plus d’évaluations (biais de popularité) : sur Fragrantica, les utilisateurs évaluent préférentiellement les parfums qu’ils apprécient, créant un cercle vertueux popularité → bonnes notes. Les parfums satisfaisants sont aussi légèrement plus anciens (effet de survivance) : les mauvais parfums anciens ont été retirés du marché et ne sont plus référencés.

Code
p1 <- data_rated %>% filter(!is.na(Gender)) %>% group_by(Gender) %>% filter(n() >= 50) %>%
  summarise(taux = mean(Satisfaction == "Oui"), .groups = "drop") %>%
  ggplot(aes(x = reorder(Gender, taux), y = taux)) +
  geom_bar(stat = "identity", fill = COL_PRIMARY) +
  coord_flip() + scale_y_continuous(labels = percent) +
  labs(x = NULL, y = "Taux de satisfaction", title = "Par genre") + THEME_REPORT
p2 <- data_rated %>% filter(!is.na(Country)) %>% group_by(Country) %>% filter(n() >= 100) %>%
  summarise(taux = mean(Satisfaction == "Oui"), .groups = "drop") %>%
  arrange(desc(taux)) %>% head(10) %>%
  ggplot(aes(x = reorder(Country, taux), y = taux)) +
  geom_bar(stat = "identity", fill = COL_PRIMARY) +
  coord_flip() + scale_y_continuous(labels = percent) +
  labs(x = NULL, y = "Taux de satisfaction", title = "Par pays (n >= 100)") + THEME_REPORT
gridExtra::grid.arrange(p1, p2, ncol = 2)
Figure 3.7: Taux de satisfaction par genre et par pays

Le genre est peu discriminant. Par pays, certaines origines (souvent des maisons de niche à production artisanale, comme les parfumeurs des Émirats spécialisés dans le oud) affichent des taux de satisfaction plus élevés, probablement car leurs parfums ciblent un public passionné et averti.

3.7 Matrice de corrélation

Code
num_vars <- data_rated %>% select(Rating_Value, Rating_Count, Year) %>% drop_na()
corrplot(cor(num_vars), method = "color", type = "upper",
         addCoef.col = "black", tl.cex = 0.8, number.cex = 0.7)
Figure 3.8: Matrice de corrélation des variables numériques

Les corrélations entre les variables numériques sont très faibles (toutes inférieures à 0.15), indiquant l’absence de multicolinéarité problématique : chaque variable (année, popularité, note) apporte une information distincte au modèle.