library(tidyverse)
library(scales)
library(lubridate)
library(knitr)
library(zoo)
source("C:/Users/teore/OneDrive/OC_DataAnalyst/P6/euro.R")
customers <- read_csv("customers.csv", col_types = c('cfd'))
products <- read_csv('products.csv', col_types = c('cdf'))
transactions <- read_csv('transactions.csv')
Problèmes de décodage dans le fichier transactions. Il s’agit de lignes de test avec id T_0 donc on peut laisser ces valeurs nulles ou bien les retirer.
glimpse(products)
## Rows: 3,287
## Columns: 3
## $ id_prod <chr> "0_1421", "0_1368", "0_731", "1_587", "0_1507", "0_1163", "1_4~
## $ price <dbl> 19.99, 5.13, 17.99, 4.99, 3.99, 9.99, 36.99, 34.99, 16.99, 18.~
## $ categ <fct> 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,~
glimpse(transactions)
## Rows: 679,532
## Columns: 4
## $ id_prod <chr> "0_1518", "1_251", "0_1277", "2_209", "0_1509", "0_1418", "~
## $ date <dttm> 2022-05-20 13:21:29, 2022-02-02 07:55:19, 2022-06-18 15:44~
## $ session_id <chr> "s_211425", "s_158752", "s_225667", "s_52962", "s_325227", ~
## $ client_id <chr> "c_103", "c_8534", "c_6714", "c_6941", "c_4232", "c_1478", ~
transactions %>% filter(is.na(date)) %>% unique()
## # A tibble: 2 x 4
## id_prod date session_id client_id
## <chr> <dttm> <chr> <chr>
## 1 T_0 NA s_0 ct_0
## 2 T_0 NA s_0 ct_1
#les lignes dont la date a été traduite par des valeurs nulles sont que de tests; on pourra retirer le même id sur le fichier produits
transactions <- na.omit(transactions)
products[!products$id_prod == 'T_0',] -> products
sum(duplicated(products$id_prod))
## [1] 0
Comparaison entre les id des deux dataframes. Une valeur de product_id du fichier transactions n’est pas présente dans products. 21 références du fichier products ne paraissent pas dans transactions.
diffprodtr <- setdiff(products$id_prod, transactions$id_prod)
difftrprod <- setdiff(transactions$id_prod, products$id_prod)
transactions[transactions$id_prod == '0_2245',]
## # A tibble: 221 x 4
## id_prod date session_id client_id
## <chr> <dttm> <chr> <chr>
## 1 0_2245 2022-09-23 07:22:38 s_272266 c_4746
## 2 0_2245 2022-07-23 09:24:14 s_242482 c_6713
## 3 0_2245 2022-12-03 03:26:35 s_306338 c_5108
## 4 0_2245 2021-08-16 11:33:25 s_76493 c_1391
## 5 0_2245 2022-07-16 05:53:01 s_239078 c_7954
## 6 0_2245 2023-01-21 18:39:25 s_330241 c_6268
## 7 0_2245 2022-11-20 20:21:06 s_300389 c_8524
## 8 0_2245 2021-10-20 13:11:05 s_107564 c_1746
## 9 0_2245 2021-04-17 16:43:16 s_21906 c_7808
## 10 0_2245 2022-02-28 18:08:49 s_172304 c_4964
## # ... with 211 more rows
products[products$id_prod %in% diffprodtr,]
## # A tibble: 21 x 3
## id_prod price categ
## <chr> <dbl> <fct>
## 1 0_1016 35.1 0
## 2 0_1780 1.67 0
## 3 0_1062 20.1 0
## 4 0_1119 2.99 0
## 5 0_1014 1.15 0
## 6 1_0 31.8 1
## 7 0_1318 20.9 0
## 8 0_1800 22.0 0
## 9 0_1645 2.99 0
## 10 0_322 2.99 0
## # ... with 11 more rows
pdtr <- merge(products, transactions, on='product_id')
head(pdtr)
## id_prod price categ date session_id client_id
## 1 0_0 3.75 0 2022-11-05 12:19:02 s_293040 c_6343
## 2 0_0 3.75 0 2021-09-07 21:38:41 s_86520 c_7587
## 3 0_0 3.75 0 2021-05-08 12:18:32 s_31643 c_2204
## 4 0_0 3.75 0 2022-08-23 12:05:45 s_257317 c_5104
## 5 0_0 3.75 0 2021-05-08 05:55:57 s_31508 c_7219
## 6 0_0 3.75 0 2022-11-23 19:17:58 s_301733 c_7532
glimpse(pdtr)
## Rows: 679,111
## Columns: 6
## $ id_prod <chr> "0_0", "0_0", "0_0", "0_0", "0_0", "0_0", "0_0", "0_0", "0_~
## $ price <dbl> 3.75, 3.75, 3.75, 3.75, 3.75, 3.75, 3.75, 3.75, 3.75, 3.75,~
## $ categ <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,~
## $ date <dttm> 2022-11-05 12:19:02, 2021-09-07 21:38:41, 2021-05-08 12:18~
## $ session_id <chr> "s_293040", "s_86520", "s_31643", "s_257317", "s_31508", "s~
## $ client_id <chr> "c_6343", "c_7587", "c_2204", "c_5104", "c_7219", "c_7532",~
#création des intervalles par semaine et mois
pdtr$week <- as.Date(cut(pdtr$date,breaks = "week"))
pdtr$month <- as.Date(cut(pdtr$date,breaks = "month"))
ca_m <- pdtr %>% group_by(month) %>% summarise(CA = sum(price)) # CA par mois
ca_h <- pdtr %>% group_by(week) %>% summarise(CA = sum(price)) # CA par semaine
ca_j <- pdtr %>% group_by(day = as.Date(date)) %>% summarise(CA = sum(price)) # CA par jour
ca_m %>% summarise(max(CA), min(CA), mean(CA), median(CA), sd(CA)) %>% mutate(across(.fns=euro)) %>% kable(caption = "CA mensuel global")
| max(CA) | min(CA) | mean(CA) | median(CA) | sd(CA) |
|---|---|---|---|---|
| 535.572€ | 320.799€ | 493.905€ | 501.566€ | 41.208,74€ |
summarise(ca_h, max(CA), min(CA), mean(CA), median(CA), sd(CA)) %>% mutate(across(.fns=euro)) %>% kable(caption = "CA hebdomadaire global")
| max(CA) | min(CA) | mean(CA) | median(CA) | sd(CA) |
|---|---|---|---|---|
| 136.415€ | 37.275,96€ | 112.893€ | 114.583€ | 12.674,93€ |
summarise(ca_j, max(CA), min(CA), mean(CA), median(CA), sd(CA)) %>% mutate(across(.fns=euro)) %>% kable(caption = "CA journalier global")
| max(CA) | min(CA) | mean(CA) | median(CA) | sd(CA) |
|---|---|---|---|---|
| 20.487,68€ | 8.076,04€ | 16.237,98€ | 16.223,30€ | 1.847,91€ |
sd <- sd(ca_m$CA)
ggplot(data = ca_m,aes(month, CA)) +
geom_col(fill = "steelblue") + geom_hline(yintercept = mean(ca_m$CA), colour ='red') +
scale_x_date(date_labels = "%b-%Y" ,breaks = "2 month") +
theme(axis.text.x = element_text(angle = 90)) +
labs(x = '', y = 'Totale des ventes', title = "Chiffre d'affaires mensuel") +
scale_y_continuous(labels = euro, n.breaks = 6)
#adding rolling average
ca_m %>% mutate(rolavr = rollmean(x = CA, k = 3, align = "center", na.pad = TRUE)) -> ca_m
ca_h %>% mutate(rolavr = rollmean(x = CA, k = 4, align = "center", na.pad = TRUE)) -> ca_h
ca_h[ca_h$week == '2023-02-13',]$rolavr = NA
# graph by week:
colors <- c("Total" = "steelblue", "Moyenne Mensuelle" = "black", "Moyenne Mobile" = "red")
ggplot(data = pdtr,
aes(week, price)) +
stat_summary(aes(color='Total'), fun = sum,
geom = "line", size = 2) + geom_line(data = ca_h, aes(x=week, y=rolavr,color='Moyenne Mobile'),show.legend = TRUE, size=1) + geom_hline(aes(color="Moyenne Mensuelle"), yintercept = mean(ca_h$CA),linetype='dotted',show.legend = TRUE) + scale_x_date(labels = date_format('%b-%Y'), breaks = "3 month", limits = c(min(pdtr$week), max(pdtr$week) -7)) +
labs(title="Evolution du chiffre d'affaires hebdomadaire", y="Total hebdomadaire",x="") + scale_y_continuous(labels=euro) + scale_color_manual(values =colors) +theme_minimal() + theme(axis.text.x = element_text(angle = 90), legend.title = element_blank())
On analysera les prix listés dans le fichier products pour avoir un coup d’œil sur les prix et leur indicateurs.
products %>% group_by(categ) %>% summarise(min(price), max(price), mean(price), median(price), sd(price))
## # A tibble: 3 x 6
## categ `min(price)` `max(price)` `mean(price)` `median(price)` `sd(price)`
## <fct> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0 0.62 41.0 11.7 10.3 7.57
## 2 1 2 81.0 25.5 23.0 15.4
## 3 2 31.0 300 108. 102. 49.6
products %>% ggplot(aes(categ, price)) + geom_boxplot(aes(fill=categ)) + scale_y_continuous(labels=euro, n.breaks=8) + colScale + labs(x = "Categorie", y = "Prix")
On remarque la présence des trois tranches des prix bien demarquées et que la catégorie “2” se distingue nettement de deux autres avec une médiane de ~100€. Regardons maintenant le chiffre global par catégorie et le top produits:
ventes_tot_cat <- pdtr %>% group_by(categ) %>% summarise(total = sum(price))
ventes_tot_prod <- pdtr %>% group_by(id_prod, categ) %>% summarise(n_ventes = n(), total = sum(price)) %>% arrange(desc(total))
ventes_tot_cat %>% mutate(total = euro(total)) %>% kable(caption = 'Ventes totales par categorie')
| categ | total |
|---|---|
| 0 | 4.419.731€ |
| 1 | 4.653.723€ |
| 2 | 2.780.275€ |
head(ventes_tot_prod,10) %>% mutate(total = euro(total)) %>% kable(align = c('lccr'), caption='Top 10 produits')
| id_prod | categ | n_ventes | total |
|---|---|---|---|
| 2_159 | 2 | 650 | 94.893,50€ |
| 2_135 | 2 | 1005 | 69.334,95€ |
| 2_112 | 2 | 968 | 65.407,76€ |
| 2_102 | 2 | 1027 | 60.736,78€ |
| 2_209 | 2 | 814 | 56.971,86€ |
| 1_395 | 1 | 1875 | 54.356,25€ |
| 1_369 | 1 | 2252 | 54.025,48€ |
| 2_110 | 2 | 865 | 53.846,25€ |
| 2_39 | 2 | 915 | 53.060,85€ |
| 2_166 | 2 | 228 | 52.449,12€ |
tail(ventes_tot_prod,10) %>% mutate(total = euro(total)) %>% kable(align = c('lccr'), caption='Le 10 produits moins vendus')
| id_prod | categ | n_ventes | total |
|---|---|---|---|
| 0_1840 | 0 | 2 | 2,56€ |
| 0_898 | 0 | 2 | 2,54€ |
| 0_1498 | 0 | 1 | 2,48€ |
| 0_1728 | 0 | 1 | 2,27€ |
| 0_1601 | 0 | 1 | 1,99€ |
| 0_541 | 0 | 1 | 1,99€ |
| 0_807 | 0 | 1 | 1,99€ |
| 0_1653 | 0 | 2 | 1,98€ |
| 0_1284 | 0 | 1 | 1,38€ |
| 0_1539 | 0 | 1 | 0,99€ |
ggplot(ventes_tot_cat %>% ungroup(), aes(x=categ, y=total, fill= categ)) + geom_col(color="white", position = 'dodge') + scale_y_continuous(labels = euro) +
labs(title = "Chiffre d'affaires par categorie", x="Categorie", y="Total") + colScale
top20r <- ventes_tot_prod %>% ungroup() %>% slice_max(total, n = 20, with_ties = FALSE)
top20nv <- ventes_tot_prod %>% ungroup() %>% slice_max(n_ventes, n = 20, with_ties = FALSE)
#distribution n ventes?
ggplot(top20r, aes(x = fct_reorder(id_prod, total, .desc = TRUE), y= total, fill= categ)) + geom_col() + labs(title='Top 20 produits par revenu', x='Id Produit', y='Total ventes') + scale_y_continuous(labels=euro, n.breaks = 7) + colScale + theme(axis.text.x = element_text(angle = 90))
ggplot(top20nv, aes(x = fct_reorder(id_prod, n_ventes, .desc = TRUE), y= n_ventes, fill= categ)) + geom_col() + labs(title='Top 20 produits le plus vendus', x='Id Produit', y='Total ventes') + colScale + theme(axis.text.x = element_text(angle = 90))
La catégorie 1 c’est celle qui apporte le plus de revenus mais elle ne dépasse pas largement la catégorie 0. Le top produits sont de catégorie 2 et 1 (la catégorie qui a le prix moyen plus fort) et on ne retrouve pas de produits cat. 0, qui pourtant dépasse la 2 en CA totale. Entre le produits le plus vendu on retrouve plusieurs références de cat1 qui sont aussi en tête de liste pour revenus.
Nous allons joindre le fichier pdtr créé précedemment au fichier customers qui contient en outre que l’identifiant du client, son genre et son année de naissance.
str(customers)
## spec_tbl_df [8,623 x 3] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ client_id: chr [1:8623] "c_4410" "c_7839" "c_1699" "c_5961" ...
## $ sex : Factor w/ 2 levels "f","m": 1 1 1 1 2 2 1 1 2 2 ...
## $ birth : num [1:8623] 1967 1975 1984 1962 1943 ...
## - attr(*, "spec")=
## .. cols(
## .. client_id = col_character(),
## .. sex = col_factor(levels = NULL, ordered = FALSE, include_na = FALSE),
## .. birth = col_double()
## .. )
sum(duplicated(customers$client_id)) # les id clients sont uniques
## [1] 0
all(is.element(pdtr$client_id, customers$client_id)) # toutes les id client du fichier pdtr sont inclues dans customers
## [1] TRUE
setdiff(customers$client_id, pdtr$client_id) # quelques id ne sont pas références dans transactions, on peut les ignorer
## [1] "c_8253" "c_3789" "c_4406" "ct_0" "c_2706" "c_3443" "c_4447" "c_3017"
## [9] "c_4086" "c_6930" "c_4358" "c_8381" "c_1223" "c_6862" "c_5245" "c_5223"
## [17] "c_6735" "c_862" "c_7584" "c_90" "c_587" "ct_1" "c_3526"
pdtr_cust <- merge(pdtr, customers, on = 'client_id')
str(pdtr_cust)
## 'data.frame': 679111 obs. of 10 variables:
## $ client_id : chr "c_1" "c_1" "c_1" "c_1" ...
## $ id_prod : chr "0_1448" "1_713" "0_1429" "0_1475" ...
## $ price : num 18.94 33.99 7.99 11.99 8.99 ...
## $ categ : Factor w/ 3 levels "0","1","2": 1 2 1 1 1 2 1 2 1 1 ...
## $ date : POSIXct, format: "2021-07-26 17:37:29" "2021-11-15 20:40:00" ...
## $ session_id: chr "s_67467" "s_120172" "s_105105" "s_230001" ...
## $ week : Date, format: "2021-07-26" "2021-11-15" ...
## $ month : Date, format: "2021-07-01" "2021-11-01" ...
## $ sex : Factor w/ 2 levels "f","m": 2 2 2 2 2 2 2 2 2 2 ...
## $ birth : num 1955 1955 1955 1955 1955 ...
summary(pdtr_cust)
## client_id id_prod price categ
## Length:679111 Length:679111 Min. : 0.62 0:415459
## Class :character Class :character 1st Qu.: 8.87 1:227169
## Mode :character Mode :character Median : 13.99 2: 36483
## Mean : 17.45
## 3rd Qu.: 18.99
## Max. :300.00
## date session_id week
## Min. :2021-03-01 00:01:07 Length:679111 Min. :2021-03-01
## 1st Qu.:2021-09-08 09:15:03 Class :character 1st Qu.:2021-09-06
## Median :2022-03-03 07:40:49 Mode :character Median :2022-02-28
## Mean :2022-03-03 15:15:06 Mean :2022-02-28
## 3rd Qu.:2022-08-31 00:01:46 3rd Qu.:2022-08-29
## Max. :2023-02-28 23:58:30 Max. :2023-02-27
## month sex birth
## Min. :2021-03-01 f:338285 Min. :1929
## 1st Qu.:2021-09-01 m:340826 1st Qu.:1970
## Median :2022-03-01 Median :1980
## Mean :2022-02-16 Mean :1978
## 3rd Qu.:2022-08-01 3rd Qu.:1987
## Max. :2023-02-01 Max. :2004
Analysons maintenant le chiffre d’affaire par client, pour mettre en évidence les clients principaux.
achatsxclient <- pdtr_cust %>% group_by(client_id) %>% summarise(tot_achats = sum(price)) %>% arrange(desc(tot_achats))
achatsxclient %>% head(10) %>% mutate(tot_achats = euro(tot_achats)) %>% kable()
| client_id | tot_achats |
|---|---|
| c_1609 | 324.033€ |
| c_4958 | 289.760€ |
| c_6714 | 153.599€ |
| c_3454 | 113.638€ |
| c_3263 | 5.277€ |
| c_1570 | 5.272€ |
| c_2899 | 5.214€ |
| c_2140 | 5.209€ |
| c_7319 | 5.156€ |
| c_7959 | 5.085€ |
achatsxclient %>% ggplot(aes(tot_achats)) + geom_boxplot() + scale_x_continuous(label=euro) + theme(axis.ticks.y = element_blank(), axis.text.y = element_blank())
Le quatre clients en tête de liste constituent le ~ 0.05 % des clients totaux et il génèrent a peut près le 7.5% du chiffre d’affaires totale:
## % du CA totale des clients principaux: 0.07432518
## coefficient gini: 0.4463865
On peut donc supposer que les premiers quatre clients, vu leur total d’achats soient des business. Pour étudier les clients “normaux” on verra donc de le mettre a coté.
business_id <- achatsxclient[1:4, "client_id"]$client_id
clients <- pdtr_cust %>% filter(!client_id %in% business_id)
b_to_b <- pdtr_cust[pdtr_cust$client_id %in% business_id,]
Examinons maintenant la distribution des age des clients.
# binning by birth year
clients$agegroup <-cut(clients$birth, 5, dig.lab = 10)
n_age <- clients %>% group_by(birth,sex) %>% distinct(client_id) %>% count() %>% arrange(desc(n))
n_clients <- clients %>% group_by(agegroup, sex) %>% distinct(client_id,sex) %>% count() %>% arrange(desc(n))
clients %>% distinct(client_id,birth) %>% summarise(min(birth), max(birth), mean(birth), median(birth), IQR(birth), sd(birth))
## min(birth) max(birth) mean(birth) median(birth) IQR(birth) sd(birth)
## 1 1929 2004 1978.26 1979 26 16.91054
clients %>% group_by(client_id) %>% distinct(client_id,sex,birth) %>% ggplot(aes(x = sex, y = birth, fill=sex)) + geom_boxplot() + labs(x="Sexe", y="Année de naissance")
n_age %>% ggplot(aes(birth, n)) + geom_col(aes(fill=sex), color="white") + scale_y_continuous(breaks = scales::pretty_breaks(n = 10)) + labs(title="Nombre des clients par age et sexe")
n_clients %>% ggplot(aes(agegroup, n)) + geom_col(aes(fill=sex), color="white") + scale_y_continuous(breaks = scales::pretty_breaks(n = 10)) + labs(x="Tranche d'age", y="Nombre de personnes")
La répartition par sexe des clients est a peu près égale, avec une prévalence des générations over 1974. L’année de naissance plus présent est le 2004 avec ~430 membres.
clients %>% group_by(agegroup) %>% summarise(total_ventes = sum(price))%>% arrange(desc(total_ventes)) %>% mutate(total_ventes = euro(total_ventes))
## # A tibble: 5 x 2
## agegroup total_ventes
## <fct> <chr>
## 1 (1974,1989] 4.058.100€
## 2 (1989,2004.075] 3.466.544€
## 3 (1959,1974] 2.202.166€
## 4 (1944,1959] 1.013.221€
## 5 (1928.925,1944] 232.666€
clients %>% ggplot(aes(x=agegroup,fill=categ)) + geom_bar(aes(weight = price), color='grey') + scale_y_continuous(labels=euro) + colScale + labs(x='Année de naissance', y='Ventes totales')
clients %>% ggplot(aes(x=categ,fill=categ)) + geom_bar(aes(weight = price), color='grey') + facet_grid(.~ agegroup) + scale_y_continuous(labels=euro) + colScale + labs(x='', y='Ventes totales')
C’est évidente la préférence de la tranche (1989, 2005) pour le produits de catégorie deux, catégorie sur laquelle ils ont presque l’exclusivité et engendre le plus gros revenus (toute tranche d’age confondue). En revanche, la tranche d’age qui contribue le plus au chiffre d’affaire c’est 1974-1989 avec un CA totale de 4M €.
#création du df stats_clients
achats_clients_cat <- clients %>%
group_by(client_id) %>%
count(categ) %>%
pivot_wider(names_from = categ, values_from = n, names_glue = "cat_{categ}", values_fill = 0)
achats_clients <- clients %>%
group_by(client_id,agegroup,sex, birth) %>%
summarise(tot_achats = sum(price), achats_mois = n()/24)
achats_session <- clients %>% group_by(client_id,session_id) %>% summarise(tot_session = sum(price)) %>% group_by(client_id) %>% summarise(panier_moyen = median(tot_session))
stats_clients <- merge(achats_clients, achats_session )
stats_clients <- merge(stats_clients, achats_clients_cat)
head(stats_clients)
## client_id agegroup sex birth tot_achats achats_mois panier_moyen cat_0
## 1 c_1 (1944,1959] m 1955 558.18 1.6250000 11.990 30
## 2 c_10 (1944,1959] m 1956 1353.60 2.4166667 29.005 20
## 3 c_100 (1989,2004.075] m 1992 254.85 0.3333333 15.990 2
## 4 c_1000 (1959,1974] f 1966 2261.89 5.2083333 22.990 43
## 5 c_1001 (1974,1989] m 1982 1812.86 4.2500000 33.610 70
## 6 c_1002 (1974,1989] f 1982 415.78 1.5000000 20.980 30
## cat_1 cat_2
## 1 8 1
## 2 34 4
## 3 5 1
## 4 82 0
## 5 31 1
## 6 6 0
achats_clients %>% group_by(agegroup) %>% summarise(tot_age = sum(tot_achats)) %>% mutate(pourcentage = tot_age / sum(tot_age) * 100) -> ca_age
ca_age %>% ggplot(aes(x= fct_reorder(agegroup, pourcentage), y=pourcentage, fill=agegroup)) + geom_bar(stat='identity', color="black", position = "dodge") + scale_y_continuous(labels=euro) + geom_text(aes(y = pourcentage, label = percent(pourcentage/100)), size=5, nudge_y = 1.5) + theme_void() + labs(title="Pourcentage du CA total par tranche d'age")
psex <- clients %>% group_by(sex) %>% summarise(total = sum(price))%>% mutate(p = total/sum(total))
psex %>% ggplot(aes(x=sex, y=total, fill=sex)) + geom_col() + scale_y_continuous(labels = euro) + geom_text(data=psex, aes(label = percent(p))) + labs(title = "Repartition par genre du chiffre d'affaires total")
Nous allons nous poser la question suivante juste a but didactique. Il faudra remarquer que plusieurs tests statistiques ont un sens seulement si appliqués sur des échantillons de la population (test de comparaison de la variance, t-test, ANOVA). Ici nous travaillons sur la totalité de la population, donc ces paramétres nous les connaissons déjà.
Est-ce qu’il existe une différence significative entre les totaux d’achats des clients des deux sexes ?
Conditions pour un t-test
1)les deux échantillons doivent être indépendants et normalement distribués.
2)Ça sera alors possible de tester l’égalité de la variance et successivement l’égalité des moyennes.
stats_clients %>% group_by(sex) %>% summarise(mean(tot_achats), median(tot_achats)) %>% kable()
| sex | mean(tot_achats) | median(tot_achats) |
|---|---|---|
| f | 1274.248 | 1029.56 |
| m | 1278.925 | 1025.90 |
achats_clients %>% ggplot(aes(x=sex, y=tot_achats, fill=sex)) + geom_boxplot()+ stat_summary(fun=mean) + scale_y_continuous(label=euro, n.breaks = 10) + theme_minimal()
achats_clients %>% ggplot(aes(x=tot_achats, fill=sex)) + geom_histogram(aes(y=..density..), color="white") + geom_density(alpha=0.5) + facet_wrap(~sex) +scale_y_continuous(labels = scales::percent) + scale_x_continuous(label=euro)
op = par(mfrow=c(1,2))
qqnorm(subset(achats_clients, sex=='m')$tot_achats)
qqline(subset(achats_clients, sex=='m')$tot_achats)
qqnorm(subset(achats_clients, sex=='f')$tot_achats)
qqline(subset(achats_clients, sex=='f')$tot_achats)
par(op)
#les deux distributions ne remplissent les conditions de normalité. On procédera a un test non-paramétrique, qui dans ce cas testera l'hypothese d'egalité des medianes des deux groupes:
wilcox.test(achats_clients$tot_achats[achats_clients$sex=='m'],achats_clients$tot_achats[achats_clients$sex=='f'])
##
## Wilcoxon rank sum test with continuity correction
##
## data: achats_clients$tot_achats[achats_clients$sex == "m"] and achats_clients$tot_achats[achats_clients$sex == "f"]
## W = 9218727, p-value = 0.9898
## alternative hypothesis: true location shift is not equal to 0
On ne rejettera donc pas H0 muhommes == mufemmes et on ne concluera que le sexe n’as pas d’influence sur la somme des achats d’un client.
#statistiques b to c
clients %>% group_by(month) %>% summarise(CA = sum(price)) %>% summarise(max(CA), min(CA), mean(CA), median(CA), sd(CA)) %>% mutate(across(.fns=euro)) %>% kable(caption = "CA mensuel")
| max(CA) | min(CA) | mean(CA) | median(CA) | sd(CA) |
|---|---|---|---|---|
| 492.927€ | 296.728€ | 457.196€ | 464.510€ | 38.382,21€ |
clients %>% group_by(week) %>% summarise(CA = sum(price)) %>% summarise( max(CA), min(CA), mean(CA), median(CA), sd(CA)) %>% mutate(across(.fns=euro)) %>% kable(caption = "CA hebdomadaire")
| max(CA) | min(CA) | mean(CA) | median(CA) | sd(CA) |
|---|---|---|---|---|
| 124.078€ | 34.644,42€ | 104.502€ | 105.832€ | 11.727,85€ |
clients %>% group_by(day = as.Date(date)) %>% summarise(CA = sum(price)) %>% summarise(max(CA), min(CA), mean(CA), median(CA), sd(CA)) %>% mutate(across(.fns=euro)) %>% kable(caption = "CA journalier")
| max(CA) | min(CA) | mean(CA) | median(CA) | sd(CA) |
|---|---|---|---|---|
| 19.155,90€ | 7.396,45€ | 15.031,09€ | 15.054,34€ | 1.715,58€ |
ca_mois_c <- clients %>% group_by(month) %>% summarise(CA = sum(price)) %>% mutate(rolavr = rollmean(CA,3, na.pad = TRUE))
ca_week_c <- clients %>% group_by(week) %>% summarise(CA = sum(price)) %>% mutate(rolavr = rollmean(CA,3, na.pad = TRUE))
ggplot(data = clients,
aes(month, price)) +
stat_summary(aes(fill=agegroup),fun = sum,
geom = "bar", position="stack") + geom_line(data = ca_mois_c, aes(x = month, y=rolavr), color="red",size=1) +
scale_x_date(labels = date_format("%b-%Y"), limits = c(min(clients$week), max(clients$week) - 7)) + labs(title="Evolution du chiffre d'affaires hebdomadaire BtoC par tranche d'age", y="",x="") + scale_y_continuous(labels=euro) + theme(axis.text.x = element_text(angle = 90)) + geom_vline(data = ca_j, aes(xintercept = as.Date('2021-10-01')), linetype='dotted')
ggplot(data = clients,
aes(week, price)) +
stat_summary(aes(fill=categ),geom='bar',fun = sum,size=1,position ='stack') + geom_line(data = ca_week_c, aes(x = week, y=rolavr), color="red",size=1) +
colScale +
scale_x_date(labels = date_format("%b-%Y"), breaks = "3 months") + labs(title="Evolution du chiffre d'affaire BtoC hebdomadaire par categorie", y="",x="") + scale_y_continuous(labels=euro) + theme(axis.text.x = element_text(angle = 90)) + geom_vline(data = ca_j, aes(xintercept = as.Date('2021-10-01')), linetype='dotted')
# B to B
ca_week_btb <- b_to_b %>% group_by(week) %>% summarise(CA = sum(price)) %>% mutate(rolavr = rollmean(CA,3, na.pad = TRUE))
ggplot(data = b_to_b,
aes(week, price)) +
stat_summary(aes(fill=categ),geom='bar',fun = sum,position ='stack') + geom_line(data = ca_week_btb, aes(x = week, y=rolavr), color="red", size=1) +
colScale +
scale_x_date(labels = date_format("%b-%Y"), breaks = "3 month") + labs(title="Evolution du chiffre d'affaires hebdomadaire BtoB par categorie", y="",x="") + scale_y_continuous(labels=euro) + theme(axis.text.x = element_text(angle = 90)) + geom_vline(data = ca_j, aes(xintercept = as.Date('2021-10-01')), linetype='dotted')
On remarque une evidente chute du CA alentour Oct. 2, avec une baisse considérable des ventes de cat 1. En effet aucun produit de catégorie 1 a été vendu pendant le mois d’octobre 2021.
Pour ce genre de corrélation entre variables qualitatives nous irons utiliser un test chi2:
library(broom)
sc <- table(clients$sex, clients$categ)
chi.sc <- chisq.test(sc)
print(chi.sc)
##
## Pearson's Chi-squared test
##
## data: sc
## X-squared = 20.213, df = 2, p-value = 4.08e-05
augment(chi.sc)
## # A tibble: 6 x 9
## Var1 Var2 .observed .prop .row.prop .col.prop .expected .resid .std.resid
## <fct> <fct> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 f 0 200793 0.317 0.610 0.518 201518. -1.62 -3.75
## 2 m 0 186488 0.295 0.615 0.482 185763. 1.68 3.75
## 3 f 1 111331 0.176 0.338 0.524 110493. 2.52 4.46
## 4 m 1 101017 0.160 0.333 0.476 101855. -2.62 -4.46
## 5 f 2 16980 0.0268 0.0516 0.517 17092. -0.858 -1.27
## 6 m 2 15868 0.0251 0.0523 0.483 15756. 0.894 1.27
ggplot(data = augment(chi.sc), aes(x=Var1, y=Var2)) +
geom_tile(aes(fill=.resid)) +
geom_text(aes(label=round(.resid,2))) +
labs(x="Sexe", y="Categorie") + scale_fill_gradient2() + theme_minimal() +theme(legend.position = "none")
Le test permet de rejeter H0 (sexe et catégorie d’achats sont indépendants): il y a donc, à un niveau alpha de 0.05, une corrélation entre le deux variables. Les différence plus accentués sont remarquables pour les achats de catégorie 1.
age,agegroup et autre variablesEncore une fois, nous aborderons l’analyse suivante à but démonstratif, car on sait déjà qu’il existe une différence entre le moyennes des groupes étudiés.
Pour utiliser une ANOVA pour étudier la relation entre tranche d’age et montant des totale des achats on doit vérifier les conditions:
1) Les mesures sont indépendantes
2) Les échantillons ont des variances égales
3) Au moins 20 individus par échantillon, ou normalité des populations de chaque échantillon supposée ou vérifiée
stats_clients %>% group_by(agegroup) %>%summarise(total = sum(tot_achats), var=var(tot_achats), mean=mean(tot_achats)) %>% kable()
| agegroup | total | var | mean |
|---|---|---|---|
| (1928.925,1944] | 232666.2 | 401305.2 | 891.4413 |
| (1944,1959] | 1013221.5 | 426121.1 | 911.9905 |
| (1959,1974] | 2202166.1 | 685199.4 | 1076.8538 |
| (1974,1989] | 4058100.5 | 1171571.2 | 1548.8933 |
| (1989,2004.075] | 3466543.9 | 899671.0 | 1354.6479 |
stats_clients %>% group_by(agegroup) %>% ggplot(aes(agegroup, tot_achats, fill= agegroup)) +geom_boxplot() + stat_summary(fun=mean) + scale_y_continuous(label=euro)
ggplot(stats_clients, aes(sample=tot_achats, group=agegroup)) +geom_qq() + geom_qq_line() + facet_wrap(~agegroup)
#ratio max var / min var >> 2
Même en acceptant la première condition d’indépendance, on remarquera que les distributions des totaux par catégorie d’age ne remplissent pas la condition de normalité et le ratio entre les variances max/min est supérieur à 2.Nous allons nous servir donc du test non paramétrique de Kruskal-Wallis avant d’étudier la relation entre age considérée comme variable continue et total d’achats.
kruskal.test(stats_clients$tot_achats, stats_clients$agegroup)
##
## Kruskal-Wallis rank sum test
##
## data: stats_clients$tot_achats and stats_clients$agegroup
## Kruskal-Wallis chi-squared = 481.41, df = 4, p-value < 2.2e-16
H0(égalité des moyennes entre groupes) est facilement rejeté.
D’après le résultat du test de Kruskal-Wallis, nous savons qu’il existe une différence significative entre les groupes, mais nous ne savons pas quelles paires de groupes sont différentes.
Il est possible d’utiliser la fonction pairwise.wilcox.test() pour calculer des comparaisons par paires entre les niveaux de groupe avec des corrections pour les tests multiples.
pairwise.wilcox.test(stats_clients$tot_achats, stats_clients$agegroup)
##
## Pairwise comparisons using Wilcoxon rank sum test with continuity correction
##
## data: stats_clients$tot_achats and stats_clients$agegroup
##
## (1928.925,1944] (1944,1959] (1959,1974] (1974,1989]
## (1944,1959] 0.7840 - - -
## (1959,1974] 0.0085 2.0e-05 - -
## (1974,1989] < 2e-16 < 2e-16 < 2e-16 -
## (1989,2004.075] 4.2e-14 < 2e-16 < 2e-16 4.9e-08
##
## P value adjustment method: holm
Les moyennes de chaque groupe sont toutes significativement différentes l’une de l’autre exceptée la différence entre 1928-1944 et 1944-1959. La tranche d’age est corrélée a donc une corrélation avec la somme totale des achats pour la plupart des catégories.
Nous allons résumer les statistiques demandées sur un tableau ou les clients sont regroupé par années de naissance:
final_stats <- stats_clients %>% group_by(birth) %>% summarise(moy_achats_tot = mean(tot_achats), moy_achats_mois = mean(achats_mois), panier_moyen = median(panier_moyen))
stats_clients %>% group_by(agegroup) %>% summarise(moy_achats_tot = mean(tot_achats), moy_achats_mois = mean(achats_mois), panier_moyen = median(panier_moyen)) %>% kable()
| agegroup | moy_achats_tot | moy_achats_mois | panier_moyen |
|---|---|---|---|
| (1928.925,1944] | 891.4413 | 2.208972 | 18.99 |
| (1944,1959] | 911.9905 | 2.269802 | 18.91 |
| (1959,1974] | 1076.8538 | 2.895640 | 19.99 |
| (1974,1989] | 1548.8933 | 4.876256 | 27.94 |
| (1989,2004.075] | 1354.6479 | 1.780985 | 55.76 |
Ensuite nous allons étudier à l’aide de la regression lineaire la correlation entre l’année de naissance et les autre variables.
stats_clients %>% group_by(agegroup) %>% summarise(moy_tot_achats = mean(tot_achats), sd_tot_achats = sd(tot_achats))
## # A tibble: 5 x 3
## agegroup moy_tot_achats sd_tot_achats
## <fct> <dbl> <dbl>
## 1 (1928.925,1944] 891. 633.
## 2 (1944,1959] 912. 653.
## 3 (1959,1974] 1077. 828.
## 4 (1974,1989] 1549. 1082.
## 5 (1989,2004.075] 1355. 949.
ggplot(stats_clients, aes(agegroup, tot_achats, fill = agegroup)) + geom_boxplot(show.legend = FALSE) + stat_summary(fun =mean, show.legend = FALSE) + scale_y_continuous(label=euro, n.breaks = 6)
stats_clients %>% ggplot(aes(birth, tot_achats, color=agegroup)) + geom_jitter(alpha=0.5) + scale_y_continuous(label=euro)
final_stats %>% ggplot(aes(birth, moy_achats_tot)) + geom_point() + geom_smooth(method = "lm", se=FALSE) + scale_y_continuous(label=euro)
cor.test(~ tot_achats + birth, data=stats_clients)
##
## Pearson's product-moment correlation
##
## data: tot_achats and birth
## t = 17.782, df = 8594, p-value < 2.2e-16
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## 0.1679045 0.2086854
## sample estimates:
## cor
## 0.1883762
lr.agetot <- lm(tot_achats ~ birth, data=stats_clients)
lr.agemoytot <- lm(moy_achats_tot ~ birth, data=final_stats)
summary(lr.agetot)
##
## Call:
## lm(formula = tot_achats ~ birth, data = stats_clients)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1538.9 -700.7 -235.6 500.3 3987.3
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1.975e+04 1.183e+03 -16.70 <2e-16 ***
## birth 1.063e+01 5.978e-01 17.78 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 937.2 on 8594 degrees of freedom
## Multiple R-squared: 0.03549, Adjusted R-squared: 0.03537
## F-statistic: 316.2 on 1 and 8594 DF, p-value: < 2.2e-16
summary(lr.agemoytot)
##
## Call:
## lm(formula = moy_achats_tot ~ birth, data = final_stats)
##
## Residuals:
## Min 1Q Median 3Q Max
## -316.4 -150.5 -61.2 156.4 421.8
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -19623.05 2084.98 -9.412 2.79e-14 ***
## birth 10.56 1.06 9.961 2.61e-15 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 202.8 on 74 degrees of freedom
## Multiple R-squared: 0.5728, Adjusted R-squared: 0.567
## F-statistic: 99.21 on 1 and 74 DF, p-value: 2.611e-15
IL y a bien une corrélation significative, même si faible (r=0.19) entre tot_achats et birth. Si nous travaillons avec tous les données, un modèle linéaire est très faiblement significatif et permet d’expliquer seulement le 3% de variabilité de la variable dépendante. En revanche si on travaille avec la moyenne par groupe le modèle est beaucoup plus intéressant avec un R2 de 0.567.
stats_clients %>% group_by(agegroup) %>% summarise(p_moyen = mean(panier_moyen), sd_p_m = sd(panier_moyen))
## # A tibble: 5 x 3
## agegroup p_moyen sd_p_m
## <fct> <dbl> <dbl>
## 1 (1928.925,1944] 21.3 9.98
## 2 (1944,1959] 21.3 9.46
## 3 (1959,1974] 23.1 10.8
## 4 (1974,1989] 28.8 7.83
## 5 (1989,2004.075] 57.0 26.4
ggplot(stats_clients, aes(agegroup, panier_moyen, fill = agegroup)) + geom_boxplot(show.legend = FALSE) + stat_summary(fun =mean, show.legend = FALSE)
stats_clients %>% ggplot(aes(birth, panier_moyen, color=agegroup)) + geom_jitter(alpha=0.3) + scale_y_continuous(label=euro)
final_stats %>% ggplot(aes(birth, panier_moyen)) + geom_point() + geom_smooth(method='lm',se=FALSE) + geom_smooth(se=FALSE, linetype='dotted',color = 'orange') + scale_y_continuous(label=euro)
cor.test(stats_clients$birth, stats_clients$panier_moyen)
##
## Pearson's product-moment correlation
##
## data: stats_clients$birth and stats_clients$panier_moyen
## t = 67.42, df = 8594, p-value < 2.2e-16
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## 0.5741645 0.6018228
## sample estimates:
## cor
## 0.5881656
lr.pm <- lm(panier_moyen ~ birth, data=stats_clients)
summary(lr.pm)
##
## Call:
## lm(formula = panier_moyen ~ birth, data = stats_clients)
##
## Residuals:
## Min 1Q Median 3Q Max
## -46.806 -10.307 -3.432 6.339 273.398
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1.485e+03 2.254e+01 -65.88 <2e-16 ***
## birth 7.682e-01 1.139e-02 67.42 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 17.86 on 8594 degrees of freedom
## Multiple R-squared: 0.3459, Adjusted R-squared: 0.3459
## F-statistic: 4545 on 1 and 8594 DF, p-value: < 2.2e-16
Ici la relation est bien significative avec un r = 0.59 et un modèle linéaire est possible. Même si un modèle simple n’explique pas beaucoup de la variance, il est un point de départ.
stats_clients %>% group_by(agegroup) %>%summarise(n_achats_mois=mean(achats_mois), sd_achats_mois= sd(achats_mois))
## # A tibble: 5 x 3
## agegroup n_achats_mois sd_achats_mois
## <fct> <dbl> <dbl>
## 1 (1928.925,1944] 2.21 1.53
## 2 (1944,1959] 2.27 1.59
## 3 (1959,1974] 2.90 2.40
## 4 (1974,1989] 4.88 3.46
## 5 (1989,2004.075] 1.78 1.82
ggplot(stats_clients, aes(agegroup, achats_mois, fill = agegroup)) + geom_boxplot(show.legend = FALSE) + stat_summary(fun =mean, show.legend = FALSE)
stats_clients %>% ggplot(aes(birth, achats_mois, color=agegroup)) + geom_jitter(alpha=0.5)
final_stats %>% ggplot(aes(birth, moy_achats_mois)) + geom_point() + geom_smooth(method='lm') + geom_smooth(se=FALSE, color='orange', linetype='dotted')
cor.test(~ achats_mois + birth, data=stats_clients)
##
## Pearson's product-moment correlation
##
## data: achats_mois and birth
## t = -2.8239, df = 8594, p-value = 0.004756
## alternative hypothesis: true correlation is not equal to 0
## 95 percent confidence interval:
## -0.05155423 -0.00931282
## sample estimates:
## cor
## -0.03044712
lr.achm <- lm(achats_mois ~ birth, data=stats_clients)
summary(lr.achm)
##
## Call:
## lm(formula = achats_mois ~ birth, data = stats_clients)
##
## Residuals:
## Min 1Q Median 3Q Max
## -3.2128 -1.9150 -0.9193 0.9087 13.7602
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 13.134927 3.565862 3.684 0.000231 ***
## birth -0.005090 0.001802 -2.824 0.004756 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 2.826 on 8594 degrees of freedom
## Multiple R-squared: 0.000927, Adjusted R-squared: 0.0008108
## F-statistic: 7.974 on 1 and 8594 DF, p-value: 0.004756
Ici on remarque bien une evidente relation non lineaire(mais significative) entre les deux variables et en effet comme souvent dans ces cas le coefficient r est proche de 0.
On resumera ces relations à l’aide d’un pairplot:
library(GGally)
ggpairs(stats_clients, aes(color=agegroup,alpha=0.5), columns = c(4:7) , progress = FALSE) +theme_minimal()
Pour terminer nous allons étudier la relation entre ces deux variable, pour vérifier la leur dépendance éventuelle.
stats_clients %>% group_by(agegroup) %>% summarise(across(cat_0:cat_2, sum)) %>% pivot_longer(cols=cat_0:cat_2,names_to = 'categorie', values_to = 'nombre_achats') %>% ggplot() + geom_tile(aes(agegroup,categorie, fill=nombre_achats)) + scale_fill_viridis_c()
tac <- table(clients$agegroup, clients$categ)
chitac <- chisq.test(tac)
chitac
##
## Pearson's Chi-squared test
##
## data: tac
## X-squared = 174517, df = 8, p-value < 2.2e-16
ggplot(data = augment(chitac), aes(x=Var1, y=Var2)) +
geom_tile(aes(fill=.resid)) +
geom_text(aes(label=round(.resid,2))) +
labs(x="Tranche d'age", y="Categorie") + scale_fill_gradient2() + theme_minimal() +theme(legend.position = "none")
Avec une p-value < 2.2e-16 on rejette l’hypothèse nulle d’indépendance. On pourra remarquer principalement la forte valeur du résidu en catégorie 2 pour la tranche d’age 1989-2004.
Probabilité qu’un client achète la référence 0_525 sachant qu’il a acheté la référence 2_159
La probabilité conditionnelle à calculer est la suivante : P(0_525 | 2_159) = P(0_525) ^ P(2_159) / P(2_159)
getproba <- function(subset) {mean(subset)}
conditional <- function(proposition, given) {getproba(proposition[given])}
pdtr_cust %>% group_by(client_id) %>% summarise(ref1 = if ('0_525' %in% id_prod) 1 else 0, ref2 = if ('2_159' %in% id_prod) 1 else 0 ) -> cl_ref
sub1 <- cl_ref$ref1 == 1
sub2 <- cl_ref$ref2 ==1
conditional(sub1, sub2)
## [1] 0.8660377
#ou alternativement
getproba(sub1 & sub2) / getproba(sub2)
## [1] 0.8660377
Le 86 % de clients qui ont acheté la référence 2_159 ont aussi acheté la référence 0_525; a noter aussi que tous les clients qui ont acheté la première réferénce ont aussi acheté la seconde.
Pour résumer:
final_stats %>% mutate(agegroup = cut(birth, 5)) %>% ggpairs(columns = c(1:4), aes(fill=agegroup, color=agegroup),progress = FALSE) + theme_light() + labs(title ='Correlations sur les données agrégés')
#donnés non agrégés
cor(stats_clients$birth, stats_clients[c(5:10)])
## tot_achats achats_mois panier_moyen cat_0 cat_1 cat_2
## [1,] 0.1883762 -0.03044712 0.5881656 -0.00264917 -0.2885691 0.5155918
#données agrégés
cor(final_stats$birth, final_stats[-1])
## moy_achats_tot moy_achats_mois panier_moyen
## [1,] 0.7568219 0.2196597 0.7772562
tot_achats : bien que faiblement cette variable est bien corrélée à l’âge du client; cette relation est bien plus évidente quand on traite avec la moyenne. La catégorie qui tend à dépenser le plus en moyenne c’est les 1974-1989, suivi des générations plus jeunes.
panier moyen : cette corrélation est la plus forte entre celles enquêtées avec une tendance non linéaire très marqué. Danse ce cas on remarque bien la relation positive entre catégorie d’âge et panier moyen, ce dérnièr (en moyenne) tend à s’agrandir en baissant l’âge du client.
achats mois : encore une fois nous trouvons une relation significative, cette fois fortement non linéaire entre l’âge et la fréquence d’achats, avec des groupes bien démarqués. Les acheteurs plus fréquents ce sont ceux de la tranche 1974-1989, suivis de générations plus anciennes et en dernière la génération 1989-2004, qui en revanche démontre le panier moyen plus importante (et cela s’explique avec la préférence pour les produits de catégorie 2, bien plus chères des autres).
Nous avons aussi rémarqué une relation, bien que faible, entre sexe et catégorie de produits achetés et entre tranche d’âge et catégorie produit, bien marqué en particulier pour la jeune génération et la catégorie 2.