Sommaire
I.a-Customers
I.b-Products
I.c-Transactions
II.a-Serie temporelle CA
II.b-Les trois flux de vente
II.c-Analyse des produits
III.a-Correlation sex categorie
III.b-Age : CA, Frequence & Panier moyen
III.c-Analyse de la variance ente l'age et la catégorie
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
pd.__version__
'1.4.2'
customers = pd.read_csv('customers.csv')
customers.head(3)
| client_id | sex | birth | |
|---|---|---|---|
| 0 | c_4410 | f | 1967 |
| 1 | c_7839 | f | 1975 |
| 2 | c_1699 | f | 1984 |
products = pd.read_csv('products.csv')
products.head(3)
| id_prod | price | categ | |
|---|---|---|---|
| 0 | 0_1421 | 19.99 | 0 |
| 1 | 0_1368 | 5.13 | 0 |
| 2 | 0_731 | 17.99 | 0 |
transactions = pd.read_csv('transactions.csv')
transactions.head(3)
| id_prod | date | session_id | client_id | |
|---|---|---|---|---|
| 0 | 0_1518 | 2022-05-20 13:21:29.043970 | s_211425 | c_103 |
| 1 | 1_251 | 2022-02-02 07:55:19.149409 | s_158752 | c_8534 |
| 2 | 0_1277 | 2022-06-18 15:44:33.155329 | s_225667 | c_6714 |
customers
| client_id | sex | birth | |
|---|---|---|---|
| 0 | c_4410 | f | 1967 |
| 1 | c_7839 | f | 1975 |
| 2 | c_1699 | f | 1984 |
| 3 | c_5961 | f | 1962 |
| 4 | c_5320 | m | 1943 |
| ... | ... | ... | ... |
| 8618 | c_7920 | m | 1956 |
| 8619 | c_7403 | f | 1970 |
| 8620 | c_5119 | m | 1974 |
| 8621 | c_5643 | f | 1968 |
| 8622 | c_84 | f | 1982 |
8623 rows × 3 columns
customers.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 8623 entries, 0 to 8622 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 client_id 8623 non-null object 1 sex 8623 non-null object 2 birth 8623 non-null int64 dtypes: int64(1), object(2) memory usage: 202.2+ KB
#reconfiguration de 'birth' en variable quantitative continue
customers["birth"] = customers["birth"].astype('float')
customers.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 8623 entries, 0 to 8622 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 client_id 8623 non-null object 1 sex 8623 non-null object 2 birth 8623 non-null float64 dtypes: float64(1), object(2) memory usage: 202.2+ KB
#Statistique descriptives exploratoires
customers.describe()
| birth | |
|---|---|
| count | 8623.000000 |
| mean | 1978.280877 |
| std | 16.919535 |
| min | 1929.000000 |
| 25% | 1966.000000 |
| 50% | 1979.000000 |
| 75% | 1992.000000 |
| max | 2004.000000 |
#detection des doublons
customers.loc[customers.duplicated(keep=False),:]
#aucun doublons detecté
| client_id | sex | birth |
|---|
#confirmation de l'absence de doublons sur la clé primaire
customers.loc[customers["client_id"].duplicated(keep=False),:]
#aucun doublons detecté
| client_id | sex | birth |
|---|
#detection des valeurs manquantes
print(customers.isnull().sum())
#toute les données sont renseignés
client_id 0 sex 0 birth 0 dtype: int64
#regularité des modalités des variables qualitatives
print (f' > birth : {customers["birth"].unique()}.')
print("________________")
print (f' > sex : {customers["sex"].unique()}.')
#RAS
> birth : [1967. 1975. 1984. 1962. 1943. 1993. 1978. 1971. 1982. 1945. 2003. 1959. 1977. 1954. 1987. 2000. 1992. 1963. 1958. 1994. 1936. 1986. 1942. 1970. 1957. 1968. 2002. 2004. 1979. 1974. 1964. 1951. 1937. 1981. 1965. 1960. 1996. 1983. 1990. 1955. 1988. 1991. 1972. 1980. 1989. 1976. 1985. 1953. 1998. 1956. 1948. 1973. 1969. 1938. 1995. 1952. 1949. 1999. 2001. 1939. 1950. 1966. 1935. 1941. 1961. 1997. 1944. 1929. 1947. 1946. 1932. 1931. 1933. 1930. 1940. 1934.]. ________________ > sex : ['f' 'm'].
# Visualisation année de naissance par boxplot
plt.figure(figsize = (12,8))#16 de largeur, 8 de hauteur
plt.boxplot(customers['birth'])
plt.show()
#RAS
#visualisation de la rapartition homme/femme
plt.figure(figsize = (12,8))#16 de largeur, 8 de hauteur
sns.countplot (data = customers, x = 'sex')
<AxesSubplot:xlabel='sex', ylabel='count'>
products
| id_prod | price | categ | |
|---|---|---|---|
| 0 | 0_1421 | 19.99 | 0 |
| 1 | 0_1368 | 5.13 | 0 |
| 2 | 0_731 | 17.99 | 0 |
| 3 | 1_587 | 4.99 | 1 |
| 4 | 0_1507 | 3.99 | 0 |
| ... | ... | ... | ... |
| 3282 | 2_23 | 115.99 | 2 |
| 3283 | 0_146 | 17.14 | 0 |
| 3284 | 0_802 | 11.22 | 0 |
| 3285 | 1_140 | 38.56 | 1 |
| 3286 | 0_1920 | 25.16 | 0 |
3287 rows × 3 columns
products.info()
#categ int?
<class 'pandas.core.frame.DataFrame'> RangeIndex: 3287 entries, 0 to 3286 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 3287 non-null object 1 price 3287 non-null float64 2 categ 3287 non-null int64 dtypes: float64(1), int64(1), object(1) memory usage: 77.2+ KB
products.describe()
#Anomalie. :
#categ est une qualitative, integer est inapproprié
#min -1
| price | categ | |
|---|---|---|
| count | 3287.000000 | 3287.000000 |
| mean | 21.856641 | 0.370246 |
| std | 29.847908 | 0.615387 |
| min | -1.000000 | 0.000000 |
| 25% | 6.990000 | 0.000000 |
| 50% | 13.060000 | 0.000000 |
| 75% | 22.990000 | 1.000000 |
| max | 300.000000 | 2.000000 |
products["categ"].unique()
array([0, 1, 2])
#creation d'une copie de product pour eviter de corrompre les produits
products_dat = products
#reconfiguaration categ en object et copie de products
products_dat['categ']= products_dat['categ'].astype(str)
products_dat.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 3287 entries, 0 to 3286 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 3287 non-null object 1 price 3287 non-null float64 2 categ 3287 non-null object dtypes: float64(1), object(2) memory usage: 77.2+ KB
products_dat.describe()
#une valeur negative détectés
| price | |
|---|---|
| count | 3287.000000 |
| mean | 21.856641 |
| std | 29.847908 |
| min | -1.000000 |
| 25% | 6.990000 |
| 50% | 13.060000 |
| 75% | 22.990000 |
| max | 300.000000 |
#detection du prix negatif
negprice = products_dat.loc[products_dat["price"] <0,:]
negprice
| id_prod | price | categ | |
|---|---|---|---|
| 731 | T_0 | -1.0 | 0 |
#suppresion du prix negatif par generation d'une copie avec condition.
products_dat = products_dat[products_dat["price"]>0]
#detection des doublons
products_dat.loc[products_dat.duplicated(keep=False),:]
#aucun doublons detecté
| id_prod | price | categ |
|---|
#confirmation de l'absence de doublon sur les clés
products_dat.loc[products_dat['id_prod'].duplicated(keep=False),:]
#aucun deoublons detecté sur la clé primaire id_prod
| id_prod | price | categ |
|---|
#Visualisation des valeur null
print(products_dat.isnull().sum())
#aucun NaN
id_prod 0 price 0 categ 0 dtype: int64
#representation du prix.
plt.figure(figsize = (12,8))
products_histogramme = plt.show(products_dat["price"].hist(density=False,bins=200))
plt.title("De nombreux produits jusqu'50€")
plt.savefig('products_histogramme.png')
La majorité des prix sont concentré en 1 et 50€.
#Visualisation des prix pour chaque catégorie
plt.figure(figsize = (6,8))#1st largeur, 2nd hauteur
plt.title("La categorie 2 contient les plus haut prix(Boxplot2)")
Boxplot2 = sns.boxplot (x = products_dat['categ'],y = products_dat['price'])
transactions
| id_prod | date | session_id | client_id | |
|---|---|---|---|---|
| 0 | 0_1518 | 2022-05-20 13:21:29.043970 | s_211425 | c_103 |
| 1 | 1_251 | 2022-02-02 07:55:19.149409 | s_158752 | c_8534 |
| 2 | 0_1277 | 2022-06-18 15:44:33.155329 | s_225667 | c_6714 |
| 3 | 2_209 | 2021-06-24 04:19:29.835891 | s_52962 | c_6941 |
| 4 | 0_1509 | 2023-01-11 08:22:08.194479 | s_325227 | c_4232 |
| ... | ... | ... | ... | ... |
| 679527 | 0_1551 | 2022-01-15 13:05:06.246925 | s_150195 | c_8489 |
| 679528 | 1_639 | 2022-03-19 16:03:23.429229 | s_181434 | c_4370 |
| 679529 | 0_1425 | 2022-12-20 04:33:37.584749 | s_314704 | c_304 |
| 679530 | 0_1994 | 2021-07-16 20:36:35.350579 | s_63204 | c_2227 |
| 679531 | 1_523 | 2022-09-28 01:12:01.973763 | s_274568 | c_3873 |
679532 rows × 4 columns
transactions.info()
#date est inconnue par python en tant que tel
<class 'pandas.core.frame.DataFrame'> RangeIndex: 679532 entries, 0 to 679531 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 679532 non-null object 1 date 679532 non-null object 2 session_id 679532 non-null object 3 client_id 679532 non-null object dtypes: object(4) memory usage: 20.7+ MB
#la date est constitué de 2 éléments 'heure', 'date',
#il faudrait des colonnes distinctes avec ces éléments pour conserver l'info de manière organisé.
# commande str special chaine de caractere. (toutes les methode de chaine de caractère sont rangés dedans.)
transactions['heure'] = transactions['date'].str.slice(11,19)
transactions['dateX'] = transactions['date'].str.slice(0,10)
#en utulisant la methode.slice on se deplace dans la chaîne de caractère pour selectionner.
transactions = transactions.drop(columns = 'date') #suppression de l'ancien date
transactions.head(3)
| id_prod | session_id | client_id | heure | dateX | |
|---|---|---|---|---|---|
| 0 | 0_1518 | s_211425 | c_103 | 13:21:29 | 2022-05-20 |
| 1 | 1_251 | s_158752 | c_8534 | 07:55:19 | 2022-02-02 |
| 2 | 0_1277 | s_225667 | c_6714 | 15:44:33 | 2022-06-18 |
transactions.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 679532 entries, 0 to 679531 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 679532 non-null object 1 session_id 679532 non-null object 2 client_id 679532 non-null object 3 heure 679532 non-null object 4 dateX 679532 non-null object dtypes: object(5) memory usage: 25.9+ MB
#detection de Valeurs nulles
print(transactions.isnull().sum())
#Aucun
id_prod 0 session_id 0 client_id 0 heure 0 dateX 0 dtype: int64
#detection et isolement des doublons
dfdoubl_transac = transactions.loc[transactions.duplicated(keep=False),:]
dfdoubl_transac
#183 doublons détectés
#il semblerait que ces doublons ne soient pas important
#on dirait des doublons sur T_O qui sont des tests.
| id_prod | session_id | client_id | heure | dateX | |
|---|---|---|---|---|---|
| 3019 | T_0 | s_0 | ct_0 | 3-01 02: | test_2021- |
| 5138 | T_0 | s_0 | ct_0 | 3-01 02: | test_2021- |
| 9668 | T_0 | s_0 | ct_1 | 3-01 02: | test_2021- |
| 10728 | T_0 | s_0 | ct_0 | 3-01 02: | test_2021- |
| 15292 | T_0 | s_0 | ct_0 | 3-01 02: | test_2021- |
| ... | ... | ... | ... | ... | ... |
| 657830 | T_0 | s_0 | ct_0 | 3-01 02: | test_2021- |
| 662081 | T_0 | s_0 | ct_1 | 3-01 02: | test_2021- |
| 670680 | T_0 | s_0 | ct_1 | 3-01 02: | test_2021- |
| 671647 | T_0 | s_0 | ct_1 | 3-01 02: | test_2021- |
| 679180 | T_0 | s_0 | ct_1 | 3-01 02: | test_2021- |
200 rows × 5 columns
# recherche d'info sur les doublons : compte des doublons
dfdoubl_transac.value_counts()
#on remarque que les doublons sont localisé sur 2 clients uniquement.
# -> T_0 (id_prod)
# -> test_2021-03-01 (date + 'test'?)
# -> ct_0 & ct_1 (client)
# -> 02:30:02 (heure)
#les transactions semblent différentes mais toujours sur T_0
id_prod session_id client_id heure dateX
T_0 s_0 ct_0 3-01 02: test_2021- 106
ct_1 3-01 02: test_2021- 94
dtype: int64
#suppressions de T_0 par ecrasement avec commande de création de df sous condition
transactions = transactions[transactions["id_prod"] != "T_0"]
#visualisation des doublons
transactions.loc[transactions.duplicated(keep=False),:]
#Tous les doublons ont disparu
| id_prod | session_id | client_id | heure | dateX |
|---|
#confirmation de l'absence de NaN sur session_id
print(transactions["session_id"].isnull().sum())
0
#reconfiguration de date en format 'datetime'
transactions['dateX'] = pd.to_datetime(transactions['dateX']).astype('datetime64[D]')
transactions.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 679332 entries, 0 to 679531 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 679332 non-null object 1 session_id 679332 non-null object 2 client_id 679332 non-null object 3 heure 679332 non-null object 4 dateX 679332 non-null datetime64[ns] dtypes: datetime64[ns](1), object(4) memory usage: 31.1+ MB
Jonction des dataframes
prod_transac = pd.merge(products_dat,transactions,how = "right", on = "id_prod" , indicator = True)
#jonction a droite garde toute les transactions car transaction est a droite
prod_transac
| id_prod | price | categ | session_id | client_id | heure | dateX | _merge | |
|---|---|---|---|---|---|---|---|---|
| 0 | 0_1518 | 4.18 | 0 | s_211425 | c_103 | 13:21:29 | 2022-05-20 | both |
| 1 | 1_251 | 15.99 | 1 | s_158752 | c_8534 | 07:55:19 | 2022-02-02 | both |
| 2 | 0_1277 | 7.99 | 0 | s_225667 | c_6714 | 15:44:33 | 2022-06-18 | both |
| 3 | 2_209 | 69.99 | 2 | s_52962 | c_6941 | 04:19:29 | 2021-06-24 | both |
| 4 | 0_1509 | 4.99 | 0 | s_325227 | c_4232 | 08:22:08 | 2023-01-11 | both |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 679327 | 0_1551 | 12.99 | 0 | s_150195 | c_8489 | 13:05:06 | 2022-01-15 | both |
| 679328 | 1_639 | 10.99 | 1 | s_181434 | c_4370 | 16:03:23 | 2022-03-19 | both |
| 679329 | 0_1425 | 12.99 | 0 | s_314704 | c_304 | 04:33:37 | 2022-12-20 | both |
| 679330 | 0_1994 | 4.98 | 0 | s_63204 | c_2227 | 20:36:35 | 2021-07-16 | both |
| 679331 | 1_523 | 23.99 | 1 | s_274568 | c_3873 | 01:12:01 | 2022-09-28 | both |
679332 rows × 8 columns
#detection des erreurs de jonction
prod_transac['_merge'].value_counts()
#221 lignes n'ont pas de correspondance dans product_dat
both 679111 right_only 221 left_only 0 Name: _merge, dtype: int64
#affichage des jonctions droite seulement
prod_transac[prod_transac['_merge'] == 'right_only']
| id_prod | price | categ | session_id | client_id | heure | dateX | _merge | |
|---|---|---|---|---|---|---|---|---|
| 2633 | 0_2245 | NaN | NaN | s_272266 | c_4746 | 07:22:38 | 2022-09-23 | right_only |
| 10103 | 0_2245 | NaN | NaN | s_242482 | c_6713 | 09:24:14 | 2022-07-23 | right_only |
| 11723 | 0_2245 | NaN | NaN | s_306338 | c_5108 | 03:26:35 | 2022-12-03 | right_only |
| 15670 | 0_2245 | NaN | NaN | s_76493 | c_1391 | 11:33:25 | 2021-08-16 | right_only |
| 16372 | 0_2245 | NaN | NaN | s_239078 | c_7954 | 05:53:01 | 2022-07-16 | right_only |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 669533 | 0_2245 | NaN | NaN | s_80395 | c_131 | 09:06:03 | 2021-08-25 | right_only |
| 670484 | 0_2245 | NaN | NaN | s_175311 | c_4167 | 19:59:19 | 2022-03-06 | right_only |
| 671088 | 0_2245 | NaN | NaN | s_209381 | c_4453 | 11:35:20 | 2022-05-16 | right_only |
| 675480 | 0_2245 | NaN | NaN | s_163405 | c_1098 | 09:05:43 | 2022-02-11 | right_only |
| 677797 | 0_2245 | NaN | NaN | s_134446 | c_4854 | 22:34:54 | 2021-12-14 | right_only |
221 rows × 8 columns
#recherche sur l'anomalie de merge
prod_transac.loc[prod_transac['_merge'] == 'right_only','id_prod'].unique()
#on voit que l'anomalie concerne 1 seul produit :
#ce produit n'a pas de prix. il n'est pas dans le fichier product-> non repertorié.
array(['0_2245'], dtype=object)
Le produit "id_prod 0_2245" n'a pas de prix. il n'est pas dans le fichier product(non repertorié).
A faire remonter au gerant.
# ecrasement de l'objet non repertorié par condition
prod_transac = prod_transac[prod_transac['price'].notnull()]
prod_transac.shape
(679111, 8)
#suppression de colonne -merge pour eviter les conflits
prod_transac = prod_transac.drop(columns = '_merge')
prod_transac.head(1)
| id_prod | price | categ | session_id | client_id | heure | dateX | |
|---|---|---|---|---|---|---|---|
| 0 | 0_1518 | 4.18 | 0 | s_211425 | c_103 | 13:21:29 | 2022-05-20 |
#jonctions avec clients
lapagefull = pd.merge(prod_transac, customers , how = 'right', on = 'client_id' , indicator = True)
lapagefull
| id_prod | price | categ | session_id | client_id | heure | dateX | sex | birth | _merge | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0_1316 | 7.20 | 0 | s_141762 | c_4410 | 09:11:18 | 2021-12-29 | f | 1967.0 | both |
| 1 | 1_385 | 25.99 | 1 | s_9707 | c_4410 | 01:40:22 | 2021-03-22 | f | 1967.0 | both |
| 2 | 1_190 | 14.53 | 1 | s_118628 | c_4410 | 18:11:43 | 2021-11-12 | f | 1967.0 | both |
| 3 | 0_1455 | 8.99 | 0 | s_9942 | c_4410 | 14:29:25 | 2021-03-22 | f | 1967.0 | both |
| 4 | 1_483 | 15.99 | 1 | s_178686 | c_4410 | 21:35:55 | 2022-03-13 | f | 1967.0 | both |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 679129 | 0_1438 | 9.31 | 0 | s_215697 | c_84 | 06:11:50 | 2022-05-29 | f | 1982.0 | both |
| 679130 | 0_1020 | 9.71 | 0 | s_107849 | c_84 | 03:52:13 | 2021-10-21 | f | 1982.0 | both |
| 679131 | 0_1399 | 17.99 | 0 | s_98493 | c_84 | 16:13:18 | 2021-10-01 | f | 1982.0 | both |
| 679132 | 0_1417 | 17.99 | 0 | s_5960 | c_84 | 23:55:06 | 2021-03-13 | f | 1982.0 | both |
| 679133 | 0_1438 | 9.31 | 0 | s_186172 | c_84 | 06:11:50 | 2022-03-29 | f | 1982.0 | both |
679134 rows × 10 columns
#detection des erreurs de merge
lapagefull['_merge'].value_counts()
# 23 echec merge
both 679111 right_only 23 left_only 0 Name: _merge, dtype: int64
#zoom sur les 23 echec de merge right_only
lapagefull[lapagefull['_merge'] == 'right_only']
| id_prod | price | categ | session_id | client_id | heure | dateX | sex | birth | _merge | |
|---|---|---|---|---|---|---|---|---|---|---|
| 59474 | NaN | NaN | NaN | NaN | c_8253 | NaN | NaT | f | 2001.0 | right_only |
| 200224 | NaN | NaN | NaN | NaN | c_3789 | NaN | NaT | f | 1997.0 | right_only |
| 218102 | NaN | NaN | NaN | NaN | c_4406 | NaN | NaT | f | 1998.0 | right_only |
| 218103 | NaN | NaN | NaN | NaN | ct_0 | NaN | NaT | f | 2001.0 | right_only |
| 220316 | NaN | NaN | NaN | NaN | c_2706 | NaN | NaT | f | 1967.0 | right_only |
| 225464 | NaN | NaN | NaN | NaN | c_3443 | NaN | NaT | m | 1959.0 | right_only |
| 248325 | NaN | NaN | NaN | NaN | c_4447 | NaN | NaT | m | 1956.0 | right_only |
| 248881 | NaN | NaN | NaN | NaN | c_3017 | NaN | NaT | f | 1992.0 | right_only |
| 260937 | NaN | NaN | NaN | NaN | c_4086 | NaN | NaT | f | 1992.0 | right_only |
| 313042 | NaN | NaN | NaN | NaN | c_6930 | NaN | NaT | m | 2004.0 | right_only |
| 319633 | NaN | NaN | NaN | NaN | c_4358 | NaN | NaT | m | 1999.0 | right_only |
| 387270 | NaN | NaN | NaN | NaN | c_8381 | NaN | NaT | f | 1965.0 | right_only |
| 392554 | NaN | NaN | NaN | NaN | c_1223 | NaN | NaT | m | 1963.0 | right_only |
| 488877 | NaN | NaN | NaN | NaN | c_6862 | NaN | NaT | f | 2002.0 | right_only |
| 495608 | NaN | NaN | NaN | NaN | c_5245 | NaN | NaT | f | 2004.0 | right_only |
| 505343 | NaN | NaN | NaN | NaN | c_5223 | NaN | NaT | m | 2003.0 | right_only |
| 514282 | NaN | NaN | NaN | NaN | c_6735 | NaN | NaT | m | 2004.0 | right_only |
| 523031 | NaN | NaN | NaN | NaN | c_862 | NaN | NaT | f | 1956.0 | right_only |
| 542075 | NaN | NaN | NaN | NaN | c_7584 | NaN | NaT | f | 1960.0 | right_only |
| 610964 | NaN | NaN | NaN | NaN | c_90 | NaN | NaT | m | 2001.0 | right_only |
| 612433 | NaN | NaN | NaN | NaN | c_587 | NaN | NaT | m | 1993.0 | right_only |
| 670499 | NaN | NaN | NaN | NaN | ct_1 | NaN | NaT | m | 2001.0 | right_only |
| 672975 | NaN | NaN | NaN | NaN | c_3526 | NaN | NaT | m | 1956.0 | right_only |
On remarque 23 clients qui n'ont aucune transactions
On va remonter l'information au gerant
#suppression de colonne -merge pour alléger le df
lapagefull = lapagefull.drop(columns = '_merge')
lapagefull.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 679134 entries, 0 to 679133 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 679111 non-null object 1 price 679111 non-null float64 2 categ 679111 non-null object 3 session_id 679111 non-null object 4 client_id 679134 non-null object 5 heure 679111 non-null object 6 dateX 679111 non-null datetime64[ns] 7 sex 679134 non-null object 8 birth 679134 non-null int64 dtypes: datetime64[ns](1), float64(1), int64(1), object(6) memory usage: 51.8+ MB
#compte le nombre de fois qu'une reference sort
ref_compte=lapagefull['id_prod'].value_counts()
ref_compte
1_369 2252
1_417 2189
1_414 2180
1_498 2128
1_425 2096
...
0_1601 1
0_1539 1
0_1498 1
0_1633 1
0_1728 1
Name: id_prod, Length: 3265, dtype: int64
lpcat0 = lapagefull.loc[lapagefull['categ'] == '0',:]
lpcat1 = lapagefull.loc[lapagefull['categ'] == '1',:]
lpcat2 = lapagefull.loc[lapagefull['categ'] == '2',:]
lpcat2.head(2)
| id_prod | price | categ | session_id | client_id | heure | dateX | sex | birth | |
|---|---|---|---|---|---|---|---|---|---|
| 72 | 2_159 | 145.99 | 2 | s_127714 | c_4410 | 07:36:25 | 2021-12-01 | f | 1967.0 |
| 231 | 2_207 | 50.99 | 2 | s_151417 | c_415 | 01:31:06 | 2022-01-18 | m | 1993.0 |
CAcat0 = round(lpcat0['price'].sum(),2)
CAcat1 = round(lpcat1['price'].sum(),2)
CAcat2 = round(lpcat2['price'].sum(),2)
CAtotal = round(CAcat2 + CAcat1+CAcat0,2)
print(f" CA categorie 0 : {CAcat0} € .")
print(f" CA categorie 1 : {CAcat1} €.")
print(f" CA categorie 2: {CAcat2} €.")
print(f" CA total: {CAtotal} €.")
print(f" CA total en Millions: {round(CAtotal/1000000,1)} M €.")
CA categorie 0 : 4419730.97 € . CA categorie 1 : 4653722.69 €. CA categorie 2: 2780275.02 €. CA total: 11853728.68 €. CA total en Millions: 11.9 M €.
# CA globale par catégorie
plt.figure(figsize = (16,8))#16 de largeur, 8 de hauteur
#definir data
Cat_pie = [CAcat0, CAcat1, CAcat2]
labels = ['cat0', 'cat1', 'cat2']
#definiri Seaborn palette couleur seaborn
colors = sns.color_palette('pastel')[0:5]
title = 'Représenation du CA par Categorie'
#generer pie chart
plt.pie(Cat_pie, labels = labels, colors = colors, autopct='%.0f%%')
#Titre
plt.title("CA Total = 11.9 M € ", bbox={'facecolor':'1', 'pad':5})
#ax.set(xlabel='common xlabel', ylabel='common ylabel')
plt.show()
On remarque un CA davantage représenté par la catégorie 1 (39%) comparé a la distribution des prix en faveur de la catégorie2 (voir boxplot2)
lapagefull.head(3)
| id_prod | price | categ | session_id | client_id | heure | dateX | sex | birth | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 0_1316 | 7.20 | 0 | s_141762 | c_4410 | 09:11:18 | 2021-12-29 | f | 1967 |
| 1 | 1_385 | 25.99 | 1 | s_9707 | c_4410 | 01:40:22 | 2021-03-22 | f | 1967 |
| 2 | 1_190 | 14.53 | 1 | s_118628 | c_4410 | 18:11:43 | 2021-11-12 | f | 1967 |
# Courbe : aggregat du CA en serie temporelle
#groupeby + sum par price.
CAjour = lapagefull[["dateX","price"]].groupby("dateX").sum().reset_index()
#Rename de price qui est maintenant une vente journalière
CAjour = CAjour.rename(columns={"price":"CAday"})
CAjour
| dateX | CAday | |
|---|---|---|
| 0 | 2021-03-01 | 16565.22 |
| 1 | 2021-03-02 | 15486.45 |
| 2 | 2021-03-03 | 15198.69 |
| 3 | 2021-03-04 | 15196.07 |
| 4 | 2021-03-05 | 17471.37 |
| ... | ... | ... |
| 725 | 2023-02-24 | 15207.89 |
| 726 | 2023-02-25 | 15761.25 |
| 727 | 2023-02-26 | 16304.72 |
| 728 | 2023-02-27 | 19170.81 |
| 729 | 2023-02-28 | 18105.15 |
730 rows × 2 columns
#construction de la courbe du CA general en fonction du temps.
plt.figure(figsize = (12,10))
sns.lineplot(data = CAjour, x = 'dateX' , y = 'CAday')
<AxesSubplot:xlabel='dateX', ylabel='CAday'>
On remarque que la courbe contient trop d'informations ce qui engendre du brouillard
#Lissage de la courbe du CA sur 7jours
CAjour["CAday7"] = CAjour["CAday"].rolling(7).mean() #permet de generer une moyenne mobile/glissante sur 7 jour
plt.figure(figsize = (12,10))
plt.title('CA 2ans lissé sur 7jours' )
CA_2ans_lissé_sur_7jours = sns.lineplot(data = CAjour, x = 'dateX' , y = 'CAday7')
-Courbe ajustée sur 7 jours du Chiffre d'afffaire total de 2021 à 2023.
CAjour.head(10)
| dateX | CAday | CAday7 | |
|---|---|---|---|
| 0 | 2021-03-01 | 16565.22 | NaN |
| 1 | 2021-03-02 | 15486.45 | NaN |
| 2 | 2021-03-03 | 15198.69 | NaN |
| 3 | 2021-03-04 | 15196.07 | NaN |
| 4 | 2021-03-05 | 17471.37 | NaN |
| 5 | 2021-03-06 | 15785.28 | NaN |
| 6 | 2021-03-07 | 14760.20 | 15780.468571 |
| 7 | 2021-03-08 | 15679.53 | 15653.941429 |
| 8 | 2021-03-09 | 15710.51 | 15685.950000 |
| 9 | 2021-03-10 | 15496.87 | 15728.547143 |
#distinction du CA par catégorie
CAjourcat = lapagefull[["dateX","price","categ"]].groupby(["dateX","categ"]).sum().reset_index()
#Rename de price qui est maintenant une vente
CAjourcat = CAjourcat.rename(columns={"price":"CAj"})
CAjourcat
| dateX | categ | CAj | |
|---|---|---|---|
| 0 | 2021-03-01 | 0 | 6262.65 |
| 1 | 2021-03-01 | 1 | 6811.53 |
| 2 | 2021-03-01 | 2 | 3491.04 |
| 3 | 2021-03-02 | 0 | 6718.27 |
| 4 | 2021-03-02 | 1 | 5627.15 |
| ... | ... | ... | ... |
| 2159 | 2023-02-27 | 1 | 6461.76 |
| 2160 | 2023-02-27 | 2 | 5271.62 |
| 2161 | 2023-02-28 | 0 | 4901.86 |
| 2162 | 2023-02-28 | 1 | 9226.25 |
| 2163 | 2023-02-28 | 2 | 3977.04 |
2164 rows × 3 columns
#1er courbe CA par categorie
plt.figure(figsize = (12,10))
sns.lineplot(data = CAjourcat, x = 'dateX' , y = 'CAj',hue = 'categ')
#La courbe contient trop d'info, elle va etre affinée
<AxesSubplot:xlabel='dateX', ylabel='CAj'>
#deplacement des categorie sur 3 colonnes
CAjourcat = pd.pivot_table(data = CAjourcat , index = "dateX" , columns = "categ", values = "CAj").reset_index()
CAjourcat
| categ | dateX | 0 | 1 | 2 |
|---|---|---|---|---|
| 0 | 2021-03-01 | 6262.65 | 6811.53 | 3491.04 |
| 1 | 2021-03-02 | 6718.27 | 5627.15 | 3141.03 |
| 2 | 2021-03-03 | 6121.03 | 5691.41 | 3386.25 |
| 3 | 2021-03-04 | 5891.48 | 6098.98 | 3205.61 |
| 4 | 2021-03-05 | 5975.97 | 7071.21 | 4424.19 |
| ... | ... | ... | ... | ... |
| 725 | 2023-02-24 | 6197.92 | 5471.07 | 3538.90 |
| 726 | 2023-02-25 | 4942.69 | 8050.61 | 2767.95 |
| 727 | 2023-02-26 | 5522.90 | 7009.09 | 3772.73 |
| 728 | 2023-02-27 | 7437.43 | 6461.76 | 5271.62 |
| 729 | 2023-02-28 | 4901.86 | 9226.25 | 3977.04 |
730 rows × 4 columns
#lissage des categories sur 7 jours
CAjourcat["0"] = CAjourcat["0"].rolling(7).mean()
CAjourcat["1"] = CAjourcat["1"].rolling(7).mean()
CAjourcat["2"] = CAjourcat["2"].rolling(7).mean()
# rebascule les colonnes avec Melt pour tracer (inverse de pivot.table)
#permet de remettre categ dans 1 colonne.
CAjourcat = pd.melt(frame = CAjourcat , id_vars = "dateX" , value_vars = ["0","1","2"], var_name = "categ").reset_index()
CAjourcat
| index | dateX | categ | value | |
|---|---|---|---|---|
| 0 | 0 | 2021-03-01 | 0 | NaN |
| 1 | 1 | 2021-03-02 | 0 | NaN |
| 2 | 2 | 2021-03-03 | 0 | NaN |
| 3 | 3 | 2021-03-04 | 0 | NaN |
| 4 | 4 | 2021-03-05 | 0 | NaN |
| ... | ... | ... | ... | ... |
| 2185 | 2185 | 2023-02-24 | 2 | 3974.147143 |
| 2186 | 2186 | 2023-02-25 | 2 | 3607.191429 |
| 2187 | 2187 | 2023-02-26 | 2 | 3528.142857 |
| 2188 | 2188 | 2023-02-27 | 2 | 3825.195714 |
| 2189 | 2189 | 2023-02-28 | 2 | 3849.872857 |
2190 rows × 4 columns
plt.figure(figsize = (12,10))
sns.lineplot(data = CAjourcat, x = 'dateX' , y = 'value',hue = 'categ')
<AxesSubplot:xlabel='dateX', ylabel='value'>
- Courbe du CA par catégorie lissée sur 7 jours -
#Observation du nombre de vente
Dfnb_vente = lapagefull[["dateX","price","categ"]].groupby(["dateX","categ"]).count().reset_index()
Dfnb_vente.rename(columns = {"price" : "nb_vente"}, inplace = True)
Dfnb_vente
| dateX | categ | nb_vente | |
|---|---|---|---|
| 0 | 2021-03-01 | 0 | 581 |
| 1 | 2021-03-01 | 1 | 335 |
| 2 | 2021-03-01 | 2 | 46 |
| 3 | 2021-03-02 | 0 | 620 |
| 4 | 2021-03-02 | 1 | 276 |
| ... | ... | ... | ... |
| 2159 | 2023-02-27 | 1 | 332 |
| 2160 | 2023-02-27 | 2 | 75 |
| 2161 | 2023-02-28 | 0 | 472 |
| 2162 | 2023-02-28 | 1 | 439 |
| 2163 | 2023-02-28 | 2 | 50 |
2164 rows × 3 columns
Dfnb_vente
| dateX | categ | nb_vente | |
|---|---|---|---|
| 0 | 2021-03-01 | 0 | 581 |
| 1 | 2021-03-01 | 1 | 335 |
| 2 | 2021-03-01 | 2 | 46 |
| 3 | 2021-03-02 | 0 | 620 |
| 4 | 2021-03-02 | 1 | 276 |
| ... | ... | ... | ... |
| 2159 | 2023-02-27 | 1 | 332 |
| 2160 | 2023-02-27 | 2 | 75 |
| 2161 | 2023-02-28 | 0 | 472 |
| 2162 | 2023-02-28 | 1 | 439 |
| 2163 | 2023-02-28 | 2 | 50 |
2164 rows × 3 columns
plt.figure(figsize = (12,10))
plt.title('Categorie 0 : 37% du CA, des ventes toujours plus nombreuse pour des produits a petit prix')
sns.lineplot(data = Dfnb_vente, x = 'dateX' , y = 'nb_vente',hue = 'categ')
<AxesSubplot:title={'center':'Categorie 0 : 37% du CA, des ventes toujours plus nombreuse pour des produits a petit prix'}, xlabel='dateX', ylabel='nb_vente'>
Courbe du nombre de vente dans le temps.
On remarque une irrégularité qui semble être lissée sur l'axe categ 1.
#mise en relief de l'irrégularité d'octobre : technique du double pivot.
#phase 1 pivot-table sur categ
Dfnb_vente01 = pd.pivot_table ( data = Dfnb_vente , index = "dateX",columns = "categ", values = "nb_vente",fill_value = 0 ).reset_index()
Dfnb_vente01.head(5)
| categ | dateX | 0 | 1 | 2 |
|---|---|---|---|---|
| 0 | 2021-03-01 | 581 | 335 | 46 |
| 1 | 2021-03-02 | 620 | 276 | 43 |
| 2 | 2021-03-03 | 591 | 280 | 40 |
| 3 | 2021-03-04 | 563 | 297 | 43 |
| 4 | 2021-03-05 | 561 | 331 | 51 |
# phase 2 : remise des categ en 1 colonne par melt
Dfnb_vente01 = pd.melt(frame = Dfnb_vente01 , id_vars = "dateX" , value_vars = ["0","1","2"], var_name = "categ").reset_index()
Dfnb_vente01
| index | dateX | categ | value | |
|---|---|---|---|---|
| 0 | 0 | 2021-03-01 | 0 | 581 |
| 1 | 1 | 2021-03-02 | 0 | 620 |
| 2 | 2 | 2021-03-03 | 0 | 591 |
| 3 | 3 | 2021-03-04 | 0 | 563 |
| 4 | 4 | 2021-03-05 | 0 | 561 |
| ... | ... | ... | ... | ... |
| 2185 | 2185 | 2023-02-24 | 2 | 47 |
| 2186 | 2186 | 2023-02-25 | 2 | 46 |
| 2187 | 2187 | 2023-02-26 | 2 | 53 |
| 2188 | 2188 | 2023-02-27 | 2 | 75 |
| 2189 | 2189 | 2023-02-28 | 2 | 50 |
2190 rows × 4 columns
plt.figure(figsize = (12,10))
plt.title('Representation du CA par categorie')
sns.lineplot(data = Dfnb_vente01, x = 'dateX' , y = 'value',hue = 'categ')
<AxesSubplot:title={'center':'Representation du CA par categorie'}, xlabel='dateX', ylabel='value'>
les axes nous montrent une relative regularité dans le nombre de ventes. Les categories les moins chere concentrent plus de vente.
On remarque que l'irrégularité sur l'axe 1 est davantage mise en relief grâce à la technique du double pivot qui permet de re-encoder les valeurs à zero au lieu de les lisser car elle n'ont pas de ligne dès le depart. Par deduction python se sert des autres date et met 0 sur categ1 dans cet espace temporel.
# isolement de l'anomalie pour categorie 1
zonevide = Dfnb_vente01[ Dfnb_vente01 ['value']== 0]
zonevide
#on remarque que tout le mois d'octobre est vide
| index | dateX | categ | value | |
|---|---|---|---|---|
| 945 | 945 | 2021-10-02 | 1 | 0 |
| 946 | 946 | 2021-10-03 | 1 | 0 |
| 947 | 947 | 2021-10-04 | 1 | 0 |
| 948 | 948 | 2021-10-05 | 1 | 0 |
| 949 | 949 | 2021-10-06 | 1 | 0 |
| 950 | 950 | 2021-10-07 | 1 | 0 |
| 951 | 951 | 2021-10-08 | 1 | 0 |
| 952 | 952 | 2021-10-09 | 1 | 0 |
| 953 | 953 | 2021-10-10 | 1 | 0 |
| 954 | 954 | 2021-10-11 | 1 | 0 |
| 955 | 955 | 2021-10-12 | 1 | 0 |
| 956 | 956 | 2021-10-13 | 1 | 0 |
| 957 | 957 | 2021-10-14 | 1 | 0 |
| 958 | 958 | 2021-10-15 | 1 | 0 |
| 959 | 959 | 2021-10-16 | 1 | 0 |
| 960 | 960 | 2021-10-17 | 1 | 0 |
| 961 | 961 | 2021-10-18 | 1 | 0 |
| 962 | 962 | 2021-10-19 | 1 | 0 |
| 963 | 963 | 2021-10-20 | 1 | 0 |
| 964 | 964 | 2021-10-21 | 1 | 0 |
| 965 | 965 | 2021-10-22 | 1 | 0 |
| 966 | 966 | 2021-10-23 | 1 | 0 |
| 967 | 967 | 2021-10-24 | 1 | 0 |
| 968 | 968 | 2021-10-25 | 1 | 0 |
| 969 | 969 | 2021-10-26 | 1 | 0 |
| 970 | 970 | 2021-10-27 | 1 | 0 |
lapagefull.head(2)
| id_prod | price | categ | session_id | client_id | heure | dateX | sex | birth | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 0_1316 | 7.20 | 0 | s_141762 | c_4410 | 09:11:18 | 2021-12-29 | f | 1967.0 |
| 1 | 1_385 | 25.99 | 1 | s_9707 | c_4410 | 01:40:22 | 2021-03-22 | f | 1967.0 |
#aggregation des prix par client
CAclient00 = lapagefull.groupby("client_id" ).agg({"price" : sum }).reset_index()
CAclient00.rename(columns = {"price" : "CAperso"}, inplace = True)
CAclient00
| client_id | CAperso | |
|---|---|---|
| 0 | c_1 | 558.18 |
| 1 | c_10 | 1353.60 |
| 2 | c_100 | 254.85 |
| 3 | c_1000 | 2261.89 |
| 4 | c_1001 | 1812.86 |
| ... | ... | ... |
| 8618 | c_997 | 1490.01 |
| 8619 | c_998 | 2779.88 |
| 8620 | c_999 | 701.40 |
| 8621 | ct_0 | 0.00 |
| 8622 | ct_1 | 0.00 |
8623 rows × 2 columns
Calcul de l'egalité de la repartition du CA : Courbe de Lorenz et Indice de Gini
#courbe de Lorenz
dep = CAclient00['CAperso']
n = len(dep)
lorenz = np.cumsum(np.sort(dep)) / dep.sum()
lorenz = np.append([0],lorenz) # La courbe de Lorenz commence à 0
xaxis = np.linspace(0-1/n,1+1/n,n+1) #Il y a un segment de taille n pour chaque individu, plus 1 segment supplémentaire d'ordonnée 0. Le premier segment commence à 0-1/n, et le dernier termine à 1+1/n.
plt.figure(figsize = (12,9))
plt.title('Courbe de Lorenz CA client')
plt.plot(xaxis,lorenz,drawstyle='steps-post')
plt.plot((0,0),(1,1), c='r')
plt.show()
#calculer indice de GINI
Courbe de Lorenz
On remarque une repartition relativement inegales cependant une flexion très forte représentant un peu moins de 5% de client cumule un CA particulièrement elevé.
#Indice de Gini
AUC = (lorenz.sum() -lorenz[-1]/2 -lorenz[0]/2)/n # Surface sous la courbe de Lorenz. Le premier segment (lorenz[0]) est à moitié en dessous de 0, on le coupe
S = 0.5 - AUC # surface entre la première bissectrice et le courbe de Lorenz
gini = 2*S
print (f"indice de gini : {gini}")
print("l'indice varie entre 0 et 1, plus il est proche de 0 plus la repartition est EGALITAIRE")
indice de gini : 0.4478631863407775 l'indice varie entre 0 et 1, plus il est proche de 0 plus la repartition est EGALITAIRE
#trie CA client ordre decroissant
CAclient_tri = CAclient00.sort_values (by = ["CAperso"], ascending = False ).reset_index()
CAclient_tri
| index | client_id | CAperso | |
|---|---|---|---|
| 0 | 678 | c_1609 | 324033.35 |
| 1 | 4398 | c_4958 | 289760.34 |
| 2 | 6350 | c_6714 | 153598.92 |
| 3 | 2728 | c_3454 | 113637.93 |
| 4 | 2516 | c_3263 | 5276.87 |
| ... | ... | ... | ... |
| 8618 | 3786 | c_4406 | 0.00 |
| 8619 | 3732 | c_4358 | 0.00 |
| 8620 | 8511 | c_90 | 0.00 |
| 8621 | 250 | c_1223 | 0.00 |
| 8622 | 8622 | ct_1 | 0.00 |
8623 rows × 3 columns
print(f"Le CA du 4em client est X{round (CAclient_tri.iloc[3, 2] / CAclient_tri.iloc[4, 2] )} plus élévé que le 5em, on peut supposer que les 4 premiers sont des clients partenaires-professionels.")
Le CA du 4em client est X22 plus élévé que le 5em, on peut supposer que les 4 premiers sont des clients partenaires-professionels.
listpro = ['c_3454','c_6714','c_4958','c_1609' ]
# visualisation par boxplot
# Visualisation client
plt.figure(figsize = (8,8))#16 de largeur, 8 de hauteur
plt.boxplot(CAclient_tri['CAperso'])
plt.title("CAperso Boxplot")
plt.show()
On remarque les 4 clients pro qui sont effectivement des outliers. Ils seront exclus des analyses.
# visualition de la distribution en excluant les 4 outliers
plt.figure(figsize = (8,8))#16 de largeur, 8 de hauteur
plt.boxplot(CAclient_tri.loc[CAclient_tri['CAperso']< 100000,'CAperso'])
plt.title("Boxplot CAperso des particuliers")
plt.show()
On remarque une distribution majoritaire entre 600 et 2000€ de CA avec une mediane a peu près a 1000€
#jonctions avec les données clients (customers)
CAclient_tri = pd.merge(CAclient_tri, customers, how = 'right', on = 'client_id', indicator = True)
CAclient_tri
| index | client_id | CAperso | sex | birth | _merge | |
|---|---|---|---|---|---|---|
| 0 | 3791 | c_4410 | 1376.82 | f | 1967.0 | both |
| 1 | 7599 | c_7839 | 554.93 | f | 1975.0 | both |
| 2 | 777 | c_1699 | 190.60 | f | 1984.0 | both |
| 3 | 5513 | c_5961 | 1150.42 | f | 1962.0 | both |
| 4 | 4802 | c_5320 | 396.53 | m | 1943.0 | both |
| ... | ... | ... | ... | ... | ... | ... |
| 8618 | 7690 | c_7920 | 1897.16 | m | 1956.0 | both |
| 8619 | 7116 | c_7403 | 2592.73 | f | 1970.0 | both |
| 8620 | 4578 | c_5119 | 729.69 | m | 1974.0 | both |
| 8621 | 5160 | c_5643 | 1995.95 | f | 1968.0 | both |
| 8622 | 8222 | c_84 | 427.51 | f | 1982.0 | both |
8623 rows × 6 columns
#detection des erreurs de merge
CAclient_tri['_merge'].value_counts()
# merge succes
both 8623 left_only 0 right_only 0 Name: _merge, dtype: int64
#suppression de colonne -merge pour alléger le df
CAclient_tri = CAclient_tri.drop(columns = '_merge')
CAclient_tri.head(1)
| index | client_id | CAperso | sex | birth | |
|---|---|---|---|---|---|
| 0 | 3791 | c_4410 | 1376.82 | f | 1967.0 |
CAclient_tri.head(3)
| index | client_id | CAperso | sex | birth | |
|---|---|---|---|---|---|
| 0 | 3791 | c_4410 | 1376.82 | f | 1967.0 |
| 1 | 7599 | c_7839 | 554.93 | f | 1975.0 |
| 2 | 777 | c_1699 | 190.60 | f | 1984.0 |
# Visualisation sex en excluant les 4 outliers
plt.figure(figsize = (8,10))#16 de largeur, 8 de hauteur
plt.title('distributin du CA entre les H et F')
sns.boxplot(data = CAclient_tri[CAclient_tri['CAperso'] < 100000], y = 'CAperso', x = 'sex')
<AxesSubplot:title={'center':'distributin du CA entre les H et F'}, xlabel='sex', ylabel='CAperso'>
Le CA semble distribué de manière equitable entre les hommes et les femmes.
#Creation DF en excluant les client Pro
CAclient_tri_particulier = CAclient_tri.loc[CAclient_tri["CAperso"] < 100000,["client_id","CAperso"]]
CAclient_tri_particulier
| client_id | CAperso | |
|---|---|---|
| 0 | c_4410 | 1376.82 |
| 1 | c_7839 | 554.93 |
| 2 | c_1699 | 190.60 |
| 3 | c_5961 | 1150.42 |
| 4 | c_5320 | 396.53 |
| ... | ... | ... |
| 8618 | c_7920 | 1897.16 |
| 8619 | c_7403 | 2592.73 |
| 8620 | c_5119 | 729.69 |
| 8621 | c_5643 | 1995.95 |
| 8622 | c_84 | 427.51 |
8619 rows × 2 columns
#courbe de Lorenz Ajusté (en exclusion les clients Pro)
dep = CAclient_tri_particulier['CAperso']
n = len(dep)
lorenz = np.cumsum(np.sort(dep)) / dep.sum()
lorenz = np.append([0],lorenz) # La courbe de Lorenz commence à 0
xaxis = np.linspace(0-1/n,1+1/n,n+1) #Il y a un segment de taille n pour chaque individu, plus 1 segment supplémentaire d'ordonnée 0. Le premier segment commence à 0-1/n, et le dernier termine à 1+1/n.
plt.figure(figsize = (12,9))
plt.title('Courbe de Lorenz CA client')
plt.plot(xaxis,lorenz,drawstyle='steps-post')
plt.plot((0,0),(1,1), c='r')
plt.show()
#Indice de Gini Ajusté
AUC = (lorenz.sum() -lorenz[-1]/2 -lorenz[0]/2)/n # Surface sous la courbe de Lorenz. Le premier segment (lorenz[0]) est à moitié en dessous de 0, on le coupe
S = 0.5 - AUC # surface entre la première bissectrice et le courbe de Lorenz
gini_ajuste= 2*S
print (f"indice de gini ajusté: {gini_ajuste}")
print("En excluant les client pro, l'indice de GINI ajusté se rapproche légerement plus de 0")
indice de gini ajusté: 0.40421099614744493 En excluant les client pro, l'indice de GINI ajusté se rapproche légerement plus de 0
dfProduit = lapagefull.groupby("id_prod" ).agg({"price" : sum }).reset_index()
dfProduit.rename(columns = {"price" : "CAproduit"}, inplace = True)
dfProduit
| id_prod | CAproduit | |
|---|---|---|
| 0 | 0_0 | 4657.50 |
| 1 | 0_1 | 5352.13 |
| 2 | 0_10 | 394.90 |
| 3 | 0_100 | 61.80 |
| 4 | 0_1000 | 2954.88 |
| ... | ... | ... |
| 3260 | 2_95 | 395.96 |
| 3261 | 2_96 | 28650.18 |
| 3262 | 2_97 | 2092.87 |
| 3263 | 2_98 | 149.74 |
| 3264 | 2_99 | 594.93 |
3265 rows × 2 columns
dfProduit.describe()
| CAproduit | |
|---|---|
| count | 3265.000000 |
| mean | 3630.544772 |
| std | 7371.925385 |
| min | 0.990000 |
| 25% | 233.820000 |
| 50% | 796.860000 |
| 75% | 3408.600000 |
| max | 94893.500000 |
# Boxplot du CA_produits
plt.figure(figsize = (8,8))#16 de largeur, 8 de hauteur
plt.boxplot(dfProduit['CAproduit'])
plt.title("dfProduit Boxplot")
plt.show()
**On remarque une distribution très etendue nous allons coupé en 2 pour la visualiser différemment
# Zoom sur le box plot : visualisation des produits en excluant les + de 5 000 €
# Visualisation client
plt.figure(figsize = (5,8))#- largeur, - de hauteur
sns.boxplot(data = dfProduit[dfProduit['CAproduit'] < 5000], y = 'CAproduit')
plt.title("dfProduit Boxplot prix - 5 000 €")
plt.show()
#Recherche des Top et Flop.
dfProduit_tri = dfProduit.sort_values(by = ['CAproduit'], ascending = False).reset_index()
dfProduit_tri
| index | id_prod | CAproduit | |
|---|---|---|---|
| 0 | 3096 | 2_159 | 94893.50 |
| 1 | 3070 | 2_135 | 69334.95 |
| 2 | 3045 | 2_112 | 65407.76 |
| 3 | 3034 | 2_102 | 60736.78 |
| 4 | 3152 | 2_209 | 56971.86 |
| ... | ... | ... | ... |
| 3260 | 665 | 0_1601 | 1.99 |
| 3261 | 2079 | 0_807 | 1.99 |
| 3262 | 719 | 0_1653 | 1.98 |
| 3263 | 313 | 0_1284 | 1.38 |
| 3264 | 595 | 0_1539 | 0.99 |
3265 rows × 3 columns
#configurer le chiffre entre parenthèse pour la longueur des listes de produits TOP/FLOP
print (f"Les (n) premiers => Top : {dfProduit_tri.head(9)}")
print("_________________________________________")
print(" _______________________________________")
print (f"Les (n) derniers => Flop : {dfProduit_tri.tail(9)}")
Les (n) premiers => Top : index id_prod CAproduit 0 3096 2_159 94893.50 1 3070 2_135 69334.95 2 3045 2_112 65407.76 3 3034 2_102 60736.78 4 3152 2_209 56971.86 5 2619 1_395 54356.25 6 2591 1_369 54025.48 7 3043 2_110 53846.25 8 3201 2_39 53060.85 _________________________________________ _______________________________________ Les (n) derniers => Flop : index id_prod CAproduit 3256 2179 0_898 2.54 3257 549 0_1498 2.48 3258 802 0_1728 2.27 3259 1784 0_541 1.99 3260 665 0_1601 1.99 3261 2079 0_807 1.99 3262 719 0_1653 1.98 3263 313 0_1284 1.38 3264 595 0_1539 0.99
#on exclut les 4 clients pro (outliers) pour les tests
lapage_particulier = lapagefull.loc[~lapagefull["client_id"].isin(listpro), :]
lapage_particulier
| id_prod | price | categ | session_id | client_id | heure | dateX | sex | birth | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 0_1316 | 7.20 | 0 | s_141762 | c_4410 | 09:11:18 | 2021-12-29 | f | 1967.0 |
| 1 | 1_385 | 25.99 | 1 | s_9707 | c_4410 | 01:40:22 | 2021-03-22 | f | 1967.0 |
| 2 | 1_190 | 14.53 | 1 | s_118628 | c_4410 | 18:11:43 | 2021-11-12 | f | 1967.0 |
| 3 | 0_1455 | 8.99 | 0 | s_9942 | c_4410 | 14:29:25 | 2021-03-22 | f | 1967.0 |
| 4 | 1_483 | 15.99 | 1 | s_178686 | c_4410 | 21:35:55 | 2022-03-13 | f | 1967.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 679129 | 0_1438 | 9.31 | 0 | s_215697 | c_84 | 06:11:50 | 2022-05-29 | f | 1982.0 |
| 679130 | 0_1020 | 9.71 | 0 | s_107849 | c_84 | 03:52:13 | 2021-10-21 | f | 1982.0 |
| 679131 | 0_1399 | 17.99 | 0 | s_98493 | c_84 | 16:13:18 | 2021-10-01 | f | 1982.0 |
| 679132 | 0_1417 | 17.99 | 0 | s_5960 | c_84 | 23:55:06 | 2021-03-13 | f | 1982.0 |
| 679133 | 0_1438 | 9.31 | 0 | s_186172 | c_84 | 06:11:50 | 2022-03-29 | f | 1982.0 |
632500 rows × 9 columns
lapage_particulier.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 632500 entries, 0 to 679133 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 632477 non-null object 1 price 632477 non-null float64 2 categ 632477 non-null object 3 session_id 632477 non-null object 4 client_id 632500 non-null object 5 heure 632477 non-null object 6 dateX 632477 non-null datetime64[ns] 7 sex 632500 non-null object 8 birth 632500 non-null int64 dtypes: datetime64[ns](1), float64(1), int64(1), object(6) memory usage: 48.3+ MB
#reconfiguration de 'birth' en variable quantitative continue
lapage_particulier["birth"] = lapage_particulier["birth"].astype('float')
/var/folders/_s/572yf6nj0lbb9r53g74rp6c80000gn/T/ipykernel_67641/3855357637.py:2: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
lapage_particulier["birth"] = lapage_particulier["birth"].astype('float')
lapage_particulier.head(2)
| id_prod | price | categ | session_id | client_id | heure | dateX | sex | birth | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 0_1316 | 7.20 | 0 | s_141762 | c_4410 | 09:11:18 | 2021-12-29 | f | 1967.0 |
| 1 | 1_385 | 25.99 | 1 | s_9707 | c_4410 | 01:40:22 | 2021-03-22 | f | 1967.0 |
#le lien entre le genre d’un client et les catégories des livres achetés ;
#aggregation du prix en fonction des categories et du sex.
X = "categ"
Y = "sex"
cont = lapage_particulier[[X,Y]].pivot_table(index=X,columns=Y,aggfunc=len,margins=True,margins_name="Total")
cont
| sex | f | m | Total |
|---|---|---|---|
| categ | |||
| 0 | 200793 | 186488 | 387281 |
| 1 | 111331 | 101017 | 212348 |
| 2 | 16980 | 15868 | 32848 |
| Total | 329104 | 303373 | 632477 |
#representation par heatmap pour 2 variables qualitatives.
#tx = cont.loc[:,["Total"]]
#ty = cont.loc[["Total"],:]
#n = len(lapage_particulier )
#indep = tx.dot(ty) / n
c = cont.fillna(0) # On remplace les valeurs nulles par 0
#measure = (c-indep)**2/indep
#xi_n = measure.sum().sum()
#table = measure/xi_n
sns.heatmap(cont.iloc[:-1,:-1],annot=c.iloc[:-1,:-1])
plt.show()
plt.figure(figsize = (16,8))#(- largeur, - de hauteur)
<Figure size 1152x576 with 0 Axes>
<Figure size 1152x576 with 0 Axes>
#Calcul du chi2 d'ajustement pour savoir si la distribution observé est similaire à la distribution théorique
#On cherche a savoir si le sex a un effet sur le choix de la catégorie.
from scipy.stats import chi2_contingency
chi2_contingency(cont)
(20.213460693513895,
0.0025371819244419495,
6,
array([[201518.35754344, 185762.64245656, 387281. ],
[110493.46646914, 101854.53353086, 212348. ],
[ 17092.17598743, 15755.82401257, 32848. ],
[329104. , 303373. , 632477. ]]))
On remarque un lien essentiellement avec la catégorie 0.
On peut rejeter H0 donc il y a difference (p-value***).
Chi2 -> 20.2(p<0.01) nous indique que les resultat n'est pas lié au hasard.
(Array -> table de contingence distribué au hasard)
#generation de la table de contingence sans margins. pour calcul du chi2.
cont = lapage_particulier[[X,Y]].pivot_table(index=X,columns=Y,aggfunc=len,margins=False,margins_name="Total")
cont
| sex | f | m |
|---|---|---|
| categ | ||
| 0 | 200793 | 186488 |
| 1 | 111331 | 101017 |
| 2 | 16980 | 15868 |
#aggreagt pour CA perso
CAParticulier = lapage_particulier.groupby(["client_id","birth"] ).agg({"price" : sum }).reset_index()
CAParticulier.rename(columns = {"price" : "CAperso"}, inplace = True)
CAParticulier
| client_id | birth | CAperso | |
|---|---|---|---|
| 0 | c_1 | 1955.0 | 558.18 |
| 1 | c_10 | 1956.0 | 1353.60 |
| 2 | c_100 | 1992.0 | 254.85 |
| 3 | c_1000 | 1966.0 | 2261.89 |
| 4 | c_1001 | 1982.0 | 1812.86 |
| ... | ... | ... | ... |
| 8614 | c_997 | 1994.0 | 1490.01 |
| 8615 | c_998 | 2001.0 | 2779.88 |
| 8616 | c_999 | 1964.0 | 701.40 |
| 8617 | ct_0 | 2001.0 | 0.00 |
| 8618 | ct_1 | 2001.0 | 0.00 |
8619 rows × 3 columns
#observation de la distribution de birth
sns.displot(CAParticulier['birth'])
<seaborn.axisgrid.FacetGrid at 0x7f836550cf70>
import numpy as np
import statsmodels.api as smi
import pylab
smi.qqplot(CAParticulier['birth'], line = "r")
plt.title("droite de henry 'birth' ")
pylab.show()
La droite de henry confirme que nos données s'ecartent de la loi normale, par la présence élevé de personne née en 2000.
Notre echgantillion n'est pas représentatif de la population general car sur-représenté par les jeunes. En revanche cet echantillon correspond à ceux attendus pour l'usage des technologie dans cette ère du temps.
#observation de la distribution du CAperso
sns.displot(CAParticulier['CAperso'])
<seaborn.axisgrid.FacetGrid at 0x7f8365596eb0>
#plt.title("droite de henry 'CA perso' ")
smi.qqplot(CAParticulier['CAperso'], line = "r")
pylab.show()
Les données suivent une autre loi que celle de la loi normale.
#verification de l'amplitude des données
CAParticulier['CAperso'].describe()
count 8619.000000 mean 1273.082508 std 955.181131 min 0.000000 25% 543.830000 50% 1023.270000 75% 1776.600000 max 5276.870000 Name: CAperso, dtype: float64
#calcul correlation
#version non parametrique
import numpy as np
from scipy import stats
res = stats.spearmanr(CAParticulier['CAperso'], CAParticulier['birth'])
print(res)
print(f' p-value : {res.pvalue}')
SpearmanrResult(correlation=0.1817646363002175, pvalue=6.441219053644e-65) p-value : 6.441219053644e-65
#version parametrique
from scipy.stats import pearsonr
pearsonr (CAParticulier['CAperso'], CAParticulier['birth'])
(0.18596699741155262, 6.4023938280296465e-68)
coefficient de pearson 0.18
p-value***
-> On peut rejeter H0 (donc y'a correlation) mais pas forcement lineaire
#visualisation primaire par scatterplot
plt.figure(figsize = (14,8))#(- largeur, - de hauteur)
ax = sns.scatterplot (y = CAParticulier['CAperso'],x = CAParticulier['birth'])
ax.set(xlabel='birth', ylabel='C A perso')
plt.show()
On remarque que les plus agés depensent moins dans la bibliothèque.
# reprensation CA et Birth sous forme de boxplot classé
taille_classe = 10 # taille des classes pour la discrétisation
groupes = [] # va recevoir les données agrégées à afficher
# on calcule des tranches allant de 0 au solde maximum par paliers de taille taille_classe
tranches = np.arange(1929, max(CAParticulier["birth"]), taille_classe)
tranches += taille_classe/2 # on décale les tranches d'une demi taille de classe
indices = np.digitize(CAParticulier["birth"], tranches) # associe chaque solde à son numéro de classe
for ind, tr in enumerate(tranches): # pour chaque tranche, ind reçoit le numéro de tranche et tr la tranche en question
CAp = CAParticulier.loc[indices==ind,"CAperso"] # sélection des individus de la tranche ind
if len (CAp) > 0:
g={
'valeurs': CAp,'centre_classe': tr-(taille_classe/2),'taille': len(CAp),
'quartiles': [np.percentile(CAp,p) for p in [25,50,75]]
}
groupes.append(g)
plt.figure(figsize=(12,7))
# affichage des boxplots
plt.boxplot([g["valeurs"] for g in groupes],
positions= [g["centre_classe"] for g in groupes], # abscisses des boxplots
showfliers= True, # False elimine les outliers
widths= taille_classe*0.5) # largeur graphique des boxplots
# affichage des effectifs de chaque classe
for g in groupes:
plt.text(g["centre_classe"],0,"(n={})".format(g["taille"]),horizontalalignment='center',verticalalignment='top')
plt.show()
#aggregat pour la frequence
func = lambda x : x["session_id"].nunique()#Recherche dans les colonnes et renvoie le nombre de valeurs uniques pour chaque ligne.
dfFreqPart1 = lapage_particulier [["client_id","session_id","birth"]].groupby(["client_id","birth"]).apply(func)
dfFreqPart1
client_id birth
c_1 1955.0 33
c_10 1956.0 34
c_100 1992.0 5
c_1000 1966.0 93
c_1001 1982.0 47
..
c_997 1994.0 24
c_998 2001.0 23
c_999 1964.0 42
ct_0 2001.0 0
ct_1 2001.0 0
Length: 8619, dtype: int64
dfFreqPart1.info()
<class 'pandas.core.series.Series'>
MultiIndex: 8619 entries, ('c_1', 1955.0) to ('ct_1', 2001.0)
Series name: None
Non-Null Count Dtype
-------------- -----
8619 non-null int64
dtypes: int64(1)
memory usage: 418.7+ KB
le resulat est de type series (=! type dataframe), on va l'ajouter a notre dataframe
dfFreqPart2 = lapage_particulier[["client_id","session_id","birth"]].groupby(["client_id","birth"]).count().reset_index()
dfFreqPart2["frequence"]= dfFreqPart1.values #ici .values permet d'éviter un probleme d'indexage.extrait tout comme un numpyarray.
dfFreqPart2
| client_id | birth | session_id | frequence | |
|---|---|---|---|---|
| 0 | c_1 | 1955.0 | 39 | 33 |
| 1 | c_10 | 1956.0 | 58 | 34 |
| 2 | c_100 | 1992.0 | 8 | 5 |
| 3 | c_1000 | 1966.0 | 125 | 93 |
| 4 | c_1001 | 1982.0 | 102 | 47 |
| ... | ... | ... | ... | ... |
| 8614 | c_997 | 1994.0 | 59 | 24 |
| 8615 | c_998 | 2001.0 | 53 | 23 |
| 8616 | c_999 | 1964.0 | 46 | 42 |
| 8617 | ct_0 | 2001.0 | 0 | 0 |
| 8618 | ct_1 | 2001.0 | 0 | 0 |
8619 rows × 4 columns
#pearson entre frequence et birth #+ scatterplot
from scipy.stats import pearsonr
pearsonr (dfFreqPart2['frequence'], dfFreqPart2['birth'])
(-0.16536457412892014, 6.819828969679714e-54)
Le coefficient de pearson indique une correlation negative r= -0.16 p<0.01.
res01 = stats.spearmanr(dfFreqPart2['frequence'], dfFreqPart2['birth'])
print(res01)
print(f' p-value : {res.pvalue}')
SpearmanrResult(correlation=-0.21322431835262393, pvalue=3.454164915199389e-89) p-value : 6.441219053644e-65
#visualisation primaire par scatterplot
(dfFreqPart2['frequence'], dfFreqPart2['birth'])
plt.figure(figsize = (14,8))#(- largeur, - de hauteur)
ax = sns.scatterplot (y = dfFreqPart2['frequence'],x = dfFreqPart2['birth'])
ax.set(xlabel='birth', ylabel='frequence')
plt.show()
# reprensation sous forme de boxplot classé #
taille_classe = 10 # taille des classes pour la discrétisation
groupes = [] # va recevoir les données agrégées à afficher
# on calcule des tranches allant de 0 au solde maximum par paliers de taille taille_classe
tranches = np.arange(1929, max(dfFreqPart2["birth"]), taille_classe)
tranches += taille_classe/2 # on décale les tranches d'une demi taille de classe
indices = np.digitize(dfFreqPart2["birth"], tranches) # associe chaque solde à son numéro de classe
for ind, tr in enumerate(tranches): # pour chaque tranche, ind reçoit le numéro de tranche et tr la tranche en question
Freqp = dfFreqPart2.loc[indices==ind,'frequence'] # sélection des individus de la tranche ind
if len (Freqp) > 0:
g={
'valeurs': Freqp,'centre_classe': tr-(taille_classe/2),'taille': len(Freqp),
'quartiles': [np.percentile(Freqp,p) for p in [25,50,75]]
}
groupes.append(g)
plt.figure(figsize=(12,10))
# affichage des boxplots
plt.boxplot([g["valeurs"] for g in groupes],
positions= [g["centre_classe"] for g in groupes], # abscisses des boxplots
showfliers= True, # False elimine les outliers
widths= taille_classe*0.5) # largeur graphique des boxplots
# affichage des effectifs de chaque classe
for g in groupes:
plt.text(g["centre_classe"],0,"(n={})".format(g["taille"]),horizontalalignment='center',verticalalignment='top')
plt.show()
Cacul du coefficient de correlation entre age & panier moyen.
#aggregat des session de connexion et prix pour chaque client.
df_cl_session = lapage_particulier.groupby(["client_id","session_id"] ).agg({"price" : sum })
df_cl_session
| price | ||
|---|---|---|
| client_id | session_id | |
| c_1 | s_105105 | 7.99 |
| s_114737 | 92.62 | |
| s_120172 | 44.29 | |
| s_134971 | 10.30 | |
| s_136532 | 13.78 | |
| ... | ... | ... |
| c_999 | s_88239 | 15.99 |
| s_89648 | 5.99 | |
| s_92374 | 8.57 | |
| s_98289 | 11.99 | |
| s_99070 | 7.98 |
319237 rows × 1 columns
#calcul du panier moyen par aggregation des session et des prix
df_cl_panier = df_cl_session.groupby(['client_id']).mean()
df_cl_panier.rename(columns = {"price" : "panier_moyen"}, inplace = True)
df_cl_panier
| panier_moyen | |
|---|---|
| client_id | |
| c_1 | 16.914545 |
| c_10 | 39.811765 |
| c_100 | 50.970000 |
| c_1000 | 24.321398 |
| c_1001 | 38.571489 |
| ... | ... |
| c_995 | 21.045556 |
| c_996 | 20.576962 |
| c_997 | 62.083750 |
| c_998 | 120.864348 |
| c_999 | 16.700000 |
8596 rows × 1 columns
#confirmation du resultat par calcul manuel sur client 1
df_cl_session.loc['c_1'].mean()
price 16.914545 dtype: float64
# jonction : recuperation des ages.
df_cl_panier = pd.merge(df_cl_panier, customers , how = 'left', on = 'client_id' , indicator = True)
df_cl_panier
| client_id | panier_moyen | sex | birth | _merge | |
|---|---|---|---|---|---|
| 0 | c_1 | 16.914545 | m | 1955.0 | both |
| 1 | c_10 | 39.811765 | m | 1956.0 | both |
| 2 | c_100 | 50.970000 | m | 1992.0 | both |
| 3 | c_1000 | 24.321398 | f | 1966.0 | both |
| 4 | c_1001 | 38.571489 | m | 1982.0 | both |
| ... | ... | ... | ... | ... | ... |
| 8591 | c_995 | 21.045556 | m | 1955.0 | both |
| 8592 | c_996 | 20.576962 | f | 1970.0 | both |
| 8593 | c_997 | 62.083750 | f | 1994.0 | both |
| 8594 | c_998 | 120.864348 | m | 2001.0 | both |
| 8595 | c_999 | 16.700000 | m | 1964.0 | both |
8596 rows × 5 columns
df_cl_panier.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 8596 entries, 0 to 8595 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 client_id 8596 non-null object 1 panier_moyen 8596 non-null float64 2 sex 8596 non-null object 3 birth 8596 non-null float64 4 _merge 8596 non-null category dtypes: category(1), float64(2), object(2) memory usage: 344.3+ KB
# version non parametrique
from scipy import stats
stats.spearmanr(df_cl_panier['panier_moyen'],df_cl_panier['birth'] )
SpearmanrResult(correlation=0.7011233103707742, pvalue=0.0)
#version parametrique
from scipy.stats import pearsonr
pearsonr (df_cl_panier['panier_moyen'], df_cl_panier['birth'])
(0.6168575055268313, 0.0)
coefficient de person : 0,616 P-value***
#visualisation primaire par scatterplot
#pearsonr (df_cl_panier['panier_moyen'], df_cl_panier['birth'])
#(df_cl_panier['panier_moyen'], df_cl_panier['birth'])
plt.figure(figsize = (14,8))#(- largeur, - de hauteur)
ax = sns.scatterplot (y = df_cl_panier['panier_moyen'],x = df_cl_panier['birth'])
ax.set(xlabel='birth', ylabel='panier_moy')
plt.show()
# reprensation panier moyen sous forme de boxplot classé #
taille_classe = 20 # taille des classes pour la discrétisation
groupes = [] # va recevoir les données agrégées à afficher
# on calcule des tranches allant de 0 au solde maximum par paliers de taille taille_classe
tranches = np.arange(1929, max(df_cl_panier["birth"]), taille_classe)
tranches += taille_classe/2 # on décale les tranches d'une demi taille de classe
indices = np.digitize(df_cl_panier["birth"], tranches) # associe chaque solde à son numéro de classe
for ind, tr in enumerate(tranches): # pour chaque tranche, ind reçoit le numéro de tranche et tr la tranche en question
Panierp = df_cl_panier.loc[indices==ind,'panier_moyen'] # sélection des individus de la tranche ind
if len (Panierp) > 0:
g={
'valeurs': Panierp,'centre_classe': tr-(taille_classe/2),'taille': len(Panierp),
'quartiles': [np.percentile(Panierp,p) for p in [25,50,75]]
}
groupes.append(g)
plt.figure(figsize=(12,10))
# affichage des boxplots
plt.boxplot([g["valeurs"] for g in groupes],
positions= [g["centre_classe"] for g in groupes], # abscisses des boxplots
showfliers= True, # False elimine les outliers
widths= taille_classe*0.5) # largeur graphique des boxplots
# affichage des effectifs de chaque classe
for g in groupes:
plt.text(g["centre_classe"],0,"(n={})".format(g["taille"]),horizontalalignment='center',verticalalignment='top')
plt.show()
L'analyse par correlation indique que : au plus les clients sont jeunes au plus le prix du panier moyen est élevé.
Mais la visualisation par boxplot segmenté apporte une nuance : c'est la generation née a partir de 1989 qui concentre les plus hauts paniers.
correlation de person : 0,616 (p<0.001)
lapage_particulier.head(3)
| id_prod | price | categ | session_id | client_id | heure | dateX | sex | birth | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 0_1316 | 7.20 | 0 | s_141762 | c_4410 | 09:11:18 | 2021-12-29 | f | 1967.0 |
| 1 | 1_385 | 25.99 | 1 | s_9707 | c_4410 | 01:40:22 | 2021-03-22 | f | 1967.0 |
| 2 | 1_190 | 14.53 | 1 | s_118628 | c_4410 | 18:11:43 | 2021-11-12 | f | 1967.0 |
lapage_particulier.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 632500 entries, 0 to 679133 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 632477 non-null object 1 price 632477 non-null float64 2 categ 632477 non-null object 3 session_id 632477 non-null object 4 client_id 632500 non-null object 5 heure 632477 non-null object 6 dateX 632477 non-null datetime64[ns] 7 sex 632500 non-null object 8 birth 632500 non-null float64 dtypes: datetime64[ns](1), float64(2), object(6) memory usage: 48.3+ MB
#plt.title("droite de henry 'CA perso' ")
smi.qqplot(lapage_particulier['birth'], line = "r")
pylab.show()
#normalité et distribution des données
stats.normaltest(lapage_particulier['birth'])
NormaltestResult(statistic=33911.93845753692, pvalue=0.0)
Normaltest :
H0 : l'echantillon suit une loi normale
H1 : l'echantillon suit une autre loi (p<0.01)
from scipy.stats import shapiro
shapiro(lapage_particulier['birth'])
/Users/denovanegarel/opt/anaconda3/lib/python3.9/site-packages/scipy/stats/morestats.py:1760: UserWarning: p-value may not be accurate for N > 5000.
warnings.warn("p-value may not be accurate for N > 5000.")
ShapiroResult(statistic=0.9480418562889099, pvalue=0.0)
ShapiroTest :
H0 : l'echantillon suit une loi normale
H1 : l'echantillon suit une autre loi (p<0.01).
lapage_particulier.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 632500 entries, 0 to 679133 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 632477 non-null object 1 price 632477 non-null float64 2 categ 632477 non-null object 3 session_id 632477 non-null object 4 client_id 632500 non-null object 5 heure 632477 non-null object 6 dateX 632477 non-null datetime64[ns] 7 sex 632500 non-null object 8 birth 632500 non-null float64 dtypes: datetime64[ns](1), float64(2), object(6) memory usage: 48.3+ MB
liste = [ lapage_particulier.birth[lapage_particulier.categ==str(categ)]for categ in range(3)]#comprehension de liste.
#la boucle range(3) coincidence avrec les 3 categorie. les 3 listes contiennent juste les ages.
#en fait kruskalwallis veut 3 listes.
liste
[0 1967.0
3 1967.0
6 1967.0
8 1967.0
11 1967.0
...
679129 1982.0
679130 1982.0
679131 1982.0
679132 1982.0
679133 1982.0
Name: birth, Length: 387281, dtype: float64,
1 1967.0
2 1967.0
4 1967.0
5 1967.0
7 1967.0
...
679118 1982.0
679122 1982.0
679123 1982.0
679124 1982.0
679127 1982.0
Name: birth, Length: 212348, dtype: float64,
72 1967.0
231 1993.0
233 1993.0
236 1993.0
238 1993.0
...
678583 2003.0
678585 2003.0
678587 2003.0
678590 2003.0
678942 1974.0
Name: birth, Length: 32848, dtype: float64]
#Version non parametrique kruskal wallis
from scipy import stats
stats.kruskal(*liste) #argeument aurait pu prendre 3 listes ; #etoile unpack permet d'ouvrir la liste #prend chaque element de la liste et le met comme argument a part.
KruskalResult(statistic=72209.48289794478, pvalue=0.0)
#ANOVA parametrique
# AVEC :
#X = "categ" # qualitative
#Y = "birht" # quantitative
import statsmodels
import statsmodels.api
mdl = statsmodels.formula.api.ols('birth ~ categ', data = lapage_particulier)
res = mdl.fit()
table = statsmodels.api.stats.anova_lm(res)
table
| df | sum_sq | mean_sq | F | PR(>F) | |
|---|---|---|---|---|---|
| categ | 2.0 | 1.374187e+07 | 6.870934e+06 | 40407.204133 | 0.0 |
| Residual | 632474.0 | 1.075473e+08 | 1.700423e+02 | NaN | NaN |
On rejette H0
Les 2 tests confirment qu'il y a un effet de l'age sur la categorie.
#eta_squarred est une mesure de la taille de l'effet dans les modele ANOVA
#eta_squared = categ sumcateg /(categsum_sq + Residual sum_sq)
eta_squared = 1.374187e+07 /( 1.374187e+07+1.075473e+08)
print(f"eta_squared : {eta_squared}")
print(f"le modèle explique : {round(eta_squared*100,2)}% de la variance totale.")
eta_squared : 0.11329840908302036 le modèle explique : 11.33% de la variance totale.
#visualisation par boxplot age categ
plt.figure(figsize = (12,9))#(- largeur, - de hauteur)
sns.boxplot(data = lapage_particulier, x='categ' , y='birth' )
<AxesSubplot:xlabel='categ', ylabel='birth'>
-On remarque que la categorie 2 concentre les jeunes(+1990).
-retrouve tous les clients dans la catégorie1 avec une certaines prévalence des plus agés (1960).
Les sessions test (T_O) on été supprimé du ficher transaction
Produit numéro = id_prod 0_2245 : a faire remonter au gérant pour ajustement
23 clients n’ont aucun transactions : a faire remonter au gérant pour ajustement
Les 4 clients pro on été exclu de l’analyse.
On remarque des prix davantage élevé dans la catégorie 2
voir ("Boxplot2_prix/categ").
CA par catégorie : On remarque un CA plus représenté par la catégorie 1 (39%)
CA Total = 11.9 M €
Piechart : Représenation du CA par Categorie
Serie temporelle : une courbe représentant le CA sur 2 ans avec moyenne glissante (7jours) a été réalisé.
On remarque que tout le mois d'octobre est vide # zonevide
La courbe de Lorenz et l'indice de Gini ajusté à 0.4 nous informe de la repartition du CA relativement équilibré.
Top/flop : une ligne de commande a été configuré pour afficher n'importe quel nombre de produit
Un graphique heatmap a été généré entre sex et catégorie.
Le test du Chi2 -> 20.2(p>0.01) nous indique que les resultat n'est pas lié au hasard. les 2 variables ont une certaines dependance : être un homme ou une femme affecte le choix de la categorie.
Les données suivent une autre loi que celle de la loi normale. Elles ont un début de distribution gaussienne pour finalement être sur-représentés par les personnes nées en 2000.
La version non-paramétrique a été privilégie, la version paramétrique a été utilisé pour élargir le champs d’analyse
Coeff de Correlation entre l’age et differentes variables ont été calculés par client.
Spearmanr Result(correlation=0.1817646363002175, pvalue=6.441219053644e-65) p-value : 6.441219053644e-65
Paramétrique version : Personr (0.18596699741155262, 6.4023938280296465e-68
Les deux versions indiquent le meme résultat : Une corrélation de l’ordre de 18%(p<0.001)
SpearmanrResult(correlation=-0.21322431835262393, pvalue=3.454164915199389e-89)
Le coefficient de Pearson indique une correlation negative r= -0.16 p<0.01.
SpearmanrResult(correlation=0.7011233103707742, pvalue=0.0)
coefficient de pearson : 0,616, P<0.01)
La visualisation par boxplot segmenté apporte une nuance à la corrélation de 0.61(p<0.001) c'est la génération née a partir de 1989 qui concentre les plus hauts paniers.
Analyse de la variance ente l'age et la catégorie.
Kruskal Wallis et ANOVA test (non parametrique) on été réalisé pour savoir si l’âge avait un impact sur la catégorie.
les deux tests ont les même resultats. 11% de la variance total pourrait être expliqué par un ce modele.
On remarque que la categorie 2 concentre les jeunes(+1990).
On retrouve tous les clients dans la catégorie1 avec une certaines prévalence des plus agés (1960).
plt.figure(figsize = (7,5))
plt.title("La cetgorie 2 contient les plus haut prix(Boxplot2)")
Boxplot2 = sns.boxplot (x = products_dat['categ'],y = products_dat['price'])
# CA globale par catégorie
plt.figure(figsize = (9,5))#16 de largeur, 8 de hauteur
#definir data
Cat_pie = [CAcat0, CAcat1, CAcat2]
labels = ['cat0', 'cat1', 'cat2']
#definiri Seaborn palette couleur seaborn
colors = sns.color_palette('pastel')[0:5]
title = 'Représenation du CA par Categorie'
#generer pie chart
plt.pie(Cat_pie, labels = labels, colors = colors, autopct='%.0f%%')
#Titre
plt.title("CA Total = 11.9 M € ", bbox={'facecolor':'1', 'pad':5})
#ax.set(xlabel='common xlabel', ylabel='common ylabel')
plt.show()
#Lissage de la courbe du CA sur 7jours
CAjour["CAday7"] = CAjour["CAday"].rolling(7).mean()
plt.figure(figsize = (9,5))
plt.title('CA 2ans lissé sur 7jours' )
CA_2ans_lissé_sur_7jours = sns.lineplot(data = CAjour, x = 'dateX' , y = 'CAday7')
plt.figure(figsize = (9,5))
plt.title('Representation du CA par categorie')
sns.lineplot(data = Dfnb_vente01, x = 'dateX' , y = 'value',hue = 'categ')
<AxesSubplot:title={'center':'Representation du CA par categorie'}, xlabel='dateX', ylabel='value'>
#courbe de Lorenz
dep = CAclient00['CAperso']
n = len(dep)
lorenz = np.cumsum(np.sort(dep)) / dep.sum()
lorenz = np.append([0],lorenz) # La courbe de Lorenz commence à 0
xaxis = np.linspace(0-1/n,1+1/n,n+1) #Il y a un segment de taille n pour chaque individu, plus 1 segment supplémentaire d'ordonnée 0. Le premier segment commence à 0-1/n, et le dernier termine à 1+1/n.
plt.figure(figsize = (8,5))
plt.title('Courbe de Lorenz CA client')
plt.plot(xaxis,lorenz,drawstyle='steps-post')
plt.plot((0,0),(1,1), c='r')
plt.show()
Courbe de Lorenz
On remarque une répartition relativement constante avec une flexion très forte pour quelques clients cumulant un CA très elevé
#representation par heatmap pour 2 variables qualitatives.
c = cont.fillna(0) # On remplace les valeurs nulles par 0
sns.heatmap(cont.iloc[:-1,:-1],annot=c.iloc[:-1,:-1])
plt.show()
plt.figure(figsize = (9,5))#(- largeur, - de hauteur)
<Figure size 648x360 with 0 Axes>
<Figure size 648x360 with 0 Axes>
plt.figure(figsize = (7,5))#(- largeur, - de hauteur)
sns.boxplot(data = lapage_particulier, x='categ' , y='birth' )
<AxesSubplot:xlabel='categ', ylabel='birth'>