Analysez les ventes d'une librairie avec R ou Python¶

Projet OpenClassRooms N°6 : LAPAGE librairie¶

Sommaire

  • I-Preparations des données & analyses exploratoires

I.a-Customers

I.b-Products

I.c-Transactions

  • II Analyse du Chiffre d'affaire

II.a-Serie temporelle CA

II.b-Les trois flux de vente

II.c-Analyse des produits

  • III Test de comparaison (Julie)

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

  • IV Conclusion
In [ ]:
 

I- Preparations & visualisation des données¶

In [2]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
pd.__version__
Out[2]:
'1.4.2'
In [4]:
customers = pd.read_csv('customers.csv')
customers.head(3)
Out[4]:
client_id sex birth
0 c_4410 f 1967
1 c_7839 f 1975
2 c_1699 f 1984
In [5]:
products = pd.read_csv('products.csv')
products.head(3)
Out[5]:
id_prod price categ
0 0_1421 19.99 0
1 0_1368 5.13 0
2 0_731 17.99 0
In [6]:
transactions = pd.read_csv('transactions.csv')
transactions.head(3)
Out[6]:
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

I.a - customers¶

In [33]:
customers
Out[33]:
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

In [34]:
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
In [7]:
#reconfiguration de 'birth' en variable quantitative continue
customers["birth"] = customers["birth"].astype('float')
In [36]:
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
In [38]:
#Statistique descriptives exploratoires
customers.describe()
Out[38]:
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
In [8]:
#detection des doublons
customers.loc[customers.duplicated(keep=False),:]
#aucun doublons detecté
Out[8]:
client_id sex birth
In [9]:
#confirmation de l'absence de doublons sur la clé primaire 
customers.loc[customers["client_id"].duplicated(keep=False),:]
#aucun doublons detecté 
Out[9]:
client_id sex birth
In [10]:
#detection des valeurs manquantes 
print(customers.isnull().sum())
#toute les données sont renseignés 
client_id    0
sex          0
birth        0
dtype: int64
In [11]:
#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'].
In [12]:
# 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
In [13]:
#visualisation de la rapartition homme/femme 
plt.figure(figsize = (12,8))#16 de largeur, 8 de hauteur 
sns.countplot (data = customers, x = 'sex')
Out[13]:
<AxesSubplot:xlabel='sex', ylabel='count'>

I.b-Preparations des données Products¶

In [45]:
products
Out[45]:
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

In [46]:
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
In [14]:
products.describe()
#Anomalie. : 
#categ est une qualitative, integer est inapproprié 
#min -1
Out[14]:
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
In [15]:
products["categ"].unique()
Out[15]:
array([0, 1, 2])
In [16]:
#creation d'une copie de product pour eviter de corrompre les produits
products_dat = products
In [17]:
#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
In [18]:
products_dat.describe()
#une valeur negative détectés 
Out[18]:
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
In [19]:
#detection du prix negatif 
negprice = products_dat.loc[products_dat["price"] <0,:]
negprice
Out[19]:
id_prod price categ
731 T_0 -1.0 0
In [20]:
#suppresion du prix negatif par generation d'une copie avec condition.
products_dat = products_dat[products_dat["price"]>0]
In [21]:
#detection des doublons
products_dat.loc[products_dat.duplicated(keep=False),:]
#aucun doublons detecté
Out[21]:
id_prod price categ
In [22]:
#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
Out[22]:
id_prod price categ
In [23]:
#Visualisation des valeur null
print(products_dat.isnull().sum())
#aucun NaN 
id_prod    0
price      0
categ      0
dtype: int64
In [24]:
#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€.

In [25]:
#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'])

I.c-transactions¶

In [26]:
transactions 
Out[26]:
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

In [27]:
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
In [28]:
#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)
Out[28]:
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
In [74]:
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
In [29]:
#detection de Valeurs nulles
print(transactions.isnull().sum())
#Aucun 
id_prod       0
session_id    0
client_id     0
heure         0
dateX         0
dtype: int64
In [30]:
#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. 
Out[30]:
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

In [31]:
# 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
Out[31]:
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
In [32]:
#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 
Out[32]:
id_prod session_id client_id heure dateX
In [33]:
#confirmation de l'absence de NaN sur session_id
print(transactions["session_id"].isnull().sum())
0
In [34]:
#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

II- Analyse du Chiffre d'affaire¶

Jonction des dataframes

In [35]:
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
Out[35]:
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

In [36]:
#detection des erreurs de jonction
prod_transac['_merge'].value_counts()
#221 lignes n'ont pas de correspondance dans product_dat
Out[36]:
both          679111
right_only       221
left_only          0
Name: _merge, dtype: int64
In [37]:
#affichage des jonctions droite seulement
prod_transac[prod_transac['_merge'] == 'right_only']
Out[37]:
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

