---
title: "Partie 1 : Données et Analyse Exploratoire"
---
```{r}
#| label: setup
#| include: false
#| cache: false
source(here::here("utils.R"))
```
# Données
## Présentation du jeu de données
Le jeu de données Fragrantica contient les informations suivantes pour chaque parfum :
| 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 |
| `mainaccord1`--`5` | Texte | Les 5 accords principaux du parfum |
: Description des variables du dataset Fragrantica {#tbl-variables}
## Chargement des données
```{r}
#| label: load-data
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")
```
Les données brutes comprennent `r nrow(data_raw)` parfums et `r ncol(data_raw)` 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.
## Valeurs manquantes
```{r}
#| label: fig-missing
#| fig-cap: "Pourcentage de valeurs manquantes par variable"
#| fig-height: 2.5
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
```
`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 (`mainaccord3`--`5`) 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.
# Analyse Exploratoire
## 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.
```{r}
#| label: target-creation
#| fig-cap: "Distribution de la variable Satisfaction"
#| fig-height: 2
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")
```
La médiane (`r round(mediane_rating, 2)`) produit une répartition quasi-équilibrée (**`r round(mean(data_rated$Satisfaction == "Oui") * 100, 1)` % Oui**), favorable pour l'apprentissage statistique. Notons que ce seuil mesure une satisfaction *relative* (par rapport aux autres parfums) et non absolue.
## 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.
```{r}
#| label: fig-univar-num
#| fig-cap: "Distribution de Rating_Value et log(1+Rating_Count)"
#| fig-height: 2.5
#| fig-width: 9
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))
```
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.
## Distribution par année
```{r}
#| label: fig-year
#| fig-cap: "Nombre de parfums par année de sortie"
#| fig-height: 2
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
```
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.
## Variables catégoriques
```{r}
#| label: fig-categ
#| fig-cap: "Distribution par genre et par pays d'origine"
#| fig-height: 3
#| fig-width: 9
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)
```
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 XVII^e^ 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 ».
## Accords et notes olfactives
```{r}
#| label: fig-accords
#| fig-cap: "Accords olfactifs les plus fréquents"
#| fig-height: 2.5
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
```
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 `r nrow(accords_freq)` accords distincts seront regroupés en 10 familles olfactives au chapitre suivant.
```{r}
#| label: fig-notes
#| fig-cap: "Notes olfactives les plus fréquentes par phase"
#| fig-height: 2.5
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
```
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).
## Analyse bivariée : satisfaction et prédicteurs
```{r}
#| label: fig-multi-num
#| fig-cap: "Variables numériques selon la satisfaction"
#| fig-height: 2.5
#| fig-width: 9
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)
```
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.
```{r}
#| label: fig-multi-categ
#| fig-cap: "Taux de satisfaction par genre et par pays"
#| fig-height: 2.5
#| fig-width: 9
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)
```
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.
## Matrice de corrélation
```{r}
#| label: fig-corr
#| fig-cap: "Matrice de corrélation des variables numériques"
#| fig-height: 2.5
#| fig-width: 3.5
#| out-width: "45%"
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)
```
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.
```{r}
#| label: save-data-rated
#| include: false
saveRDS(data_rated, here::here("output", "data_rated.rds"))
saveRDS(accords_freq, here::here("output", "accords_freq.rds"))
```