(To be completed)
#2. Partie B - Calculs KPI
ca_total <-sum(tx$revenue, na.rm = TRUE)
periode <- range(tx$order_date, na.rm = TRUE)
ca_total
## [1] 311249.8
periode
## [1] "2024-01-01" "2025-06-30"
###interpretation Taille d’activité correcte sur la période analysée. Le CA total s’élève à 311 249.8 sur la période allant de janvier 2024 à juin 2025.
CM_total <- sum(tx$contribution_margin, na.rm = TRUE)
round(CM_total, 2)
## [1] 130198.6
la marge contributive totale (CM) sur l’ensemble de la période est de 130198.6 Montant disponible pour couvrir la structure et générer du profit.
CM_rate <- CM_total / ca_total
CM_rate_pct <- CM_rate * 100
CM_rate
## [1] 0.4183089
CM_rate_pct
## [1] 41.83089
Pour 100€ vendus, environ 42€ sont disponibles après coûts variables.
fixed$year <- lubridate::year(fixed$month)
annual_2024 <- sum(fixed$fixed_cost[fixed$year == 2024], na.rm = TRUE)
annual_2025_est_h1x2 <- sum(fixed$fixed_cost[fixed$year == 2025], na.rm = TRUE) * 2
annual_est_smooth <- mean(c(annual_2024, annual_2025_est_h1x2))
round(annual_2024, 2)
## [1] 87426.25
round(annual_2025_est_h1x2, 2)
## [1] 87035.82
round(annual_est_smooth, 1)
## [1] 87231
Les coûts fixes étant annualisés puis divisés par 12, cela permet de :
Calculer le point mort
Comparer CM mensuelle vs structure
Mesurer le levier opérationnel
Sans cette estimation, impossible d’évaluer la vraie rentabilité.
# Calcul de la marge contributive mensuelle (CM_m)
CM_m <- aggregate(contribution_margin ~ month, data = tx, sum)
# Calcul du coût fixe mensuel moyen
fixed_month <- mean(fixed$fixed_cost, na.rm = TRUE)
# Calcul du profit mensuel
CM_m$profit_m <- CM_m$contribution_margin - fixed_month
# Profit moyen mensuel
profit_moyen_mensuel <- mean(CM_m$profit_m, na.rm = TRUE)
# Annualisation simple
profit_annuel_estime <- profit_moyen_mensuel * 12
profit_moyen_mensuel
## [1] -41.42122
profit_annuel_estime
## [1] -497.0547
L’intérêt c’est de: Voir si le modèle est structurellement rentable
Identifier volatilité mensuelle
Estimer la capacité de scaling (annualisation ×12)
# CM mensuelle
CM_m_df <- aggregate(contribution_margin ~ month, data = tx, sum, na.rm = TRUE)
# Jointure avec le coût fixe mensuel
CM_m_df <- merge(CM_m_df, fixed, by = "month", all.x = TRUE)
# Profit mensuel)
CM_m_df$profit_m <- CM_m_df$contribution_margin - CM_m_df$fixed_cost
# Profit moyen mensuel + annualisation simple
profit_mean_month <- mean(CM_m_df$profit_m, na.rm = TRUE)
profit_annualized_simple <- profit_mean_month * 12
data.frame(
profit_mean_month = round(profit_mean_month, 3),
profit_annualized_simple = round(profit_annualized_simple, 3)
)
## profit_mean_month profit_annualized_simple
## 1 -41.421 -497.055
# B6 : meilleur et pire mois
best <- CM_m_df[which.max(CM_m_df$profit_m), c("month","profit_m")]
worst <- CM_m_df[which.min(CM_m_df$profit_m), c("month","profit_m")]
best
## month profit_m
## 1 2024-01-01 1810.781
worst
## month profit_m
## 9 2024-09-01 -1394.534
Cela permet de détecter: Saisonnalité Problèmes opérationnels ponctuels Impact d’un canal ou d’un mix spécifique Important pour comprendre si la performance est structurelle ou accidentelle.
#3 Partie C - Segmentation (insights : mix & canaux)
C1_1 <- tx %>%
dplyr::group_by(product_category) %>%
dplyr::summarise(
revenue = sum(revenue, na.rm = TRUE),
cm = sum(contribution_margin, na.rm = TRUE),
.groups = "drop"
) %>%
dplyr::mutate(
cm_rate = cm / revenue
) %>%
dplyr::arrange(dplyr::desc(cm_rate))
C1_1
## # A tibble: 5 × 4
## product_category revenue cm cm_rate
## <chr> <dbl> <dbl> <dbl>
## 1 Services 68027. 48061. 0.707
## 2 Accessories 24038. 10316. 0.429
## 3 Office 30375. 10186. 0.335
## 4 Electronics 152081. 49758. 0.327
## 5 Home 36728. 11877. 0.323
Chaque euro vendu en Services génère beaucoup plus de marge contributive que les autres catégories. Forte valeur ajoutée, faible coût variable.
cm_total <- sum(tx$contribution_margin, na.rm = TRUE)
C1_2 <- tx %>%
dplyr::group_by(product_category) %>%
dplyr::summarise(
cm_cat = sum(contribution_margin, na.rm = TRUE),
.groups = "drop"
) %>%
dplyr::mutate(
cm_total = cm_total,
cm_share = cm_cat / cm_total
) %>%
dplyr::arrange(dplyr::desc(cm_share))
C1_2
## # A tibble: 5 × 4
## product_category cm_cat cm_total cm_share
## <chr> <dbl> <dbl> <dbl>
## 1 Electronics 49758. 130199. 0.382
## 2 Services 48061. 130199. 0.369
## 3 Home 11877. 130199. 0.0912
## 4 Accessories 10316. 130199. 0.0792
## 5 Office 10186. 130199. 0.0782
Cette catégorie apporte du volume mais consomme beaucoup de coûts variables. Elle “gonfle” le chiffre d’affaires mais dilue la marge globale.
#C1.3 Montrez l’effet “mix produits” : comparez (i) la catégorie qui concentre le plus de CA et (ii) celle au meilleur CM rate. Expliquez en 3–4 lignes pourquoi volume ≠ rentabilité.
# Tableau récapitulatif par catégorie
cat_table <- tx %>%
dplyr::group_by(product_category) %>%
dplyr::summarise(
CA = sum(revenue, na.rm = TRUE),
CM = sum(contribution_margin, na.rm = TRUE),
.groups = "drop"
) %>%
dplyr::mutate(CM_rate = CM / CA)
cat_max_CA <- cat_table %>% dplyr::slice_max(CA, n = 1)
cat_best_rate <- cat_table %>% dplyr::slice_max(CM_rate, n = 1)
cat_max_CA
## # A tibble: 1 × 4
## product_category CA CM CM_rate
## <chr> <dbl> <dbl> <dbl>
## 1 Electronics 152081. 49758. 0.327
cat_best_rate
## # A tibble: 1 × 4
## product_category CA CM CM_rate
## <chr> <dbl> <dbl> <dbl>
## 1 Services 68027. 48061. 0.707
Un produit à faible marge peut représenter 50% du CA mais générer peu de profit. À l’inverse, une catégorie plus petite mais très margée peut être le vrai moteur de rentabilité.
Le pilotage ne doit pas viser “plus de CA”, mais “meilleur mix”.
cm_total <- sum(tx$contribution_margin, na.rm = TRUE)
C2_1 <- tx %>%
dplyr::group_by(channel) %>%
dplyr::summarise(
revenue = sum(revenue, na.rm = TRUE),
cm = sum(contribution_margin, na.rm = TRUE),
.groups = "drop"
) %>%
dplyr::mutate(
cm_rate = cm / revenue,
cm_total = cm_total,
cm_share = cm / cm_total
) %>%
dplyr::arrange(dplyr::desc(cm_share))
C2_1
## # A tibble: 4 × 6
## channel revenue cm cm_rate cm_total cm_share
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 SEO/Content 112179. 49860. 0.444 130199. 0.383
## 2 Paid Search 90982. 31505. 0.346 130199. 0.242
## 3 Email/CRM 64251. 31478. 0.490 130199. 0.242
## 4 Affiliation 43838. 17355. 0.396 130199. 0.133
Email/CRM = meilleur CM rate (~49%)
Canal très rentable : faible coût variable, forte conversion. Excellent levier d’efficacité.
best_rate <- C2_1 %>%
dplyr::slice_max(cm_rate, n = 1) %>%
dplyr::select(channel, cm_rate)
best_share <- C2_1 %>%
dplyr::slice_max(cm_share, n = 1) %>%
dplyr::select(channel, cm_share)
best_rate
## # A tibble: 1 × 2
## channel cm_rate
## <chr> <dbl>
## 1 Email/CRM 0.490
best_share
## # A tibble: 1 × 2
## channel cm_share
## <chr> <dbl>
## 1 SEO/Content 0.383
SEO/Content = plus forte contribution en CM (~38%)
Canal scalable : génère beaucoup de volume et donc de marge totale. Moteur principal de croissance.
Un canal scalable permet de croître. Un canal très rentable améliore la marge moyenne. La combinaison des deux optimise à la fois croissance et profitabilité.
library(dplyr)
library(tidyr)
library(lubridate)
# CM par mois et par channel
cm_m_ch <- tx %>%
group_by(month, channel) %>%
summarise(cm = sum(contribution_margin, na.rm = TRUE), .groups = "drop") %>%
arrange(channel, month)
# MoM par channel:
cm_m_ch <- cm_m_ch %>%
group_by(channel) %>%
mutate(delta_cm = cm - lag(cm)) %>%
ungroup()
# Pour chaque mois :
bridge <- cm_m_ch %>%
filter(!is.na(delta_cm)) %>%
group_by(month) %>%
summarise(
top_hausse_segment = channel[which.max(delta_cm)],
top_hausse_delta_cm = max(delta_cm),
top_baisse_segment = channel[which.min(delta_cm)],
top_baisse_delta_cm = min(delta_cm),
total_delta_cm = sum(delta_cm),
.groups = "drop"
)
bridge
## # A tibble: 17 × 6
## month top_hausse_segment top_hausse_delta_cm top_baisse_segment
## <date> <chr> <dbl> <chr>
## 1 2024-02-01 Affiliation 30.3 SEO/Content
## 2 2024-03-01 SEO/Content 1393. Affiliation
## 3 2024-04-01 Email/CRM 1091. Paid Search
## 4 2024-05-01 Paid Search 871. SEO/Content
## 5 2024-06-01 SEO/Content 1268. Email/CRM
## 6 2024-07-01 Email/CRM 997. SEO/Content
## 7 2024-08-01 SEO/Content 383. Email/CRM
## 8 2024-09-01 Paid Search 311. Affiliation
## 9 2024-10-01 Affiliation 343. SEO/Content
## 10 2024-11-01 Email/CRM 1079. SEO/Content
## 11 2024-12-01 SEO/Content 1469. Paid Search
## 12 2025-01-01 Email/CRM 354. SEO/Content
## 13 2025-02-01 SEO/Content 425. Email/CRM
## 14 2025-03-01 Email/CRM 483. SEO/Content
## 15 2025-04-01 Paid Search 441. SEO/Content
## 16 2025-05-01 SEO/Content 867. Email/CRM
## 17 2025-06-01 Email/CRM 112. SEO/Content
## # ℹ 2 more variables: top_baisse_delta_cm <dbl>, total_delta_cm <dbl>
#4 Partie D - Plan d’action (recommandation chiffrée)
1.Révision pricing / remises Augmenter légèrement les prix ou limiter les promotions améliore la marge unitaire. Même si le volume baisse légèrement, la CM totale peut augmenter si l’élasticité est faible.
months_n <- 18
shift_pct <- 0.05
# CA total période
CA_total <- sum(tx$revenue, na.rm = TRUE)
delta_CA <- shift_pct * CA_total
# CM rate par catégorie
cat_rates <- tx %>%
dplyr::group_by(product_category) %>%
dplyr::summarise(
CA = sum(revenue, na.rm = TRUE),
CM = sum(contribution_margin, na.rm = TRUE),
.groups = "drop"
) %>%
dplyr::mutate(CM_rate = CM / CA)
low <- cat_rates %>% dplyr::filter(product_category == "Electronics")
high <- cat_rates %>% dplyr::filter(product_category == "Services")
# Impact CM (période & par mois)
impact_CM_period <- delta_CA * (high$CM_rate - low$CM_rate)
impact_CM_month <- impact_CM_period / months_n
data.frame(
CA_total = round(CA_total, 2),
delta_CA_5pct = round(delta_CA, 2),
low_category = "Electronics",
low_CM_rate = round(low$CM_rate, 6),
high_category = "Services",
high_CM_rate = round(high$CM_rate, 6),
impact_CM_period = round(impact_CM_period, 2),
impact_CM_per_month = round(impact_CM_month, 2)
)
## CA_total delta_CA_5pct low_category low_CM_rate high_category high_CM_rate
## 1 311249.8 15562.49 Electronics 0.327183 Services 0.706501
## impact_CM_period impact_CM_per_month
## 1 5903.12 327.95
Déplacer 5 points de CA d’Electronics (~32,7%) vers Services (~70,6%) :
Impact : +5 903 € de CM sur la période +328 € par mois Amélioration de rentabilité sans croissance du CA total.
Couple recommandé : SEO/Content (volume) Email/CRM (rentabilité)
Objectif : croître sans détériorer le CM rate global.
#5 Partie E - Plan d’action (recommandation chiffrée)
L’hypothèse consiste à répartir les coûts fixes annuels uniformément sur 12 mois. Cela simplifie l’analyse mais peut fausser la lecture mensuelle.
Limites principales :
Saisonnalité réelle des coûts
Certaines charges ne sont pas linéaires (bonus annuels, primes, maintenance IT, renouvellements logiciels).
Exemple : si une prime annuelle est payée en mars, le lissage masque un pic réel de cash-out.
Contrats non parfaitement annuels
Certains coûts peuvent évoluer en cours d’année (renégociation, embauche, licenciement).
Le modèle suppose une structure constante → ce qui est rarement vrai.
Distorsion du profit mensuel
Un mois peut apparaître rentable alors qu’en réalité il subventionne un futur pic de dépenses.
On mesure une rentabilité “économique moyenne”, pas une rentabilité cash réelle.
Conclusion : Le lissage est acceptable pour une lecture stratégique moyenne, mais insuffisant pour une analyse fine de trésorerie ou de saisonnalité.
Observer qu’un canal est performant (ex : SEO ou Email/CRM) ne signifie pas qu’il est la cause directe de la performance.
Pourquoi ?
Corrélation ≠ causalité
Un canal peut apparaître rentable car il capte une demande déjà existante.
Exemple : SEO peut convertir des clients déjà convaincus par d’autres canaux.
Problème d’attribution
Modèle last-click biaisé.
Email peut “récupérer” la conversion finale alors que l’acquisition initiale vient d’un autre canal.
Effet halo entre canaux
Paid Search peut nourrir la notoriété qui améliore ensuite le SEO.
Les canaux interagissent.
Ce qu’il faudrait pour valider la causalité :
A/B tests (couper un canal sur une zone test)
Tests d’incrémentalité
Modèle d’attribution multi-touch
Holdout groups (exclusion partielle d’audience)
1- Contrôle des doublons
Vérifier l’unicité des commandes (order_id).
Un doublon gonfle artificiellement le CA et la CM.
Impact direct sur toutes les métriques.
2- Vérification de la granularité des jointures
Si les coûts fixes sont joints à chaque ligne transactionnelle → duplication.
Vérifier les clés de jointure (month, year, category, etc.).
Confirmer qu’on ne multiplie pas artificiellement les coûts.