In [38]:
#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é. 
Out[38]:
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.

In [39]:
# ecrasement de l'objet non repertorié par condition 
prod_transac = prod_transac[prod_transac['price'].notnull()]
prod_transac.shape
Out[39]:
(679111, 8)
In [40]:
#suppression de colonne -merge pour eviter les conflits 
prod_transac = prod_transac.drop(columns = '_merge')
prod_transac.head(1)
Out[40]:
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
In [41]:
#jonctions avec clients
lapagefull = pd.merge(prod_transac, customers , how = 'right', on = 'client_id' , indicator = True) 
lapagefull
Out[41]:
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

In [42]:
#detection des erreurs de merge
lapagefull['_merge'].value_counts()
# 23 echec merge
Out[42]:
both          679111
right_only        23
left_only          0
Name: _merge, dtype: int64
In [43]:
#zoom sur les 23 echec de merge right_only
lapagefull[lapagefull['_merge'] == 'right_only']
Out[43]:
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

In [44]:
#suppression de colonne -merge pour alléger le df
lapagefull = lapagefull.drop(columns = '_merge')
In [52]:
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
In [45]:
#compte le nombre de fois qu'une reference sort
ref_compte=lapagefull['id_prod'].value_counts()
ref_compte
Out[45]:
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
In [46]:
lpcat0 = lapagefull.loc[lapagefull['categ'] == '0',:]
lpcat1 = lapagefull.loc[lapagefull['categ'] == '1',:]
lpcat2 = lapagefull.loc[lapagefull['categ'] == '2',:]

lpcat2.head(2)
Out[46]:
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
In [47]:
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 €.
In [48]:
# 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)

In [216]:
lapagefull.head(3)
Out[216]:
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
In [49]:
# 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
Out[49]:
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

- II.a-Serie temporelle CA¶

In [50]:
#construction de la courbe du CA general en fonction du temps. 
plt.figure(figsize = (12,10))
sns.lineplot(data = CAjour, x = 'dateX' , y = 'CAday')
Out[50]:
<AxesSubplot:xlabel='dateX', ylabel='CAday'>

On remarque que la courbe contient trop d'informations ce qui engendre du brouillard

In [51]:
#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.

In [98]:
CAjour.head(10)
Out[98]:
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
In [52]:
#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
Out[52]:
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

In [ ]:
 
In [53]:
#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 
Out[53]:
<AxesSubplot:xlabel='dateX', ylabel='CAj'>
In [54]:
#deplacement des categorie sur 3 colonnes 
CAjourcat = pd.pivot_table(data = CAjourcat , index = "dateX" , columns = "categ", values = "CAj").reset_index()
CAjourcat
Out[54]:
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

In [55]:
#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() 
In [56]:
# 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 
Out[56]:
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

In [57]:
plt.figure(figsize = (12,10))
sns.lineplot(data = CAjourcat, x = 'dateX' , y = 'value',hue = 'categ')
Out[57]:
<AxesSubplot:xlabel='dateX', ylabel='value'>
                        - Courbe du CA par catégorie lissée sur 7 jours -

- II.b-Les trois flux de vente¶

In [58]:
#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
Out[58]:
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

In [59]:
Dfnb_vente
Out[59]:
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

In [60]:
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')
Out[60]:
<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.

In [61]:
#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()
In [109]:
Dfnb_vente01.head(5)
Out[109]:
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
In [62]:
# 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
Out[62]:
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

In [63]:
plt.figure(figsize = (12,10))
plt.title('Representation du CA par categorie')
sns.lineplot(data = Dfnb_vente01, x = 'dateX' , y = 'value',hue = 'categ')
Out[63]:
<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.

In [64]:
# 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 
Out[64]:
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
In [113]:
lapagefull.head(2)
Out[113]:
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
In [65]:
#aggregation des prix par client 
CAclient00 = lapagefull.groupby("client_id" ).agg({"price" : sum }).reset_index()
CAclient00.rename(columns = {"price" : "CAperso"}, inplace = True)
CAclient00 
Out[65]:
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

In [66]:
#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é.

In [67]:
#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
In [68]:
#trie CA client ordre decroissant 
CAclient_tri = CAclient00.sort_values (by = ["CAperso"], ascending = False ).reset_index() 
CAclient_tri
Out[68]:
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

In [69]:
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.
In [70]:
listpro = ['c_3454','c_6714','c_4958','c_1609' ]
In [71]:
# 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.

In [72]:
# 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€
In [73]:
#jonctions avec les données clients (customers) 
CAclient_tri = pd.merge(CAclient_tri, customers, how = 'right', on = 'client_id', indicator = True)
CAclient_tri
Out[73]:
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

In [74]:
#detection des erreurs de merge
CAclient_tri['_merge'].value_counts()
# merge succes
Out[74]:
both          8623
left_only        0
right_only       0
Name: _merge, dtype: int64
In [75]:
#suppression de colonne -merge pour alléger le df
CAclient_tri = CAclient_tri.drop(columns = '_merge')
CAclient_tri.head(1)
Out[75]:
index client_id CAperso sex birth
0 3791 c_4410 1376.82 f 1967.0
In [76]:
CAclient_tri.head(3)
Out[76]:
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
In [77]:
# 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')
Out[77]:
<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.

In [78]:
#Creation DF en excluant les client Pro
CAclient_tri_particulier = CAclient_tri.loc[CAclient_tri["CAperso"] < 100000,["client_id","CAperso"]]
In [79]:
CAclient_tri_particulier
Out[79]:
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

In [80]:
#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()
In [81]:
#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

II.c - Analyse des produits¶

In [82]:
dfProduit = lapagefull.groupby("id_prod" ).agg({"price" : sum }).reset_index()
dfProduit.rename(columns = {"price" : "CAproduit"}, inplace = True)

dfProduit
Out[82]:
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

In [127]:
dfProduit.describe()
Out[127]:
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
In [83]:
# 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

In [84]:
# 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()
In [85]:
#Recherche des Top et Flop.

dfProduit_tri = dfProduit.sort_values(by = ['CAproduit'], ascending = False).reset_index()
dfProduit_tri
Out[85]:
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

In [90]:
#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

- III Test de comparaison (Julie)¶


- III.a Correlation sex categorie¶

In [91]:
#on exclut les 4 clients pro (outliers) pour les tests
lapage_particulier = lapagefull.loc[~lapagefull["client_id"].isin(listpro), :]
lapage_particulier 
Out[91]:
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

In [129]:
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
In [92]:
#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')
In [134]:
lapage_particulier.head(2)
Out[134]:
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
In [93]:
#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
Out[93]:
sex f m Total
categ
0 200793 186488 387281
1 111331 101017 212348
2 16980 15868 32848
Total 329104 303373 632477
In [94]:
#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) 
Out[94]:
<Figure size 1152x576 with 0 Axes>
<Figure size 1152x576 with 0 Axes>
In [95]:
#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)
Out[95]:
(20.213460693513895,
 0.0025371819244419495,
 6,
 array([[201518.35754344, 185762.64245656, 387281.        ],
        [110493.46646914, 101854.53353086, 212348.        ],
        [ 17092.17598743,  15755.82401257,  32848.        ],
        [329104.        , 303373.        , 632477.        ]]))
  • La matrice de correlation nous donne une représentation de l'interet des 3 categories plus ou moins liées au client selon leur sex.

On remarque un lien essentiellement avec la catégorie 0.

  • Le test du chi2 confirme qu'il y a un lien entre le sex et la catégorie.
  • H0 : il n'y a pas difference lié au sex pour le choix de la catégorie.
  • H1 : il y a une influence du sex sur le choix de la catégorie

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.

  • les 2 variables ont une certaine dependance : être un homme ou une femme affecte le choix de la categorie.

(Array -> table de contingence distribué au hasard)

In [96]:
#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
Out[96]:
sex f m
categ
0 200793 186488
1 111331 101017
2 16980 15868

- III.b Age : CA, Frequence & Panier moyen¶

In [97]:
#aggreagt pour CA perso 
CAParticulier = lapage_particulier.groupby(["client_id","birth"] ).agg({"price" : sum }).reset_index()
CAParticulier.rename(columns = {"price" : "CAperso"}, inplace = True)
CAParticulier
Out[97]:
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

In [98]:
#observation de la distribution de birth
sns.displot(CAParticulier['birth'])
Out[98]:
<seaborn.axisgrid.FacetGrid at 0x7f836550cf70>
In [99]:
import numpy as np
import statsmodels.api as smi
import pylab
In [100]:
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.


In [101]:
#observation de la distribution du CAperso
sns.displot(CAParticulier['CAperso'])
Out[101]:
<seaborn.axisgrid.FacetGrid at 0x7f8365596eb0>
In [102]:
#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.

In [103]:
#verification de l'amplitude des données
CAParticulier['CAperso'].describe()
Out[103]:
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
In [104]:
#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
In [105]:
#version parametrique
from scipy.stats import pearsonr 
pearsonr (CAParticulier['CAperso'], CAParticulier['birth'])
Out[105]:
(0.18596699741155262, 6.4023938280296465e-68)

coefficient de pearson 0.18

p-value***

-> On peut rejeter H0 (donc y'a correlation) mais pas forcement lineaire

In [106]:
#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.

In [107]:
# 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
In [108]:
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()
In [109]:
#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
Out[109]:
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
In [110]:
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

In [111]:
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
Out[111]:
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

In [112]:
#pearson entre frequence et birth #+ scatterplot
from scipy.stats import pearsonr 
pearsonr (dfFreqPart2['frequence'], dfFreqPart2['birth'])
Out[112]:
(-0.16536457412892014, 6.819828969679714e-54)

Le coefficient de pearson indique une correlation negative r= -0.16 p<0.01.

In [113]:
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
In [114]:
#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()
In [115]:
# 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
In [116]:
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.


In [117]:
#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
Out[117]:
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

In [118]:
#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
Out[118]:
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

In [119]:
#confirmation du resultat par calcul manuel sur client 1
df_cl_session.loc['c_1'].mean()
Out[119]:
price    16.914545
dtype: float64
In [120]:
# jonction : recuperation des ages.
df_cl_panier = pd.merge(df_cl_panier, customers , how = 'left', on = 'client_id' , indicator = True) 
df_cl_panier
Out[120]:
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

In [168]:
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
In [121]:
# version non parametrique
from scipy import stats
stats.spearmanr(df_cl_panier['panier_moyen'],df_cl_panier['birth'] )
Out[121]:
SpearmanrResult(correlation=0.7011233103707742, pvalue=0.0)
In [122]:
#version parametrique 
from scipy.stats import pearsonr 
pearsonr (df_cl_panier['panier_moyen'], df_cl_panier['birth'])
Out[122]:
(0.6168575055268313, 0.0)

coefficient de person : 0,616 P-value***

In [123]:
#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()
In [124]:
# 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
In [125]:
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)



- III.c Analyse de la variance ente l'age et la catégorie¶


In [174]:
lapage_particulier.head(3)
Out[174]:
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
In [175]:
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
In [176]:
#plt.title("droite de henry 'CA perso' ")
smi.qqplot(lapage_particulier['birth'], line = "r")
pylab.show()
In [177]:
#normalité et distribution des données 
stats.normaltest(lapage_particulier['birth'])
Out[177]:
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)

In [178]:
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.")
Out[178]:
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).

In [350]:
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
In [179]:
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
Out[179]:
[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]
In [180]:
#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. 
Out[180]:
KruskalResult(statistic=72209.48289794478, pvalue=0.0)
In [181]:
#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()
In [182]:
table = statsmodels.api.stats.anova_lm(res)
table
Out[182]:
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.

In [183]:
#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.
In [184]:
#visualisation par boxplot age categ
plt.figure(figsize = (12,9))#(- largeur, - de hauteur) 

sns.boxplot(data = lapage_particulier, x='categ' , y='birth' )
Out[184]:
<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).


- IV Conclusion¶


Preparation et analyses exploratoires¶

  • 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.

CA & Extraction de coefficients¶

  • 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

  • Les 3 axes nous montrent une relative regularité dans le nombre de ventes. Les categories les moins cheres concentrent plus de vente.
  • Le CA est repartie équitablement entre les H et F

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.

Etude sur le Chiffre d'affaire par client¶

  • Avant chaque analyse, l'observation de la normalité des données a été faite.
  • 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.

  • La visualisation par concentration du scatterplot en boxplot classé nous permet de voir plus précisément quels groupe est plus où moins concerné par un élément.
  • Age et CAperso

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)

  • Age et fréquence

SpearmanrResult(correlation=-0.21322431835262393, pvalue=3.454164915199389e-89)

Le coefficient de Pearson indique une correlation negative r= -0.16 p<0.01.

  • Age et panier moyen

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).

In [371]:
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'])
In [377]:
# 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()
  • Boxplot2 bleu,orange,vert : représentation du prix en fonction des categorie
In [378]:
#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')
  • Serie temporelle : une courbe représentant le CA sur ans avec moyenne glissante sur 7jours a été réalisé. On remarque que tout le mois d'octobre est vide # zonevide
In [379]:
plt.figure(figsize = (9,5))
plt.title('Representation du CA par categorie')
sns.lineplot(data = Dfnb_vente01, x = 'dateX' , y = 'value',hue = 'categ')
Out[379]:
<AxesSubplot:title={'center':'Representation du CA par categorie'}, xlabel='dateX', ylabel='value'>
  • Triples axes : flux constant de vente. Les categories les moins cheres générent plus de vente et de chiffre d'affaire.
In [385]:
#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é

In [380]:
#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) 
Out[380]:
<Figure size 648x360 with 0 Axes>
<Figure size 648x360 with 0 Axes>
In [384]:
plt.figure(figsize = (7,5))#(- largeur, - de hauteur) 

sns.boxplot(data = lapage_particulier, x='categ' , y='birth' )
Out[384]:
<AxesSubplot:xlabel='categ', ylabel='birth'>