Analyses exploratoire des données Lapage

Informations préliminaires :

Toutes les données sont déjà nettoyées. Une simple vérifications des doublons sera effectuées

Analyse des différents indicateurs de vente:
  • Différents indicateurs et graphiques autour du chiffre d'affaires
  • L’évolution dans le temps et mettre en place une décomposition en moyenne mobile pour évaluer la tendance globale
  • Zoom sur les références : les tops | flops, la répartition par catégorie, etc…
  • Informations sur les profils de nos clients, et également la répartition du chiffre d'affaires entre eux, via par exemple une courbe de Lorenz

  • Une analyse plus ciblée sur les clients : l’objectif est de comprendre le comportement de nos clients en ligne, pour pouvoir ensuite comparer avec la connaissance acquise via nos librairies physiques.
    5 corrélations :
  • le lien entre le genre d’un client et les catégories des livres achetés
  • le lien entre l'âge des clients et le montant total des achats
  • le lien entre l'âge des clients et la fréquence d’achat
  • le lien entre l'âge des clients et la paille du panier moyen
  • le lien entre l'âge des clients et la catégorie des livres achetés
  • Les bases¶

    • I. Importation des librairies
    • II. Fonctions
    • III. Importation des données

    Découverte des données¶

    • I. Données clients - Customers
    • II. Données produits - Products
    • III. Données de ventes - Transactions

    Analyse exploratoire¶

    • I. Création d'un tableau complet

    • II. Première hypothèse

    • III. Clés de performances (KPI sur les données BtoC)
      • a. Chiffre d'affaires
        • Évolution du chiffre d'affaires
        • Chiffre d'affaire du mois dernier
        • Évolution du nombre de clients
      • b. Nombre de livres vendus selon le jour
      • c. Nombre de livres vendus selon l'heure
      • d. Évolution du panier moyen
      • e. Évolution du nombre de sessions
      • f. Évolution du prix moyen du livre

    • IV. Références (sur les données BtoC)
      • a. Top / flop
        • Bestseller (top volume)
        • Rentable (top chiffre d'affaires)
        • Flop (volume)
        • Flop (chiffre d'affaires)
        • Ventes nulles (sur la dernière années)
        • Répartition des ventes selon le product_id (courbe de Lorenz, loi de Pareto, indice de Gini)
        • Top 20% des références sur lesquelles il faut se concentrer (78% du chiffre d'affaires)
      • b. Tranche de prix des livres les plus vendus
      • c. Proportion de chiffre d'affaires par categories
        • Proportion du CA des catégories sur l'ensemble du temps
        • Évolution du CA des différentes catégories
      • d. Représentation des prix par catégories
        • Visualisation des prix par catégorie
        • Prix moyen des catégorie sur toute la période de temps
        • L'évolution du prix moyen du livre sur chaque catégorie sur toute la période de temps

    • V. Profil clients (BtoC)
      • a. Profilage clients basique
        • Répartition du chiffre d'affaires par les clients (courbe de Lorenz)
        • Ratio de parité de la clientèle
        • Parité similaire sur les 3 branches (population | CA | Nombre de commandes) ?
        • Informations globales selon le sexe
        • Tranche d'âge qui achète le plus
        • Nombre de clients par âge
      • b. Segmentation RFM
        • Préparation du dataframe initial
        • R. (Récent) Création du dataframe sur les achats les plus récents
        • F. (Fréquence) Création du dataframe sur les sessions les plus fréquentes par client
        • M. (Montant) Création du dataframe de classement des plus gros chiffre d'affaires réalisé par client
        • Création du dataframe RFM
        • Ciblage des clients selon la segmentation RFM Eleven
        • Synthèse du classement RFM

    • VI. Demandes de Julie - 5 corrélations
      • a. Lien entre le genre d'un client et les catégories de livres achetés
        • Test Chi2 de contingence (test d'association entre 2 variables qualitatives)
        • Test de Cramer-V (connaitre l'intensité de cette association)
      • b. Lien entre l'âge des clients et le montant total des achats
        • Visualisation des variables
        • Visualisation par tranches d'âges
        • Quelle loi suivent les variables
        • Test de Spearman (test de corrélation entre 2 variables quantitatives)
      • c. Lien entre l'âge des clients et la fréquence d’achat
        • Visualisation des variables
        • Test de Spearman (test de corrélation entre 2 variables quantitatives)
      • d. Lien entre l'âge des clients et la taille du panier moyen
        • Visualisation des variables
        • Test de Spearman (test de corrélation entre 2 variables quantitatives)
        • Nouveau test de Spearman sans le groupe de jeune (19-32 ans)
      • e. Lien entre l'âge des clients et la catégorie des livres achetés
        • Visualisation des variables
        • Test ANOVA (test d'association entre 3 groupes d'une variables qualitatives et d'une variable quantitative)
        • Hypothèse de validation du test
        • Indépendance des variables
        • Test de Lavene (égalité des variances entre plusieurs groupes)
        • Test final de Kruskal-Wallis (test ANOVA invalidé car les variances sont inégales)

    • VI. Optionnel pour la présentation powerpoint

    Les bases¶

    Importation des librairies¶

    In [ ]:
    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    import seaborn as sns
    from matplotlib.patches import Rectangle
    import scipy.stats as st
    import datetime as dt
    from math import *
    from statsmodels.formula.api import ols
    import re
    
    import warnings
    warnings.simplefilter("ignore")
    

    Fonctions¶

    In [ ]:
    def regress_lin(x,y):
        """Renvoie la régression linéaire des deux variables quantitatives renseignées"""
        slope, intercept, r_value, p_value, std_er = st.linregress(x,y)
    
        return plt.plot(x, (slope * x + intercept), c='r')
    
    
    def Ranking(df, column_critere, column_result):
        """
        ---
        Utilisation pour la segmentation RFM
        ---
        Système de ranking par rapport à 5 quintiles [0:1]
        
        df = le dataframe sur lequel l'on travail
        column_critere = colonne sur laquelle on se base pour la comparaison
        column_result = colonne sur laquelle on vient afficher le rang
        """
        df[column_result] = 0
    
        q = 0
        rank = 1
    
        for quintile in range(5):
            q_next = q + 0.2
    
            date = np.quantile(df[column_critere], q)
            date_next = np.quantile(df[column_critere], q_next)
    
            df.loc[(df[column_critere] >= date) & (df[column_critere] <= date_next),column_result] = rank
    
            rank += 1
            q += 0.2
    

    Importation des données¶

    In [ ]:
    #Import des informations clients
    df_customer = pd.read_csv("BDD/customers.csv", sep=";")
    
    In [ ]:
    #Import des informations produits
    df_product = pd.read_csv("BDD/products.csv", sep=";")
    
    In [ ]:
    #Import des informations de ventes
    df_transaction = pd.read_csv("BDD/Transactions.csv", sep=";")
    

    Découverte des données¶

    Données clients - Customers¶

    In [ ]:
    df_customer.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 8621 entries, 0 to 8620
    Data columns (total 3 columns):
     #   Column     Non-Null Count  Dtype 
    ---  ------     --------------  ----- 
     0   client_id  8621 non-null   object
     1   sex        8621 non-null   object
     2   birth      8621 non-null   int64 
    dtypes: int64(1), object(2)
    memory usage: 202.2+ KB
    
    In [ ]:
    #Création d'une colonne age afin de simplifier les analyses
    df_customer["age"] =  2023 - df_customer["birth"]
    
    In [ ]:
    #Suppression de la colonne de naissance devenue inutile
    df_customer.drop(columns={"birth"}, inplace=True)
    
    In [ ]:
    #Vérification des doublons du la clé primaire
    df_customer["client_id"].duplicated().sum()
    
    Out[ ]:
    0

    Il y a 8621 clients enregistrés

    In [ ]:
    #Combien de sexes différents ?
    df_customer["sex"].unique()
    
    Out[ ]:
    array(['f', 'm'], dtype=object)

    Sexes représentés : Femme ("f") & Homme ("m")

    In [ ]:
    df_customer.head()
    
    Out[ ]:
    client_id sex age
    0 c_4410 f 56
    1 c_7839 f 48
    2 c_1699 f 39
    3 c_5961 f 61
    4 c_5320 m 80

    Données produits - Products¶

    In [ ]:
    df_product.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 3286 entries, 0 to 3285
    Data columns (total 3 columns):
     #   Column   Non-Null Count  Dtype  
    ---  ------   --------------  -----  
     0   id_prod  3286 non-null   object 
     1   price    3286 non-null   float64
     2   categ    3286 non-null   int64  
    dtypes: float64(1), int64(1), object(1)
    memory usage: 77.1+ KB
    
    In [ ]:
    #Vérification des doublons des références produits
    df_product["id_prod"].duplicated().sum()
    
    Out[ ]:
    0

    Il y a 3286 produits différents

    In [ ]:
    #Quelle est l'échelle du prix
    df_product["price"].describe().round(2)
    
    Out[ ]:
    count    3286.00
    mean       21.86
    std        29.85
    min         0.62
    25%         6.99
    50%        13.08
    75%        22.99
    max       300.00
    Name: price, dtype: float64
    In [ ]:
    plt.title("Distribution des prix")
    plt.hist(df_product["price"]);
    
    plt.xlabel("Prix unitaire")
    
    Out[ ]:
    Text(0.5, 0, 'Prix unitaire')
    In [ ]:
    #Combien et quelles sont les catégories
    df_product["categ"].unique()
    
    Out[ ]:
    array([0, 1, 2], dtype=int64)

    Il y a 3 catégories: 0 , 1 et 2

    Données de ventes - Transactions¶

    In [ ]:
    df_transaction.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 687534 entries, 0 to 687533
    Data columns (total 4 columns):
     #   Column      Non-Null Count   Dtype 
    ---  ------      --------------   ----- 
     0   id_prod     687534 non-null  object
     1   date        687534 non-null  object
     2   session_id  687534 non-null  object
     3   client_id   687534 non-null  object
    dtypes: object(4)
    memory usage: 21.0+ MB
    

    Premières notifications:

    • Variable date au format Object -> passage au format Date
    • Des dates sont erronées (format (+) de 24h -> [0:24] au lieu de [0:23]) -> constat fait après un premier essai de conversion
    • Ajouter une variable mensuelle (AAAA-MM) afin de simplifier certaines analyses
    • Déterminer ce que signifie session_id -> hypothèse: session d'achat d'un client ( 1 session -> 1 client | 1 client -> * sessions )
    In [ ]:
    #Correction des dates à 24h -> 00h et + 1j 
    for date in df_transaction["date"]:
    
        if (date[11] == "2") & (date[12] == "4"):
            date_split = re.split("[-: ]", date) #divise la chaine de caractères par les "-", ":", " "
            date_split[3] = "00" #changement des 24h en 00h
    
            #Incrémentation d'un jour
            date_split[2] = int(date_split[2]) + 1
            if date_split[2] < 10 :
                date_split[2] = "0" + str(date_split[2])
            else: date_split[2] = str(date_split[2])
    
            jour = ("-").join(date_split[:3])
            heure = (":").join(date_split[3:])
            date_split = jour + " " + heure
    
            df_transaction.loc[df_transaction["date"] == date,["date"]] = date_split
    
    In [ ]:
    #Correction de la variable date au bon format
    df_transaction["date"] = pd.to_datetime(df_transaction["date"])
    
    #Récupération des données necessaire de la date (tout sauf les milisecondes)
    df_transaction["date"] = pd.to_datetime(df_transaction["date"].dt.strftime("%Y-%m-%d %H:%M:%S"))
    
    In [ ]:
    df_transaction.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 687534 entries, 0 to 687533
    Data columns (total 4 columns):
     #   Column      Non-Null Count   Dtype         
    ---  ------      --------------   -----         
     0   id_prod     687534 non-null  object        
     1   date        687534 non-null  datetime64[ns]
     2   session_id  687534 non-null  object        
     3   client_id   687534 non-null  object        
    dtypes: datetime64[ns](1), object(3)
    memory usage: 21.0+ MB
    
    In [ ]:
    #Création de la colonne mensuelle
    df_transaction["monthly"] = pd.to_datetime(df_transaction["date"].dt.strftime('%Y-%m'))
    
    In [ ]:
    #Nombre de clients uniques représentés dans ce fichier
    df_transaction["client_id"].unique().__len__()
    
    Out[ ]:
    8600
    In [ ]:
    #Nombre de produits uniques représentés dans ce fichier
    df_transaction["id_prod"].unique().__len__()
    
    Out[ ]:
    3265
    In [ ]:
    #Nombre de sessions uniques représentés dans ce fichier
    df_transaction["session_id"].unique().__len__()
    
    Out[ ]:
    345505

    Il y a beaucoup plus de transactions que de clients.
    Est-ce que la variable "session_id" correspond à plusieurs achats d'un seul client?

    In [ ]:
    #Tri des sessions
    df_transaction.sort_values(by="session_id", ascending=True).head(10)
    
    Out[ ]:
    id_prod date session_id client_id monthly
    0 0_1259 2021-03-01 00:01:07 s_1 c_329 2021-03-01
    10 1_635 2021-03-01 00:10:33 s_10 c_2218 2021-03-01
    205 0_1451 2021-03-01 04:43:58 s_100 c_3854 2021-03-01
    181 0_1030 2021-03-01 04:12:43 s_100 c_3854 2021-03-01
    1989 0_1590 2021-03-03 02:49:03 s_1000 c_1014 2021-03-01
    2021 0_1449 2021-03-03 03:18:58 s_1000 c_1014 2021-03-01
    2029 0_1438 2021-03-03 03:25:12 s_1000 c_1014 2021-03-01
    1984 0_1625 2021-03-03 02:38:09 s_1000 c_1014 2021-03-01
    20203 1_395 2021-03-22 17:46:05 s_10000 c_476 2021-03-01
    20211 0_1324 2021-03-22 17:58:20 s_10000 c_476 2021-03-01

    Un n° de session possède bien exclusivement qu'un n° de client.

    Analyse exploratoire¶

    Création d'un tableau complet¶

    In [ ]:
    df_customer.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 8621 entries, 0 to 8620
    Data columns (total 3 columns):
     #   Column     Non-Null Count  Dtype 
    ---  ------     --------------  ----- 
     0   client_id  8621 non-null   object
     1   sex        8621 non-null   object
     2   age        8621 non-null   int64 
    dtypes: int64(1), object(2)
    memory usage: 202.2+ KB
    
    In [ ]:
    df_product.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 3286 entries, 0 to 3285
    Data columns (total 3 columns):
     #   Column   Non-Null Count  Dtype  
    ---  ------   --------------  -----  
     0   id_prod  3286 non-null   object 
     1   price    3286 non-null   float64
     2   categ    3286 non-null   int64  
    dtypes: float64(1), int64(1), object(1)
    memory usage: 77.1+ KB
    
    In [ ]:
    df_transaction.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 687534 entries, 0 to 687533
    Data columns (total 5 columns):
     #   Column      Non-Null Count   Dtype         
    ---  ------      --------------   -----         
     0   id_prod     687534 non-null  object        
     1   date        687534 non-null  datetime64[ns]
     2   session_id  687534 non-null  object        
     3   client_id   687534 non-null  object        
     4   monthly     687534 non-null  datetime64[ns]
    dtypes: datetime64[ns](2), object(3)
    memory usage: 26.2+ MB
    
    In [ ]:
    #Inner join entre les transactions et les produits
    df_temp = df_transaction.merge(df_product, on="id_prod")
    
    In [ ]:
    #Récupération des données clients avec un inner join entre le future dataframe complet et les données clients
    df_complet = df_temp.merge(df_customer, on="client_id")
    
    In [ ]:
    df_complet.head()
    
    Out[ ]:
    id_prod date session_id client_id monthly price categ sex age
    0 0_1259 2021-03-01 00:01:07 s_1 c_329 2021-03-01 11.99 0 f 56
    1 0_1259 2022-10-01 00:01:07 s_275943 c_329 2022-10-01 11.99 0 f 56
    2 0_1259 2022-12-01 00:01:07 s_305291 c_329 2022-12-01 11.99 0 f 56
    3 0_1259 2023-01-01 00:01:07 s_320153 c_329 2023-01-01 11.99 0 f 56
    4 1_397 2021-11-23 18:21:56 s_123998 c_329 2021-11-01 18.99 1 f 56
    In [ ]:
    #Arrangement des colonnes
    df_complet = df_complet[["date", "monthly", "session_id", "id_prod", "price", "categ", "client_id", "age", "sex"]]
    
    In [ ]:
    df_complet.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 687534 entries, 0 to 687533
    Data columns (total 9 columns):
     #   Column      Non-Null Count   Dtype         
    ---  ------      --------------   -----         
     0   date        687534 non-null  datetime64[ns]
     1   monthly     687534 non-null  datetime64[ns]
     2   session_id  687534 non-null  object        
     3   id_prod     687534 non-null  object        
     4   price       687534 non-null  float64       
     5   categ       687534 non-null  int64         
     6   client_id   687534 non-null  object        
     7   age         687534 non-null  int64         
     8   sex         687534 non-null  object        
    dtypes: datetime64[ns](2), float64(1), int64(2), object(4)
    memory usage: 47.2+ MB
    
    In [ ]:
    df_complet.describe()
    
    Out[ ]:
    date monthly price categ age
    count 687534 687534 687534.000000 687534.000000 687534.000000
    mean 2022-03-01 21:24:01.353685504 2022-02-14 16:12:28.721081344 17.493918 0.448789 45.182609
    min 2021-03-01 00:01:07 2021-03-01 00:00:00 0.620000 0.000000 19.000000
    25% 2021-09-10 10:35:20 2021-09-01 00:00:00 8.990000 0.000000 36.000000
    50% 2022-02-27 06:50:25 2022-02-01 00:00:00 13.990000 0.000000 43.000000
    75% 2022-08-28 22:16:49 2022-08-01 00:00:00 19.080000 1.000000 53.000000
    max 2023-02-28 23:58:30 2023-02-01 00:00:00 300.000000 2.000000 94.000000
    std NaN NaN 18.238337 0.594563 13.607935
    In [ ]:
    #Calcul du C.A total
    ca_total = df_complet["price"].sum().round(0)
    
    ca_total
    
    Out[ ]:
    12027663.0

    Chiffre d'affaires total : 12'027'663 €

    Première hypothèse :¶

    • Il y a t-il des clients B2B ? (impacte important sur le C.A)
    In [ ]:
    #Création d'un tabeau avec les achats réalisés par chaque client
    df_totalca_client = df_complet.groupby("client_id")["price"].agg(total_client="sum").reset_index()
    
    In [ ]:
    #Tri des paniers dans l'ordre décroissant
    df_totalca_client.sort_values(by="total_client", ascending=False, inplace=True)
    
    In [ ]:
    df_totalca_client = df_totalca_client.reset_index()
    
    In [ ]:
    plt.title("Panier total par client")
    plt.boxplot(df_totalca_client["total_client"]);
    plt.ylabel("Panier (en €)")
    
    Out[ ]:
    Text(0, 0.5, 'Panier (en €)')
    In [ ]:
    df_totalca_client.head(6)
    
    Out[ ]:
    index client_id total_client
    0 677 c_1609 326039.89
    1 4388 c_4958 290227.03
    2 6337 c_6714 153918.60
    3 2724 c_3454 114110.57
    4 634 c_1570 5285.82
    5 2513 c_3263 5276.87

    Il semblerai qu'il y ai plusieurs clients avec un panier total largement supérieur aux autres.
    Utiliser l'Inter Quartile Range pour cibler ces clients semblent inutiles car les 4 premiers se détachent très clairement.

    In [ ]:
    #Récupération de la liste des clients B2B
    list_client_btob = df_totalca_client.iloc[0:4]["client_id"]
    
    list_client_btob
    
    Out[ ]:
    0    c_1609
    1    c_4958
    2    c_6714
    3    c_3454
    Name: client_id, dtype: object
    In [ ]:
    #Création d'un data frame pour les B2B (professionnels)
    df_btob = df_complet.loc[df_complet["client_id"].isin(list_client_btob)]
    df_btob.info()
    
    <class 'pandas.core.frame.DataFrame'>
    Index: 46800 entries, 84 to 103787
    Data columns (total 9 columns):
     #   Column      Non-Null Count  Dtype         
    ---  ------      --------------  -----         
     0   date        46800 non-null  datetime64[ns]
     1   monthly     46800 non-null  datetime64[ns]
     2   session_id  46800 non-null  object        
     3   id_prod     46800 non-null  object        
     4   price       46800 non-null  float64       
     5   categ       46800 non-null  int64         
     6   client_id   46800 non-null  object        
     7   age         46800 non-null  int64         
     8   sex         46800 non-null  object        
    dtypes: datetime64[ns](2), float64(1), int64(2), object(4)
    memory usage: 3.6+ MB
    
    In [ ]:
    #CA total client BtoB
    ca_btob = df_btob["price"].sum()
    ca_btob
    
    Out[ ]:
    884296.0900000001
    In [ ]:
    #Nombre total de livre
    qte_livre_btob = df_btob["price"].count()
    qte_livre_btob
    
    Out[ ]:
    46800
    In [ ]:
    prix_moy = round(ca_btob / qte_livre_btob, 2)
    prix_moy
    
    Out[ ]:
    18.9
    In [ ]:
    #distribution des prix
    sns.histplot(data=df_btob, x="price", kde=True)
    
    Out[ ]:
    <Axes: xlabel='price', ylabel='Count'>
    In [ ]:
    df_btob.describe()
    
    Out[ ]:
    date monthly price categ age
    count 46800 46800 46800.000000 46800.000000 46800.000000
    mean 2022-03-02 17:45:08.661089536 2022-02-15 13:11:08.307692288 18.895216 0.475577 44.835321
    min 2021-03-01 00:07:04 2021-03-01 00:00:00 0.620000 0.000000 24.000000
    25% 2021-09-09 10:13:16 2021-09-01 00:00:00 8.990000 0.000000 43.000000
    50% 2022-03-01 16:13:04.500000 2022-03-01 00:00:00 13.990000 0.000000 43.000000
    75% 2022-08-29 16:19:28 2022-08-01 00:00:00 19.530000 1.000000 54.000000
    max 2023-02-28 23:16:12 2023-02-01 00:00:00 300.000000 2.000000 55.000000
    std NaN NaN 21.144730 0.636203 9.098456
    In [ ]:
    #Création d'un data frame représentant les clients B2C (particuliers)
    df_btoc = df_complet.set_index("client_id").drop(list_client_btob).reset_index()
    df_btoc.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 640734 entries, 0 to 640733
    Data columns (total 9 columns):
     #   Column      Non-Null Count   Dtype         
    ---  ------      --------------   -----         
     0   client_id   640734 non-null  object        
     1   date        640734 non-null  datetime64[ns]
     2   monthly     640734 non-null  datetime64[ns]
     3   session_id  640734 non-null  object        
     4   id_prod     640734 non-null  object        
     5   price       640734 non-null  float64       
     6   categ       640734 non-null  int64         
     7   age         640734 non-null  int64         
     8   sex         640734 non-null  object        
    dtypes: datetime64[ns](2), float64(1), int64(2), object(4)
    memory usage: 44.0+ MB
    

    Clés de performances (KPI sur les données BtoC) :¶

    • Chiffre d'affaires
    • Nombre de livres vendus journellement
    • Nombre de livres vendus par heure
    • Évolution du panier moyen
    • Évolution du nombre de sessions
    • Évolution du prix moyen du livre

    Chiffre d'affaires¶

    Évolution du chiffre d'affaires¶
    In [ ]:
    #Visualisation du CA B2C
    df_btoc.set_index("date")["price"].resample("m").sum().plot(label="Chiffre d'affaires");
    
    #Visualisation de la moyenne mobile par semestre
    df_btoc.set_index("monthly")["price"].resample("m").sum().rolling(6).mean().plot(label="Moyenne mobile (semestre)");
    
    plt.title("Évolution du chiffre d'affaires");
    plt.xlabel("Date")
    plt.ylabel("Chiffre d'affaires (en €)")
    plt.legend()
    plt.grid(which="both")
    
    Chiffre d'affaires du dernier mois¶
    In [ ]:
    #Détermination exacte du CA pour le dernier mois
    df_temp = df_btoc.groupby("monthly")["price"].agg(ca_total="sum").reset_index()
    
    ca_total_dernier_mois = df_temp.loc[df_temp["monthly"] == df_temp["monthly"].max()]["ca_total"].item()
    ca_total_dernier_mois
    
    Out[ ]:
    421485.44
    In [ ]:
    #Détermination exacte du CA pour fevrier n-1
    df_temp = df_btoc.groupby("monthly")["price"].agg(ca_total="sum").reset_index()
    
    ca_total_dernier_mois_n1 = df_temp.loc[df_temp["monthly"] == "2022-02-01"]["ca_total"].item()
    ca_total_dernier_mois_n1
    
    Out[ ]:
    492927.13

    Conclusions :

    • Saisonnalité cohérente pour l'achats de livres:(-) Vacances scolaires estivales | (+) Rentrée scolaire | (+) Noël & Janvier (bons cadeaux Noël utilisés)
    • Forte perte en Février 2023 ~ 40'000€ (C.A estimé selon moyenne mobile: 460'000€ vs réalisé: 420'000€)
    Évolution du nombre de client¶
    In [ ]:
    df_temp = df_btoc.groupby(["monthly", "client_id"]).agg(nb_livres=("id_prod","count")).reset_index()
    df_temp = df_temp.groupby("monthly").agg(nb_clients=("nb_livres","count")).reset_index()
    
    df_temp.set_index("monthly")["nb_clients"].plot(kind="line")
    plt.grid(which="both")
    
    In [ ]:
    nb_client_derniermois = df_temp.loc[df_temp["monthly"] == "2023-02-01"]["nb_clients"].item()
    nb_client_derniermois
    
    Out[ ]:
    5583
    In [ ]:
    nb_client_derniermois_n1 = df_temp.loc[df_temp["monthly"] == "2022-02-01"]["nb_clients"].item()
    nb_client_derniermois_n1 
    
    Out[ ]:
    5725

    Nombre de livres vendus selon le jour¶

    In [ ]:
    df_time = df_btoc
    
    In [ ]:
    #Création de la colonne des jours
    df_time["jour"] = df_time['date'].dt.strftime("%A")
    
    In [ ]:
    df_time.head()
    
    Out[ ]:
    client_id date monthly session_id id_prod price categ age sex jour
    0 c_329 2021-03-01 00:01:07 2021-03-01 s_1 0_1259 11.99 0 56 f Monday
    1 c_329 2022-10-01 00:01:07 2022-10-01 s_275943 0_1259 11.99 0 56 f Saturday
    2 c_329 2022-12-01 00:01:07 2022-12-01 s_305291 0_1259 11.99 0 56 f Thursday
    3 c_329 2023-01-01 00:01:07 2023-01-01 s_320153 0_1259 11.99 0 56 f Sunday
    4 c_329 2021-11-23 18:21:56 2021-11-01 s_123998 1_397 18.99 1 56 f Tuesday
    In [ ]:
    df_temp = df_time.groupby("jour").agg(total_livre=("price", "count")).reset_index()
    df_temp
    
    Out[ ]:
    jour total_livre
    0 Friday 90370
    1 Monday 92212
    2 Saturday 90776
    3 Sunday 92116
    4 Thursday 90838
    5 Tuesday 92669
    6 Wednesday 91753
    In [ ]:
    sns.barplot(data=df_temp, x="jour", y="total_livre")
    
    Out[ ]:
    <Axes: xlabel='jour', ylabel='total_livre'>

    Conclusion :

    • Le chiffre d'affaires est similaire pour tous les jours de la semaine.

    Nombre de livres vendus selon l'heure¶

    In [ ]:
    df_time["heure"] = pd.to_timedelta(df_transaction['date'].dt.strftime("%H:%M:%S"))
    
    In [ ]:
    heures_vente = []
    for date in df_time["date"]:
        date_splited = re.split("[: ]", str(date))
        heures = date_splited[1]
    
        heures_vente.append(heures)
    
    In [ ]:
    df_time["heure"] = heures_vente
    
    In [ ]:
    df_time
    
    Out[ ]:
    client_id date monthly session_id id_prod price categ age sex jour heure
    0 c_329 2021-03-01 00:01:07 2021-03-01 s_1 0_1259 11.99 0 56 f Monday 00
    1 c_329 2022-10-01 00:01:07 2022-10-01 s_275943 0_1259 11.99 0 56 f Saturday 00
    2 c_329 2022-12-01 00:01:07 2022-12-01 s_305291 0_1259 11.99 0 56 f Thursday 00
    3 c_329 2023-01-01 00:01:07 2023-01-01 s_320153 0_1259 11.99 0 56 f Sunday 00
    4 c_329 2021-11-23 18:21:56 2021-11-01 s_123998 1_397 18.99 1 56 f Tuesday 18
    ... ... ... ... ... ... ... ... ... ... ... ...
    640729 c_7739 2022-08-28 16:51:07 2022-08-01 s_259828 2_163 68.99 2 26 m Sunday 16
    640730 c_7739 2022-10-28 16:51:07 2022-10-01 s_289331 2_163 68.99 2 26 m Friday 16
    640731 c_712 2021-12-18 20:54:25 2021-12-01 s_136405 1_64 19.81 1 56 f Saturday 20
    640732 c_712 2022-10-18 20:54:25 2022-10-01 s_284469 1_64 19.81 1 56 f Tuesday 20
    640733 c_4478 2021-09-15 19:42:08 2021-09-01 s_90430 0_1692 13.36 0 53 f Wednesday 19

    640734 rows × 11 columns

    In [ ]:
    #Création d'un dataframe pour récupérer le nombre de livres par heures et par mois
    df_temp = df_time.groupby(["monthly", "heure"]).agg(nb_livres=("price","count")).reset_index()
    
    #Création d'un dataframe récupérant le nombre de livre moyen par heures 
    df_temp = df_temp.groupby("heure").agg(nbmoy_livre=("nb_livres","mean")).reset_index()
    df_temp.head()
    
    Out[ ]:
    heure nbmoy_livre
    0 00 1108.625000
    1 01 1111.583333
    2 02 1120.250000
    3 03 1095.541667
    4 04 1093.583333
    In [ ]:
    xmax = df_temp.loc[df_temp["nbmoy_livre"] == df_temp["nbmoy_livre"].max()].index.item()
    ymax = df_temp["nbmoy_livre"].max()
    
    xmin = df_temp.loc[df_temp["nbmoy_livre"] == df_temp["nbmoy_livre"].min()].index.item()
    ymin = df_temp["nbmoy_livre"].min()
    
    In [ ]:
    sns.lineplot(data=df_temp)
    
    plt.title("Évolution du nombre moyen de livres vendus par heure sur une journée")
    plt.grid(which="both")
    plt.ylabel("Nombre moyen de livres vendus")
    plt.xlabel("Heures de la journée")
    
    plt.text(xmax+1, ymax-2, "Maximum : {}\n{}h00".format(int(ymax), xmax))
    plt.text(xmin+1, ymin, "Minimum : {}\n{}h00".format(int(ymin), xmin))
    
    plt.plot(xmax,ymax,'go')
    plt.plot(xmin,ymin,'ro')
    
    plt.legend()
    plt.show()
    

    Conclusion :

    • La quantités de livres vendus dans la journée semble assez cyclique :
      • chute vers 4h du matin
      • pique élevé vers 9h du matin
      • chute vers 15h
      • pique vers 18h
      • chute vers 22h
    • Différence d'environ 40 livres vendus (1133-1091) entre l'heure avec le plus de vente (~9h00) et l'heure avec le moins de vente (~5h00)

    Évolution du panier moyen (attention -> 45 sessions sont sur 2 mois)¶

    ex: session_id(s_158089) -> 2022-02-01 00:18:14 & 2022-01-31 23:55:49

    In [ ]:
    #Création du data frame avec le CA par session
    df_session = df_btoc.groupby(["monthly", "session_id"])["price"].agg(total_ca="sum").reset_index()
    
    In [ ]:
    #Affichage des sessions à cheval sur 2 mois
    #df_session[df_session.duplicated(subset="session_id")]
    
    In [ ]:
    #Visualisation de l'évolution du panier moyen
    df_session.set_index("monthly")["total_ca"].resample("m").mean().plot(label="Panier moyen")
    #Visualisation de la moyenne mobile en semestre
    df_session.set_index("monthly")["total_ca"].resample("m").mean().rolling(6).mean().plot(label="Moyenne mobile (semestre)")
    
    plt.title("Évolution du panier moyen par session")
    plt.xlabel("Date")
    plt.ylabel("Panier moyen (en €)")
    plt.legend()
    plt.grid()
    
    In [ ]:
    panier_moy_derniermois = df_session.loc[df_session["monthly"] == "2023-02-01"]["total_ca"].mean().item()
    panier_moy_derniermois
    
    Out[ ]:
    34.61324135665599
    In [ ]:
    panier_moy_derniermois_n1 = df_session.loc[df_session["monthly"] == "2022-02-01"]["total_ca"].mean().item()
    panier_moy_derniermois_n1 
    
    Out[ ]:
    36.90403009657857

    Conclusion :

    • Panier moyen par session stable, notament ce dernier mois -> hypothèse: moins de sessions
    • Pic important en Janvier / Février 2022 (cartes cadeaux + fond propre utilisés?)

    Évolution du nombre de sessions¶

    In [ ]:
    #Création du data frame avec le nombre de session par mois
    df_nbsession = df_btoc.groupby(["monthly", "session_id"])["price"].sum().reset_index()
    df_nbsession = df_nbsession.groupby("monthly").agg(nb_session=("session_id","count")).reset_index()
    
    In [ ]:
    #Visualisation de l'évolution du nombre de session par mois
    df_nbsession.set_index("monthly")["nb_session"].plot(label="Session")
    #Visualisation de la moyenne mobile semestrielle
    df_nbsession.set_index("monthly")["nb_session"].rolling(6).mean().plot(label="Moyenne mobile (semestre)")
    
    plt.title("Évolution du nombre de sessions")
    plt.xlabel("Date")
    plt.ylabel("Nombre de sessions")
    plt.legend()
    plt.grid()
    
    In [ ]:
    nbsession_derniermois = df_nbsession.loc[df_nbsession["monthly"] == "2023-02-01"]["nb_session"].item()
    nbsession_derniermois
    
    Out[ ]:
    12177
    In [ ]:
    nbsession_derniermois_n1 = df_nbsession.loc[df_nbsession["monthly"] == "2022-02-01"]["nb_session"].item()
    nbsession_derniermois_n1
    
    Out[ ]:
    13357

    Conclusion :

    • C'est le nombre de session en baisse qui est la cause de la chute du C.A en Février 2023
    • Il serait interessant d'avoir les visites sans actes d'achats pour déterminer un taux de conversion

    Évolution du prix moyen du livre¶

    In [ ]:
    df_btoc.set_index("monthly")["price"].resample("m").mean().plot()
    df_btoc.set_index("monthly")["price"].resample("m").mean().rolling(6).mean().plot()
    
    Out[ ]:
    <Axes: xlabel='monthly'>
    In [ ]:
    df_prix_moy = df_btoc.groupby(["monthly"]).agg(prix_moy=("price","mean")).reset_index()
    df_prix_moy
    
    Out[ ]:
    monthly prix_moy
    0 2021-03-01 16.734293
    1 2021-04-01 16.597577
    2 2021-05-01 17.314535
    3 2021-06-01 17.856944
    4 2021-07-01 19.397320
    5 2021-08-01 18.618339
    6 2021-09-01 15.203287
    7 2021-10-01 16.508220
    8 2021-11-01 18.165283
    9 2021-12-01 16.145505
    10 2022-01-01 17.829373
    11 2022-02-01 17.879761
    12 2022-03-01 17.216505
    13 2022-04-01 17.822833
    14 2022-05-01 17.147356
    15 2022-06-01 17.270697
    16 2022-07-01 17.722813
    17 2022-08-01 17.640754
    18 2022-09-01 17.399315
    19 2022-10-01 17.428574
    20 2022-11-01 17.243644
    21 2022-12-01 17.741687
    22 2023-01-01 17.820219
    23 2023-02-01 17.729586
    In [ ]:
    prixmoy_derniermois = df_prix_moy.loc[df_prix_moy["monthly"] == "2023-02-01"]["prix_moy"].item()
    prixmoy_derniermois
    
    Out[ ]:
    17.729585664409203
    In [ ]:
    prixmoy_derniermois_n1 = df_prix_moy.loc[df_prix_moy["monthly"] == "2022-02-01"]["prix_moy"].item()
    prixmoy_derniermois_n1
    
    Out[ ]:
    17.879760963400923

    Références (sur les données BtoC) :¶

    • Top / Flop
    • Tranche de prix des livres les plus vendus
    • Proportion de CA des categories
    • Représentation des prix par catégories

    Top / Flop¶

    In [ ]:
    #Création du dataframe aggrégé sur les produits
    df_temp = df_btoc.groupby("id_prod")["price"].agg(total_book="count", total_ca="sum").reset_index()
    
    Bestseller (Top volume)¶
    In [ ]:
    df_bestseller = df_temp.sort_values("total_book", ascending=False)
    
    df_bestseller.head(5)
    
    Out[ ]:
    id_prod total_book total_ca
    2589 1_369 2205 52897.95
    2642 1_417 2123 44561.77
    2639 1_414 2114 50376.62
    2731 1_498 2042 47721.54
    2651 1_425 2026 34421.74
    In [ ]:
    reference_bestseller = df_bestseller.iloc[0:10,0].tolist()
    reference_bestseller
    
    Out[ ]:
    ['1_369',
     '1_417',
     '1_414',
     '1_498',
     '1_425',
     '1_413',
     '1_412',
     '1_407',
     '1_396',
     '1_403']
    Rentable (Top chiffre d'affaires)¶
    In [ ]:
    df_rentable = df_temp.sort_values("total_ca", ascending=False)
    
    df_rentable.head(10)
    
    Out[ ]:
    id_prod total_book total_ca
    3093 2_159 624 91097.76
    3067 2_135 920 63470.80
    3043 2_112 870 58785.90
    3032 2_102 941 55650.74
    2589 1_369 2205 52897.95
    2617 1_395 1800 52182.00
    3149 2_209 725 50742.75
    2639 1_414 2114 50376.62
    2605 1_383 1716 49746.84
    3101 2_166 210 48308.40
    In [ ]:
    reference_rentable = df_rentable.iloc[0:10,0].tolist()
    reference_rentable
    
    Out[ ]:
    ['2_159',
     '2_135',
     '2_112',
     '2_102',
     '1_369',
     '1_395',
     '2_209',
     '1_414',
     '1_383',
     '2_166']
    Flop (volume)¶
    In [ ]:
    df_flop = df_temp.sort_values(["total_book", "total_ca"], ascending=True)
    
    df_flop.head(5)
    
    Out[ ]:
    id_prod total_book total_ca
    209 0_1191 1 0.99
    594 0_1539 1 0.99
    312 0_1284 1 1.38
    799 0_1726 1 1.57
    664 0_1601 1 1.99
    Flop (chiffre d'affaires)¶
    In [ ]:
    df_inutile = df_temp.sort_values("total_ca", ascending=True)
    
    df_inutile.head(5)
    
    Out[ ]:
    id_prod total_book total_ca
    594 0_1539 1 0.99
    209 0_1191 1 0.99
    312 0_1284 1 1.38
    799 0_1726 1 1.57
    718 0_1653 2 1.98
    Ventes nulles (sur la dernière années)¶
    In [ ]:
    #Récupération des références vendues sur toute la période
    prod_fulltime = df_btoc["id_prod"].unique().tolist()
    
    #Récupération des références vendues sur seulement la denrièere année 
    prod_last_annee = df_btoc.loc[df_btoc["monthly"] >= "2022-02-01"]["id_prod"].unique().tolist()
    
    In [ ]:
    ventes_nulles = list(set(prod_fulltime) - set(prod_last_annee))
    
    len(ventes_nulles)
    
    Out[ ]:
    43
    In [ ]:
    #Création du dataframe des références invendues sur la dernière année
    df_ss_vente = df_product.loc[df_product["id_prod"].isin(ventes_nulles)]
    df_ss_vente.head()
    
    Out[ ]:
    id_prod price categ
    114 0_1595 2.99 0
    274 1_470 5.41 1
    643 0_2263 32.99 0
    674 0_1902 2.28 0
    790 0_680 7.43 0
    In [ ]:
    plt.subplot(121)
    sns.histplot(data=df_ss_vente["categ"], kde=True)
    
    plt.subplot(122)
    sns.boxplot(data=df_ss_vente, x="categ", y="price")
    
    Out[ ]:
    <Axes: xlabel='categ', ylabel='price'>
    In [ ]:
    df_ss_vente_T = df_ss_vente.pivot_table(index="id_prod", values="price", columns="categ").reset_index()
    df_ss_vente_T.describe()
    
    Out[ ]:
    categ 0 1 2
    count 36.000000 4.000000 3.000000
    mean 11.247222 30.795000 138.816667
    std 11.409900 30.070196 19.774545
    min 0.990000 5.410000 115.990000
    25% 1.990000 6.692500 132.865000
    50% 2.990000 25.055000 149.740000
    75% 21.940000 49.157500 150.230000
    max 32.990000 67.660000 150.720000
    In [ ]:
    df_temp_btob = df_btob.loc[(df_btob["monthly"] >= "2022-02-01") & (df_btob["id_prod"].isin(ventes_nulles))]
    
    In [ ]:
    df_temp = df_temp_btob.groupby(["id_prod","categ", "price"]).agg(ca_total=("price","sum"), nb_book=("price","count"))
    df_temp
    
    Out[ ]:
    ca_total nb_book
    id_prod categ price
    0_1191 0 0.99 0.99 1
    0_1314 0 20.63 61.89 3
    0_1726 0 1.57 1.57 1
    0_1820 0 22.30 66.90 3
    0_419 0 20.99 41.98 2
    0_680 0 7.43 37.15 5
    1_470 1 5.41 5.41 1
    In [ ]:
    df_temp["ca_total"].sum()
    
    Out[ ]:
    215.89000000000001
    Répartition des ventes selon le product_id (courbe de Lorenz, loi de Pareto, indice de Gini)¶
    In [ ]:
    chiffre_affaires = df_rentable['total_ca'].values
    n = len(chiffre_affaires)
    
    lorenz = np.cumsum(np.sort(chiffre_affaires)) / chiffre_affaires.sum() #calcul le ratio cumulé du CA des différents produits
    pareto = np.quantile(lorenz, 0.8)
    
    lorenz = np.append([0],lorenz) # La courbe de Lorenz commence à 0
    
    #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
    xaxis = np.linspace(0-1/n,1+1/n,n+1) 
    
    plt.plot(xaxis, lorenz, label="Courbe de Lorenz")
    plt.plot([0,1], c="r", linestyle="--", label="Bissectrice")
    
    rect = Rectangle((0,0), 0.8,pareto, linewidth=1, edgecolor='r', facecolor='none', linestyle=":", label="Pareto")
    plt.gca().add_patch(rect)
    
    # Remplir la région entre la courbe de Lorenz et la bissectrice
    plt.fill_between(xaxis, xaxis, lorenz, alpha=0.2, label='Inégalité')
    
    plt.title("Courbe de Lorenz sur les prix produits")
    plt.xlabel("Proportion produit")
    plt.ylabel("Proportion CA")
    plt.legend()
    plt.grid()
    plt.show()
    
    pareto.round(2)
    
    Out[ ]:
    0.22

    Loi de Pareto (20/80) largement respectée : Il y a 20% des références qui représentent 78% du C.A (1-0.22)

    In [ ]:
    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 donc en 2, on fait de même pour le dernier segment lorenz[-1] qui est à moitié au dessus de 1.
    S = 0.5 - AUC # surface entre la première bissectrice et le courbe de Lorenz
    gini = 2*S
    gini
    
    Out[ ]:
    0.7430432755072107
    Top 20% des références sur lesquelles il faut se concentrer (78% du chiffre d'affaires) : 2 options à voir!¶

    Option 1 :

    In [ ]:
    #Détermine l'index qui vient au 1 quintile (df déjà trié par CA décroissant)
    seuil = int(np.quantile(df_rentable.index, 0.2))
    
    In [ ]:
    #Récupération des références avec le plus grand CA
    references_pareto = df_rentable.iloc[0:seuil]
    
    references_pareto.info()
    
    <class 'pandas.core.frame.DataFrame'>
    Index: 652 entries, 3093 to 21
    Data columns (total 3 columns):
     #   Column      Non-Null Count  Dtype  
    ---  ------      --------------  -----  
     0   id_prod     652 non-null    object 
     1   total_book  652 non-null    int64  
     2   total_ca    652 non-null    float64
    dtypes: float64(1), int64(1), object(1)
    memory usage: 20.4+ KB
    
    In [ ]:
    #Vérification des 20/80
    ratio_ca_test = round(references_pareto["total_ca"].sum() / df_rentable["total_ca"].sum() * 100, 2)
    
    ratio_ca_test
    
    Out[ ]:
    78.21

    20% des références représentent bien 78.21% du chiffre d'affaires -> loi de Pareto 20/80 OK

    Option 2 : (logique à revoir, ça ne fonctionne pas)

    In [ ]:
    #Création des colonnes de proportions pour le nombre de livres et le C.A 
    df_rentable["proportion_CA"] = (df_rentable["total_ca"] / df_rentable["total_ca"].sum())*100
    df_rentable["proportion_book"] = (df_rentable["total_book"] / df_rentable["total_book"].sum())*100
    
    df_rentable.head()
    
    Out[ ]:
    id_prod total_book total_ca proportion_CA proportion_book
    3093 2_159 624 91097.76 0.817507 0.097388
    3067 2_135 920 63470.80 0.569584 0.143585
    3043 2_112 870 58785.90 0.527542 0.135782
    3032 2_102 941 55650.74 0.499407 0.146863
    2589 1_369 2205 52897.95 0.474703 0.344137
    In [ ]:
    #Tri dans l'ordre décroissant de la proportion C.A
    df_rentable.sort_values("proportion_CA", ascending=False, inplace=True)
    
    In [ ]:
    #Création d'une colonne avec le cumul de proportion du nombre de livres
    df_rentable["cum_book_proportion"] = np.cumsum(df_rentable["proportion_book"])
    
    In [ ]:
    #Création d'une colonne avec le cumul de proportion de C.A
    df_rentable["cum_CA_proportion"] = np.cumsum(df_rentable["proportion_CA"])
    
    In [ ]:
    df_rentable["difference"] = df_rentable["cum_CA_proportion"] - df_rentable["cum_book_proportion"]
    
    df_rentable = df_rentable.reset_index()
    
    In [ ]:
    #Récupération du seuil où l'écart en la proportion du nombre de livres et le prop du C.A est le plus grand.
    df_rentable.loc[df_rentable["difference"] == df_rentable["difference"].max()]
    
    Out[ ]:
    index id_prod total_book total_ca proportion_CA proportion_book cum_book_proportion cum_CA_proportion difference
    243 601 0_1545 673 12780.27 0.114689 0.105036 33.903461 51.413055 17.509594
    In [ ]:
    df_rentable
    
    Out[ ]:
    index id_prod total_book total_ca proportion_CA proportion_book cum_book_proportion cum_CA_proportion difference
    0 3093 2_159 624 91097.76 0.817507 0.097388 0.097388 0.817507 7.201183e-01
    1 3067 2_135 920 63470.80 0.569584 0.143585 0.240974 1.387090 1.146117e+00
    2 3043 2_112 870 58785.90 0.527542 0.135782 0.376755 1.914632 1.537876e+00
    3 3032 2_102 941 55650.74 0.499407 0.146863 0.523618 2.414039 1.890421e+00
    4 2589 1_369 2205 52897.95 0.474703 0.344137 0.867755 2.888742 2.020987e+00
    ... ... ... ... ... ... ... ... ... ...
    3257 718 0_1653 2 1.98 0.000018 0.000312 99.999376 99.999956 5.800425e-04
    3258 799 0_1726 1 1.57 0.000014 0.000156 99.999532 99.999970 4.380606e-04
    3259 312 0_1284 1 1.38 0.000012 0.000156 99.999688 99.999982 2.943736e-04
    3260 209 0_1191 1 0.99 0.000009 0.000156 99.999844 99.999991 1.471868e-04
    3261 594 0_1539 1 0.99 0.000009 0.000156 100.000000 100.000000 -9.947598e-13

    3262 rows × 9 columns

    À voir, ça ne joue pas.

    Tranche de prix des livres les plus vendus¶

    In [ ]:
    df_btoc.describe().round(2)
    
    Out[ ]:
    date monthly price categ age
    count 640734 640734 640734.00 640734.00 640734.00
    mean 2022-03-01 19:54:49.819372544 2022-02-14 14:40:32.677522688 17.39 0.45 45.21
    min 2021-03-01 00:01:07 2021-03-01 00:00:00 0.62 0.00 19.00
    25% 2021-09-10 12:57:20.750000128 2021-09-01 00:00:00 8.99 0.00 36.00
    50% 2022-02-27 03:35:19 2022-02-01 00:00:00 13.99 0.00 44.00
    75% 2022-08-28 20:52:43.750000128 2022-08-01 00:00:00 19.04 1.00 52.00
    max 2023-02-28 23:58:30 2023-02-01 00:00:00 300.00 2.00 94.00
    std NaN NaN 18.00 0.59 13.88
    In [ ]:
    df_btoc["price"].mode()
    
    Out[ ]:
    0    15.99
    Name: price, dtype: float64
  • Prix minimum: 0.62€ (certainement un prix résultant d'une réduction comme des soldes)
  • Prix le plus élevé: 300€
  • Prix moyen: 17.39€
  • Prix le plus acheté (mode): 15.99€
  • In [ ]:
    df_btoc["price"].hist(bins=10)
    plt.xlim(left=0, right=100)
    
    Out[ ]:
    (0.0, 100.0)

    Tranche de prix la plus vendue est de 0.62€ : ~ 30€ == cat 1?

    In [ ]:
    #Prix le plus vendu
    mode_cat0 = df_btoc.loc[df_btoc["categ"] == 0]["price"].mode()
    mode_cat0
    
    Out[ ]:
    0    4.99
    Name: price, dtype: float64
    In [ ]:
    #Prix le plus vendu
    mode_cat1 = df_btoc.loc[df_btoc["categ"] == 1]["price"].mode()
    mode_cat1
    
    Out[ ]:
    0    15.99
    Name: price, dtype: float64
    In [ ]:
    #Prix le plus vendu
    mode_cat2 = df_btoc.loc[df_btoc["categ"] == 2]["price"].mode()
    mode_cat2
    
    Out[ ]:
    0    68.99
    Name: price, dtype: float64

    Proportion de chiffre d'affaires par categories¶

    Proportion du CA des catégories sur l'ensemble du temps¶
    In [ ]:
    first_date = df_btoc["date"].min().strftime("%m-%y")
    last_date = df_btoc["date"].max().strftime("%m-%y")
    
    In [ ]:
    df_proportion = df_btoc.groupby("categ").agg(ca_total = ("price","sum")).reset_index()
    
    df_proportion
    
    Out[ ]:
    categ ca_total
    0 0 4119200.69
    1 1 4520101.86
    2 2 2504064.46
    In [ ]:
    plt.pie(data=df_proportion, x="ca_total", labels="ca_total", labeldistance=0.4, autopct='%1.1f%%');
    
    plt.title("Proportion de CA des catégories")
    plt.legend(df_proportion.index, title="Catégorie", bbox_to_anchor=(1.2, 1), loc='upper right');
    plt.xlabel("Du {} au {}".format(first_date, last_date));
    

    Conclusion: Autant de CA pour les catégories 0 et 1. Deux fois moins de CA est représenté par la catégorie 2 (la plus cher)

    Évolution du CA des différentes catégories¶
    In [ ]:
    df_btoc.head()
    
    Out[ ]:
    client_id date monthly session_id id_prod price categ age sex jour heure
    0 c_329 2021-03-01 00:01:07 2021-03-01 s_1 0_1259 11.99 0 56 f Monday 00
    1 c_329 2022-10-01 00:01:07 2022-10-01 s_275943 0_1259 11.99 0 56 f Saturday 00
    2 c_329 2022-12-01 00:01:07 2022-12-01 s_305291 0_1259 11.99 0 56 f Thursday 00
    3 c_329 2023-01-01 00:01:07 2023-01-01 s_320153 0_1259 11.99 0 56 f Sunday 00
    4 c_329 2021-11-23 18:21:56 2021-11-01 s_123998 1_397 18.99 1 56 f Tuesday 18
    In [ ]:
    df_temp = df_btoc.groupby(["monthly","categ"]).agg(total_ca=("price","sum")).reset_index()
    
    In [ ]:
    df_temp = df_temp.pivot(index="monthly", columns="categ", values="total_ca")
    df_temp.head()
    
    Out[ ]:
    categ 0 1 2
    monthly
    2021-03-01 180637.95 174569.68 90711.08
    2021-04-01 191458.18 145272.82 102606.85
    2021-05-01 182350.51 155309.63 117227.32
    2021-06-01 156621.73 177840.33 112640.11
    2021-07-01 135456.27 176647.61 135489.27
    In [ ]:
    df_temp.plot(kind="bar", stacked=True)
    
    plt.title("Évolution du chiffre d'affaires des 3 catégories de livres")
    plt.legend(bbox_to_anchor=(1.2, 1), loc='upper right', borderaxespad=0, title="Catégorie")
    
    plt.xlabel("Date")
    plt.ylabel("Chiffre d'affaires (en €)")
    
    Out[ ]:
    Text(0, 0.5, "Chiffre d'affaires (en €)")
    In [ ]:
    df_temp = df_temp.reset_index()
    
    In [ ]:
    df_temp.set_index("monthly").resample("m").sum().rolling(6).mean().plot()
    
    plt.title("Évolution du chiffre d'affaires des 3 catégories de livres (MA sur semestre)")
    
    plt.legend(bbox_to_anchor=(1.2, 0.8), loc='upper right', borderaxespad=0, title="Catégorie")
    plt.grid(which="both")
    
    plt.xlabel("Date")
    plt.xlim(left=np.quantile(df_temp.monthly,0.2))#afin d'afficher seulement la portion utile
    plt.ylabel("Chiffre d'affaires (en €)")
    
    Out[ ]:
    Text(0, 0.5, "Chiffre d'affaires (en €)")

    Conclusion: ventes de la catégorie 0 stablent, cependant quand il y a plus de C.A dans la catégorie 1, le CA de la catégorie 2 chute.

    Représentation des prix par catégories¶

    Visualisation des prix par catégorie¶
    In [ ]:
    df_produit_unique = df_btoc[["id_prod","price", "categ"]].drop_duplicates(subset="id_prod")
    df_produit_unique.head()
    
    Out[ ]:
    id_prod price categ
    0 0_1259 11.99 0
    4 1_397 18.99 1
    6 0_1140 3.73 0
    8 1_451 20.99 1
    9 1_378 26.61 1
    In [ ]:
    sns.boxplot(data=df_produit_unique, x="categ", y="price")
    
    plt.grid()
    
    In [ ]:
    df_T_produit = df_produit_unique.pivot(index="id_prod", columns="categ", values="price")
    df_T_produit.describe().round(2)
    
    Out[ ]:
    categ 0 1 2
    count 2290.00 737.00 235.00
    mean 11.71 25.50 107.49
    std 7.53 15.44 49.31
    min 0.62 2.00 30.99
    25% 5.64 13.35 70.46
    50% 10.30 22.99 100.99
    75% 16.46 33.99 133.55
    max 40.99 80.99 300.00
    Prix moyen des catégorie sur toute la période de temps¶
    In [ ]:
    #Création du dataframe
    df_prix_moy = df_btoc.groupby("categ").agg(prix_moyen=("price","mean")).reset_index()
    df_prix_moy.head()
    
    Out[ ]:
    categ prix_moyen
    0 0 10.636207
    1 1 20.489571
    2 2 76.231870
    L'évolution du prix moyen du livre sur chaque catégorie sur toute la période de temps¶
    In [ ]:
    #Création du dataframe
    df_evoprix_moy = df_btoc.groupby(["monthly","categ"]).agg(prix_moyen=("price","mean")).reset_index()
    df_evoprix_moy.head()
    
    Out[ ]:
    monthly categ prix_moyen
    0 2021-03-01 0 10.678526
    1 2021-03-01 1 20.436628
    2 2021-03-01 2 76.291909
    3 2021-04-01 0 10.610040
    4 2021-04-01 1 20.573973
    In [ ]:
    #Transposition du tableau
    df_evoprix_moy = df_evoprix_moy.pivot(index="monthly", columns="categ", values="prix_moyen")
    
    In [ ]:
    df_evoprix_moy.plot(kind="bar", stacked=True)
    
    Out[ ]:
    <Axes: xlabel='monthly'>
    In [ ]:
    df_evoprix_moy.describe()
    
    Out[ ]:
    categ 0 1 2
    count 24.000000 24.000000 24.000000
    mean 10.636844 20.491227 76.280268
    std 0.033196 0.065956 1.155821
    min 10.560430 20.371095 73.811916
    25% 10.611399 20.453718 75.385607
    50% 10.641847 20.468322 76.371703
    75% 10.661510 20.522093 77.011563
    max 10.693232 20.614729 78.308163

    Conclusion : peu de variation de prix moyen sur les différentes catégories. Seule la catégorie 2 (la plus cher) varie de 4.5€

    Profil clients (BtoC) :¶

    • Répartition du chiffre d'affaires par les clients (courbe de Lorenz)
    • Ratio de parité de la clientèle
    • Parité similaire sur les 3 branches (population | CA | Nombre de commandes) ?
    • Informations globales selon le sexe
    • Tranche d'âge qui achète le plus
    • Nombre de clients par âge

    Profilage clients basique¶

    Répartition du chiffre d'affaires par les clients (courbe de Lorenz)¶
    In [ ]:
    df_CA_client = df_btoc.groupby("client_id").agg(CA_total=("price","sum")).reset_index()
    
    df_CA_client.head()
    
    Out[ ]:
    client_id CA_total
    0 c_1 629.02
    1 c_10 1353.60
    2 c_100 254.85
    3 c_1000 2291.88
    4 c_1001 1823.85
    In [ ]:
    chiffre_affaires = df_CA_client['CA_total'].values
    n = len(chiffre_affaires)
    
    lorenz = np.cumsum(np.sort(chiffre_affaires)) / chiffre_affaires.sum()
    pareto = np.quantile(lorenz, 0.8)
    
    lorenz = np.append([0],lorenz) # La courbe de Lorenz commence à 0
    
    #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
    xaxis = np.linspace(0-1/n,1+1/n,n+1) 
    
    plt.plot(xaxis, lorenz, label="Courbe de Lorenz")
    plt.plot([0,1], c="r", linestyle="--", label="Bissectrice")
    
    rect = Rectangle((0,0), 0.8,pareto, linewidth=1, edgecolor='r', facecolor='none', linestyle=":", label="Pareto")
    plt.gca().add_patch(rect)
    
    plt.title("Courbe de Lorenz sur le CA réalisé par les clients")
    plt.xlabel("Proportion client")
    plt.ylabel("Proportion CA")
    plt.legend()
    plt.grid()
    plt.show()
    
    pareto.round(2)
    
    Out[ ]:
    0.56

    Loi de Paréto (20/80) non-respectée : 20% des clients représentent 44% du CA (1 - 0.56)

    Ou lire : 80% des clients représentent 56% de CA

    Ratio de parité de la clientèle¶
    In [ ]:
    #Création du dataframe
    df_temp = df_btoc.groupby(["client_id","session_id","sex"]).agg(CA_total=("price","sum"), book_session=("session_id", "count")).reset_index()
    df_temp.head()
    
    Out[ ]:
    client_id session_id sex CA_total book_session
    0 c_1 s_101417 m 15.87 1
    1 c_1 s_105105 m 62.96 4
    2 c_1 s_114737 m 92.62 5
    3 c_1 s_120172 m 44.29 2
    4 c_1 s_134971 m 10.30 1
    In [ ]:
    df_parite = df_temp.groupby(["client_id","sex"]).agg(ca_total=("CA_total","sum"), nb_session=("book_session","count"), livre_total=("book_session","sum")).reset_index()
    df_parite.head()
    
    Out[ ]:
    client_id sex ca_total nb_session livre_total
    0 c_1 m 629.02 34 43
    1 c_10 m 1353.60 34 58
    2 c_100 m 254.85 5 8
    3 c_1000 f 2291.88 94 126
    4 c_1001 m 1823.85 47 103
    In [ ]:
    #Transposition des données sur le sexe
    df_pariteT = df_parite.groupby("sex").agg(ca_total=("ca_total","sum"), session_total=("nb_session","sum"), livre_total=("livre_total","sum"), population=("client_id","count")).reset_index()
    df_pariteT
    
    Out[ ]:
    sex ca_total session_total livre_total population
    0 f 5796925.08 168546 333494 4478
    1 m 5346441.93 153920 307240 4118
    In [ ]:
    #Affichage de la proportion homme/femme
    explode = (0, 0.1)
    plt.pie(data=df_pariteT, x="population", labels="population", 
            autopct='%1.1f%%',labeldistance=0.3, explode=explode, shadow=True, 
            colors=["pink","cornflowerblue"], textprops={'fontsize': 14});
    
    plt.title("Proportion de clients homme/femme")
    plt.legend(bbox_to_anchor=(1.2, 1), loc='upper right', borderaxespad=0, title="Sexe", labels=df_pariteT["sex"])
    
    Out[ ]:
    <matplotlib.legend.Legend at 0x2158fdcb790>
    Parité similaire sur les 3 branches (population | CA | Nombre de commandes) ?¶
    In [ ]:
    #Transposition des données pour un graphique plus lisible
    df_test = df_pariteT.set_index("sex").T
    
    In [ ]:
    df_test
    
    Out[ ]:
    sex f m
    ca_total 5796925.08 5346441.93
    session_total 168546.00 153920.00
    livre_total 333494.00 307240.00
    population 4478.00 4118.00
    In [ ]:
    #Normalisation pour afficher un graphique avec les mêmes echelles de données
    df_norm = df_test
    
    df_norm["f"] = df_test["f"] / (df_test["f"]+df_test["m"])
    df_norm["m"]= 1 - df_test["f"]
    
    df_norm
    
    Out[ ]:
    sex f m
    ca_total 0.520213 0.479787
    session_total 0.522678 0.477322
    livre_total 0.520487 0.479513
    population 0.520940 0.479060
    In [ ]:
    df_norm.plot(kind="bar", color=["pink","cornflowerblue"])
    
    plt.legend(bbox_to_anchor=(1.2, 1), loc='upper right', borderaxespad=0, title="Sexe")
    plt.grid(which="both")
    
    Informations globales selon le sexe¶
    In [ ]:
    df_temp = df_btoc.groupby(["client_id", "sex", "session_id"]).agg(ca_session=("price","sum")).reset_index()
    
    df_sexe = df_temp.groupby(["client_id", "sex"]).agg(panier_moyen=("ca_session","mean")).reset_index()
    
    df_sexe.head()
    
    Out[ ]:
    client_id sex panier_moyen
    0 c_1 m 18.500588
    1 c_10 m 39.811765
    2 c_100 m 50.970000
    3 c_1000 f 24.381702
    4 c_1001 m 38.805319
    In [ ]:
    sns.boxplot(data=df_sexe, x="sex", y="panier_moyen", showfliers=False)
    
    Out[ ]:
    <Axes: xlabel='sex', ylabel='panier_moyen'>
    In [ ]:
    df_sexe_T = df_sexe.pivot_table(index="client_id",columns="sex", values="panier_moyen")
    
    In [ ]:
    df_sexe_T.describe()
    
    Out[ ]:
    sex f m
    count 4478.000000 4118.000000
    mean 40.176306 40.649086
    std 22.741304 22.788894
    min 5.570000 4.150000
    25% 25.467425 25.986893
    50% 32.282341 32.418056
    75% 47.822981 48.731896
    max 259.422500 241.160000
    Tranches d'ages qui achète le plus¶
    In [ ]:
    df_btoc["age"].hist()
    
    plt.title("Nombre de livres achetés selon l'age");
    plt.xlabel("Age de {} à {} ans".format(df_btoc["age"].min(), df_btoc["age"].max()))
    plt.ylabel("Nombre totaux de livres achetés")
    
    Out[ ]:
    Text(0, 0.5, 'Nombre totaux de livres achetés')

    Ce sont les 35-45 ans qui achètent le plus de livres

    In [ ]:
    sns.scatterplot(data=df_btoc[["price", "age"]], x="age", y="price")
    
    Out[ ]:
    <Axes: xlabel='age', ylabel='price'>

    Ce sont essentiellement des jeunes de 19 à 30 ans qui achètes les livres les plus chers (entre 80€ et 250€)
    Par ailleur, les livres à 150€ / unité semblent satisfaire presque tous les ages

    Nombre de clients par âge¶
    In [ ]:
    #Création d'un dataframe avec les clients aggrégés
    df_temp = df_btoc.groupby("client_id").agg(age=("age","mean"), CA_total=("price","sum")).reset_index()
    
    In [ ]:
    df_temp["age"].hist()
    
    plt.title("Nombre de clients selon l'age");
    
    plt.ylabel("Nombre de clients")
    plt.xlabel("Age de {} à {} ans".format(df_btoc["age"].min(), df_btoc["age"].max()))
    
    Out[ ]:
    Text(0.5, 0, 'Age de 19 à 94 ans')
    In [ ]:
    #Quel est l'age le plus récurrent
    df_temp["age"].mode()
    
    Out[ ]:
    0    19.0
    Name: age, dtype: float64
    In [ ]:
    df_temp.describe().round(0)
    
    Out[ ]:
    age CA_total
    count 8596.0 8596.0
    mean 45.0 1296.0
    std 17.0 958.0
    min 19.0 6.0
    25% 31.0 563.0
    50% 44.0 1046.0
    75% 57.0 1796.0
    max 94.0 5286.0

    Écart type de 17 ans

    In [ ]:
    df_temp["tranche_age"] = 0
    
    In [ ]:
    #Calcul du nombre de tranche d'age necessaire (arrondi à l'entier superieur)
    nb_tranche = ceil((df_temp["age"].max() - df_temp["age"].min()) / df_temp["age"].std())
    nb_tranche
    
    Out[ ]:
    5
    In [ ]:
    taille_tranche = ceil((df_temp["age"].max() - df_temp["age"].min()) / nb_tranche)
    taille_tranche
    
    Out[ ]:
    15

    Désignation des tranches d'age (15 ans)

  • 19 - 34 ans
  • 35 - 50 ans
  • 51 - 66 ans
  • 67 - 82 ans
  • 83 - 98 ans
  • In [ ]:
    #Désignation des tranches d'ages
    age_mini = df_temp["age"].min()
    
    for tranche in range(nb_tranche):
        clients_index = np.where(
                            (df_temp["age"] >= age_mini)
                            &(df_temp["age"] <= (age_mini + taille_tranche)))
    
        df_temp["tranche_age"].iloc[clients_index] = tranche + 1
    
        age_mini = age_mini + taille_tranche + 1
    
    df_temp.head()
    
    Out[ ]:
    client_id age CA_total tranche_age
    0 c_1 68.0 629.02 4
    1 c_10 67.0 1353.60 4
    2 c_100 31.0 254.85 1
    3 c_1000 57.0 2291.88 3
    4 c_1001 41.0 1823.85 2
    In [ ]:
    #Dataframe utilisé dans les analyses pour Julies (plus bas)
    df_tranches_ageCA = df_temp
    
    In [ ]:
    #Synthèse du nombre de client par tranche d'age
    df_tranche = df_temp.groupby("tranche_age")["client_id"].count()
    df_tranche
    
    Out[ ]:
    tranche_age
    1    2707
    2    2783
    3    2040
    4     921
    5     145
    Name: client_id, dtype: int64
    In [ ]:
    #Vérification du bon nombre de clients total
    df_tranche.sum()
    
    Out[ ]:
    8596
    In [ ]:
    df_tranche.plot(kind="bar")
    plt.title("Nombre de clients par tranche d'age")
    plt.ylabel("Nombre de clients")
    plt.xlabel("Tranche d'age\nDe {} à {} ans".format(int(df_temp["age"].min()),int(df_temp["age"].max())));
    

    Segmentation RFM¶

    • Récent
    • Fréquent
    • Montant
      Créer un tableau avec ces 3 colonnes par n° client pour ensuite les classer selon le Eleven RFM (11 profils client) en utilisant les quintiles
    Préparation du dataframe initial¶
    In [ ]:
    #Création d'un data frame destiné aux classements
    df_rfm = df_btoc
    
    df_rfm["date"] = pd.to_datetime(df_rfm["date"].dt.strftime("%Y-%m-%d"))
    
    df_rfm
    
    Out[ ]:
    client_id date monthly session_id id_prod price categ age sex jour heure
    0 c_329 2021-03-01 2021-03-01 s_1 0_1259 11.99 0 56 f Monday 00
    1 c_329 2022-10-01 2022-10-01 s_275943 0_1259 11.99 0 56 f Saturday 00
    2 c_329 2022-12-01 2022-12-01 s_305291 0_1259 11.99 0 56 f Thursday 00
    3 c_329 2023-01-01 2023-01-01 s_320153 0_1259 11.99 0 56 f Sunday 00
    4 c_329 2021-11-23 2021-11-01 s_123998 1_397 18.99 1 56 f Tuesday 18
    ... ... ... ... ... ... ... ... ... ... ... ...
    640729 c_7739 2022-08-28 2022-08-01 s_259828 2_163 68.99 2 26 m Sunday 16
    640730 c_7739 2022-10-28 2022-10-01 s_289331 2_163 68.99 2 26 m Friday 16
    640731 c_712 2021-12-18 2021-12-01 s_136405 1_64 19.81 1 56 f Saturday 20
    640732 c_712 2022-10-18 2022-10-01 s_284469 1_64 19.81 1 56 f Tuesday 20
    640733 c_4478 2021-09-15 2021-09-01 s_90430 0_1692 13.36 0 53 f Wednesday 19

    640734 rows × 11 columns

    In [ ]:
    #Regroupement par session
    df_rfm = df_rfm.groupby(["date","client_id","session_id"])["price"].agg(CA_session="sum").reset_index()
    
    df_rfm
    
    Out[ ]:
    date client_id session_id CA_session
    0 2021-03-01 c_1031 s_348 27.99
    1 2021-03-01 c_1052 s_429 247.22
    2 2021-03-01 c_1074 s_360 29.21
    3 2021-03-01 c_110 s_244 21.23
    4 2021-03-01 c_1112 s_54 31.31
    ... ... ... ... ...
    323945 2023-02-28 c_945 s_348033 105.98
    323946 2023-02-28 c_951 s_348377 9.99
    323947 2023-02-28 c_973 s_348099 74.66
    323948 2023-02-28 c_990 s_348255 34.73
    323949 2023-02-28 c_990 s_348299 7.99

    323950 rows × 4 columns

    R. (Récent) Création du dataframe sur les achats les plus récents¶
    In [ ]:
    #Récupération des sessions les plus récentes par client
    df_recent = df_rfm.groupby("client_id")["date"].max().reset_index()
    
    df_recent
    
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    j:\Mon Drive\Professionnel\OC\OC - Projet 6\Grenier_Yohan_1_notebook_092023.ipynb Cellule 242 line 2
          <a href='vscode-notebook-cell:/j%3A/Mon%20Drive/Professionnel/OC/OC%20-%20Projet%206/Grenier_Yohan_1_notebook_092023.ipynb#Y463sZmlsZQ%3D%3D?line=0'>1</a> #Récupération des sessions les plus récentes par client
    ----> <a href='vscode-notebook-cell:/j%3A/Mon%20Drive/Professionnel/OC/OC%20-%20Projet%206/Grenier_Yohan_1_notebook_092023.ipynb#Y463sZmlsZQ%3D%3D?line=1'>2</a> df_recent = df_rfm.groupby("client_id")["date"].max().reset_index()
          <a href='vscode-notebook-cell:/j%3A/Mon%20Drive/Professionnel/OC/OC%20-%20Projet%206/Grenier_Yohan_1_notebook_092023.ipynb#Y463sZmlsZQ%3D%3D?line=3'>4</a> df_recent
    
    NameError: name 'df_rfm' is not defined
    In [ ]:
    #Tri dans l'ordre du plus récent au moins récent
    df_recent.sort_values("date", ascending=False, inplace=True)
    df_recent
    
    Out[ ]:
    client_id date
    2713 c_3445 2023-02-28
    6034 c_6444 2023-02-28
    1532 c_238 2023-02-28
    1529 c_2377 2023-02-28
    2942 c_3653 2023-02-28
    ... ... ...
    2371 c_3136 2021-05-08
    693 c_1624 2021-04-27
    4084 c_4686 2021-04-07
    5825 c_6256 2021-04-02
    5865 c_6292 2021-03-09

    8596 rows × 2 columns

    In [ ]:
    Ranking(df_recent,"date","R")
    
    In [ ]:
    df_recent
    
    Out[ ]:
    client_id date R
    2713 c_3445 2023-02-28 5
    6034 c_6444 2023-02-28 5
    1532 c_238 2023-02-28 5
    1529 c_2377 2023-02-28 5
    2942 c_3653 2023-02-28 5
    ... ... ... ...
    2371 c_3136 2021-05-08 1
    693 c_1624 2021-04-27 1
    4084 c_4686 2021-04-07 1
    5825 c_6256 2021-04-02 1
    5865 c_6292 2021-03-09 1

    8596 rows × 3 columns

    In [ ]:
    df_recent["R"].unique()
    
    Out[ ]:
    array([5, 4, 3, 2, 1], dtype=int64)
    F. (Fréquence) Création du dataframe sur les sessions les plus fréquentes par client¶
    In [ ]:
    #Récupération des sessions les plus fréquentes par client
    df_frequence = df_rfm.groupby("client_id")["session_id"].agg(total_session = "count").reset_index()
    
    df_frequence.head()
    
    Out[ ]:
    client_id total_session
    0 c_1 34
    1 c_10 34
    2 c_100 5
    3 c_1000 95
    4 c_1001 47
    In [ ]:
    #Tri dans l'ordre du plus de sessions au moins
    df_frequence.sort_values("total_session", ascending=False, inplace=True)
    
    df_frequence.head()
    
    Out[ ]:
    client_id total_session
    8340 c_8526 167
    707 c_1637 166
    1405 c_2265 166
    8323 c_8510 165
    6306 c_669 164
    In [ ]:
    #Création du classement par fréquence
    Ranking(df_frequence, "total_session", "F")
    
    df_frequence
    
    Out[ ]:
    client_id total_session F
    8340 c_8526 167 5
    707 c_1637 166 5
    1405 c_2265 166 5
    8323 c_8510 165 5
    6306 c_669 164 5
    ... ... ... ...
    1433 c_2290 1 1
    5825 c_6256 1 1
    4042 c_4648 1 1
    2583 c_3327 1 1
    8147 c_8351 1 1

    8596 rows × 3 columns

    M. (Montant) Création du dataframe de classement des plus gros chiffre d'affaires réalisé par client¶
    In [ ]:
    #Récupération du classement de chiffre d'affaires par client
    df_montant = df_rfm.groupby("client_id")["CA_session"].agg(totalCA_client = "sum").reset_index()
    
    df_montant.head()
    
    Out[ ]:
    client_id totalCA_client
    0 c_1 629.02
    1 c_10 1353.60
    2 c_100 254.85
    3 c_1000 2291.88
    4 c_1001 1823.85
    In [ ]:
    #Tri dans l'ordre du plus grand chiffre d'affaires au moins
    df_montant.sort_values("totalCA_client", ascending=False, inplace=True)
    
    df_montant.head()
    
    Out[ ]:
    client_id totalCA_client
    634 c_1570 5285.82
    2512 c_3263 5276.87
    1267 c_2140 5260.18
    2107 c_2899 5214.05
    7002 c_7319 5155.77
    In [ ]:
    #Création du classement par CA client
    Ranking(df_montant, "totalCA_client", "M")
    
    df_montant
    
    Out[ ]:
    client_id totalCA_client M
    634 c_1570 5285.82 5
    2512 c_3263 5276.87 5
    1267 c_2140 5260.18 5
    2107 c_2899 5214.05 5
    7002 c_7319 5155.77 5
    ... ... ... ...
    3853 c_4478 13.36 1
    4042 c_4648 11.20 1
    7885 c_8114 9.98 1
    7914 c_8140 8.30 1
    8147 c_8351 6.31 1

    8596 rows × 3 columns

    Création du dataframe RFM¶
    In [ ]:
    #Merge des 3 dataframe R(récence), F(Fréquence), M(Montant)
    df_RFM = pd.merge(df_recent, df_frequence, on="client_id")
    df_RFM = pd.merge(df_RFM, df_montant, on="client_id")
    
    df_RFM
    
    Out[ ]:
    client_id date R total_session F totalCA_client M
    0 c_3445 2023-02-28 5 54 4 1978.80 4
    1 c_6444 2023-02-28 5 99 5 3193.62 5
    2 c_238 2023-02-28 5 84 5 2665.22 5
    3 c_2377 2023-02-28 5 71 5 1402.86 4
    4 c_3653 2023-02-28 5 61 5 1597.55 4
    ... ... ... ... ... ... ... ...
    8591 c_3136 2021-05-08 1 2 1 176.13 1
    8592 c_1624 2021-04-27 1 1 1 28.54 1
    8593 c_4686 2021-04-07 1 2 1 64.58 1
    8594 c_6256 2021-04-02 1 1 1 23.26 1
    8595 c_6292 2021-03-09 1 1 1 24.24 1

    8596 rows × 7 columns

    In [ ]:
    #Création de la colonne pour le code RFM
    df_RFM["RFM"] = df_RFM["R"].map(str) + df_RFM["F"].map(str) + df_RFM["M"].map(str)
    
    In [ ]:
    #Tri dans l'ordre du top clients
    df_RFM.sort_values("RFM", ascending=False, inplace=True)
    
    In [ ]:
    df_RFM
    
    Out[ ]:
    client_id date R total_session F totalCA_client M RFM
    1769 c_4973 2023-02-24 5 109 5 3503.94 5 555
    549 c_247 2023-02-27 5 90 5 2597.22 5 555
    531 c_7403 2023-02-27 5 123 5 2592.73 5 555
    1332 c_6635 2023-02-25 5 112 5 3704.54 5 555
    533 c_7418 2023-02-27 5 74 5 2792.71 5 555
    ... ... ... ... ... ... ... ... ...
    7893 c_2417 2022-11-08 1 12 1 196.10 1 111
    7895 c_3439 2022-11-08 1 5 1 102.27 1 111
    7897 c_4392 2022-11-08 1 10 1 317.32 1 111
    7898 c_3024 2022-11-08 1 4 1 179.96 1 111
    8595 c_6292 2021-03-09 1 1 1 24.24 1 111

    8596 rows × 8 columns

    In [ ]:
    #Conversion de la colonne RFM en INT
    df_RFM["RFM"] = pd.to_numeric(df_RFM["RFM"],downcast="integer")
    
    Ciblage des clients selon la segmentation RFM Eleven¶

    Source avec exemple

    In [ ]:
    #Création des 11 catégories
    noms_segments = {"1. Meilleur" : [555, 554, 544, 545, 454, 455, 445], 
                        "2. Fidèle" : [543, 444, 435, 355, 354, 345, 344, 335], 
                        "3. Potentiellement fidèle": [553, 551, 552, 541, 542, 533, 532, 531, 452, 451, 442, 441, 431, 453, 433, 432, 423, 353, 352, 351, 342, 341, 333, 323], 
                        "4. Nouveau client" : [512, 511, 422, 421, 412, 411, 311],
                        "5. Prometteur" : [525, 524, 523, 522, 521, 515, 514, 513, 425,424, 413,414,415, 315, 314, 313], 
                        "6. Standard" : [535, 534, 443, 434, 343, 334, 325, 324], 
                        "7. Besoin d'attention" : [331, 321, 312, 221, 213, 231, 241, 251],
                        "8. À ne pas perdre" : [255, 254, 245, 244, 253, 252, 243, 242, 235, 234, 225, 224, 153, 152, 145, 143, 142, 135, 134, 133, 125, 124], 
                        "9. À risque" : [155, 154, 144, 214,215,115, 114, 113], 
                        "10. En perdition" : [332, 322, 233, 232, 223, 222, 132, 123, 122, 212, 211], 
                        "11. Perdu" : [111, 112, 121, 131, 141, 151]}
    
    In [ ]:
    #Création de la colonne catégorie client
    df_RFM["client_categ"] = "Non renseigné"
    
    In [ ]:
    #Assignation des segments aux clients
    for client in df_RFM["client_id"]:
        rfm_client = df_RFM.loc[df_RFM["client_id"] == client,"RFM"].item()
        
        for segment in noms_segments.items():
            if rfm_client in segment[1]:
                df_RFM.loc[df_RFM["client_id"] == client,"client_categ"] = segment[0]
    
    In [ ]:
    df_RFM
    
    Out[ ]:
    client_id date R total_session F totalCA_client M RFM client_categ
    1769 c_4973 2023-02-24 5 109 5 3503.94 5 555 1. Meilleur
    549 c_247 2023-02-27 5 90 5 2597.22 5 555 1. Meilleur
    531 c_7403 2023-02-27 5 123 5 2592.73 5 555 1. Meilleur
    1332 c_6635 2023-02-25 5 112 5 3704.54 5 555 1. Meilleur
    533 c_7418 2023-02-27 5 74 5 2792.71 5 555 1. Meilleur
    ... ... ... ... ... ... ... ... ... ...
    7893 c_2417 2022-11-08 1 12 1 196.10 1 111 11. Perdu
    7895 c_3439 2022-11-08 1 5 1 102.27 1 111 11. Perdu
    7897 c_4392 2022-11-08 1 10 1 317.32 1 111 11. Perdu
    7898 c_3024 2022-11-08 1 4 1 179.96 1 111 11. Perdu
    8595 c_6292 2021-03-09 1 1 1 24.24 1 111 11. Perdu

    8596 rows × 9 columns

    In [ ]:
    sns.pairplot(df_RFM[["client_id", "total_session", "totalCA_client", "client_categ"]], hue="client_categ",)
    
    Out[ ]:
    <seaborn.axisgrid.PairGrid at 0x215910e5510>
    Synthèse du classement RFM¶
    In [ ]:
    df_RFM_synthese = df_RFM.groupby("client_categ").agg(
        CA_total = ("totalCA_client","sum"), 
        session_total = ("total_session","sum"),
        client_total = ("client_id","count")).reset_index()
    
    df_RFM_synthese.sort_values("CA_total", ascending=False, inplace=True)
    df_RFM_synthese
    
    Out[ ]:
    client_categ CA_total session_total client_total
    0 1. Meilleur 4250233.37 138072 1671
    3 2. Fidèle 1890145.39 55199 980
    9 8. À ne pas perdre 1336501.61 31184 810
    1 10. En perdition 1055245.36 28207 1525
    7 6. Standard 858618.51 21989 620
    4 3. Potentiellement fidèle 741971.28 23807 832
    2 11. Perdu 340986.04 9010 1062
    6 5. Prometteur 251936.16 4395 261
    5 4. Nouveau client 164546.84 4934 414
    8 7. Besoin d'attention 141262.03 5230 329
    10 9. À risque 111920.42 1923 92

    Demandes de Julie - 5 corrélations :¶

  • le lien entre le genre (categ) d’un client et les catégories des livres (categ) achetés | Test: Khi2 sur tableau de contingence (param) ou Test exact de Fisher (non-param)
  • le lien entre l'âge (numeric) des clients et le montant total (numeric) des achats | Test: Corrélation de Pearson (param) ou Corrélation de Spearman (non-param)
  • le lien entre l'âge (numeric) des clients et la fréquence d’achat* (numeric) | Test: Corrélation de Pearson (param) ou Corrélation de Spearman (non-param)
  • le lien entre l'âge (numeric) des clients et la taille du panier moyen (numeric) | Test: Corrélation de Pearson (param) ou Corrélation de Spearman (non-param)
  • le lien entre l'âge (numeric) des clients et la catégorie des livres achetés (categ) | Test: ANOVA, T-test, Kruskall-Wallis
  • - Lien entre le genre d'un client et les catégories de livres achetés¶

    Test d'association entre 2 variables qualitatives (genre - categ) -> Test Chi2 de contingence :¶
    • Conditions de validité :
      • les 2 variables sont qualitatives
      • effectifs attendus dans chaque cellule > 5 (à vérifier)

    • Hypothèses :
      • nulle H0: Il n'y a pas de lien entre le genre et la catégorie de livres achetés (indépendance)
      • alternative H1: Il y a un lien entre le genre et la catégorie de livres achetés (dépendance)

    • Risque accepté : 5%
    • Suite à envisager -> Test de Cramer-V pour connaitre l'intensité
    In [ ]:
    #Première visualisation
    genre_categ = df_btoc.pivot_table(index='sex', columns='categ', values='id_prod', aggfunc=len).reset_index()
    genre_categ.plot(kind='bar', x='sex');
    
    In [ ]:
    #Création du tableau de contingence
    X = "categ"
    Y = "sex"
    
    df_cont = df_btoc[[X,Y]].pivot_table(index=X, columns=Y, aggfunc=len, margins=True, margins_name="total")
    
    df_cont
    
    Out[ ]:
    sex f m total
    categ
    0 200793 186488 387281
    1 115721 104884 220605
    2 16980 15868 32848
    total 333494 307240 640734
    • effectifs attendus dans chaque cellule > 5 (vérifié)
    In [ ]:
    #Création du dataframe de comparaison (dataframe théorique)
    tx = df_cont.loc[:,["total"]] #récupère la colonne total
    ty = df_cont.loc[["total"],:] #récupère la ligne total
    
    n = len(df_btoc) #récupère le total général (toutes les lignes)
    
    indep = tx.dot(ty) / n #produit matriciel en la colonne total et ligne total
    
    In [ ]:
    #Calcul des écarts entre les 2 dataframe (réel et théorique)
    measure = (df_cont-indep)**2/indep #(Effectifs observés − Effectifs théoriques)² / Effectifs théoriques -> calcul des écarts de chaque cellules
    
    xi_n = measure.sum().sum() #Valeur du Chi2 (somme de tous les écarts)
    
    table = measure/xi_n #En divisant tous les écarts par leur somme totale -> tout est entre 0:1
    
    In [ ]:
    table
    
    Out[ ]:
    sex f m total
    categ
    0 0.133794 0.145227 0.0
    1 0.310415 0.336940 0.0
    2 0.035303 0.038320 0.0
    total 0.000000 0.000000 0.0
    In [ ]:
    #Représentation graphique des différences
    sns.heatmap(table.iloc[:-1,:-1],annot=table.iloc[:-1,:-1]) #Heatmap sans la ligne "total" et la colonne "total"
    plt.title("Tableau de contingence des écarts\nEffectifs observés - Effectifs théoriques");
    
    In [ ]:
    #Copie du dataframe sans la colonnes "total" et la ligne ""total" pour calculer la p-value
    df_cont_sstotal = df_cont.drop(index="total",columns={"total"})
    
    df_cont_sstotal
    
    Out[ ]:
    sex f m
    categ
    0 200793 186488
    1 115721 104884
    2 16980 15868
    In [ ]:
    #Calcul de la p-value
    st_chi2, st_p, st_dof, st_exp = st.chi2_contingency(df_cont_sstotal)
    
    st_p
    
    Out[ ]:
    1.1955928116587024e-05

    Conclusion :

    • p-value largement inférieur à 5% -> le test est significatif et H0 est rejetée
      • Les variables sont donc non-indépendante , il y a bien un lien entre le sexe et le choix de catégorie de livres
    Passons au test de Cramer-V pour connaitre l'intensité de cette association :¶
    • Conditions de validité :
      • les 2 variables sont qualitatives
      • avoir le résultat (la différence total au carré des cellules) du test de Chi2
    • Association jugée forte si coef_cramerv > 0.6
    In [ ]:
    n = df_cont_sstotal.sum().sum()
    min_dim = min(df_cont_sstotal.shape)-1
    
    coef_cramerv = np.sqrt(st_chi2/(n*min_dim))
    
    coef_cramerv
    
    Out[ ]:
    0.005948029928802536

    Conclusion :

    • Le coefficient de Cramer-V est très proche de 0
      • Il n'y a qu'une très faible association entre la variable genre et catégorie

    - Lien entre l'âge des clients et le montant total des achats¶

    Teste de correlation entre 2 variables quantitatives (age - montant total) -> Test à determiner :¶
    Commençons par visualiser ces 2 variables¶
    In [ ]:
    #Création des variables necessaire à l'affichage de la regression linaire - Méthode des moindres carrés (OLS)
    slope, intercept, r_value, p_value, std_er = st.linregress(df_tranches_ageCA["age"],df_tranches_ageCA["CA_total"])
    
    def predict(x):
       return slope * x + intercept
    
    In [ ]:
    #Affichage graphique
    sns.scatterplot(data=df_tranches_ageCA, x="age", y="CA_total")
    
    regress_lin(df_tranches_ageCA["age"],df_tranches_ageCA["CA_total"])
    
    plt.legend(["individu","OLS"])
    
    plt.show()
    

    Correlation légère et négative à première vue -> le CA_total maximum semble diminuer avec l'age

    Vérification et clarification par le biais de tranches d'age¶
    In [ ]:
    df_tranches_ageCA.head()
    
    Out[ ]:
    client_id age CA_total tranche_age
    0 c_1 68.0 629.02 4
    1 c_10 67.0 1353.60 4
    2 c_100 31.0 254.85 1
    3 c_1000 57.0 2291.88 3
    4 c_1001 41.0 1823.85 2
    In [ ]:
    df_tranches_ageCA["tranche_age"].unique()
    
    Out[ ]:
    array([4, 1, 3, 2, 5], dtype=int64)
    In [ ]:
    #Récupération des ages concernés par les tranches pour plus de lisibilité 
    tranches = []
    
    for t in sorted(df_tranches_ageCA["tranche_age"].unique()):
        age_mini = int(df_tranches_ageCA.loc[df_tranches_ageCA["tranche_age"] == t, "age"].min())
        age_max = int(df_tranches_ageCA.loc[df_tranches_ageCA["tranche_age"] == t, "age"].max())
    
        tranches.append(str(age_mini)+"-"+str(age_max))
    
    tranches
    
    Out[ ]:
    ['19-34', '35-50', '51-66', '67-82', '83-94']
    In [ ]:
    # Assignation des tranches d'ages
    for t in sorted(df_tranches_ageCA["tranche_age"].unique()):
        df_tranches_ageCA.loc[df_tranches_ageCA["tranche_age"] == t, "tranche_age"] = tranches[t-1]
    
    In [ ]:
    df_tranches_ageCA.sort_values("tranche_age", ascending=True, inplace=True)
    
    In [ ]:
    #Création d'un dictionnaire du nombre d'effectif par tranche
    groupes = []
    for tranche in sorted(df_tranches_ageCA["tranche_age"].unique()):
        nb_clients = df_tranches_ageCA.loc[df_tranches_ageCA["tranche_age"] == tranche]["tranche_age"].count()
    
        groupes.append(nb_clients)
    
    In [ ]:
    #Représentation graphique
    sns.boxplot(df_tranches_ageCA, x="tranche_age", y="CA_total")
    
    i=0
    for g in groupes:
        plt.text(i,0,"(n={})".format(g), horizontalalignment='center', verticalalignment='top')  
        i+=1
    plt.show()
    

    À prendre note :

    • Les tranches d'age un nombre d'effectifs égal
    • Il semble y avoir 3 groupes qui se dessinent -> 19-34ans | 35-50ans | 51-94ans
    • Il semble y avoir une légère corrélation négative entre l'age et le montant total (surtout à partir de la 2eme tranche)
    Visualisation de la distribution des 2 variables (suivent-elles une loi normale) :¶
    In [ ]:
    sns.histplot(data=df_tranches_ageCA["age"], kde=True)
    
    Out[ ]:
    <Axes: xlabel='age', ylabel='Count'>
    In [ ]:
    sns.histplot(data=df_tranches_ageCA["CA_total"], kde=True)
    
    Out[ ]:
    <Axes: xlabel='CA_total', ylabel='Count'>

    Les 2 variables ne suivent visuellement pas de loi normale
    Vérifions à l'aide du test de Shapiro (quantité d'individu de taille moyenne):

    Test de Shapiro-Wilk sur chacune des 2 variables :

    • Conditions de validité :
      • les 2 variables sont quantitatives
      • loi normale non-necessaire
      • taille d'échantillon petite ou moyenne

    • Hypothèses :
      • nulle H0: La variable suit une loi normale
      • alternative H1: La variable ne suit pas une loi normale
    In [ ]:
    for var in [df_tranches_ageCA["age"], df_tranches_ageCA["CA_total"]]:
        shapiro_value, p_value = st.shapiro(var)
    
        print("La p-value est de la variable {} est de : {} ".format(var.name, p_value))
    
    La p-value est de la variable age est de : 4.628808323714737e-39 
    La p-value est de la variable CA_total est de : 0.0 
    

    Conclusion :

    • Les p-value des 2 variables sont nettement inférieurs au seuil de 0.05 (seuil conventionnel de risque accepté)
      • Les 2 variables ne suivent pas une loi normale
    Nous ne pouvons pas utiliser le test de Pearson (paramétrique) -> Test de Spearman :¶
    • Conditions de validité :

      • les 2 variables sont quantitatives
      • loi normale non-necessaire

    • Hypothèses p-value:

      • nulle H0: Il **n'y a pas de corrélation monotone ** entre l'age et le panier total (indépendance)
      • alternative H1: Il y a une corrélation monotone entre l'age et le panier total (dépendance)

    • Risque accepté : 5%

    • Intensité et sens de corrélation indiqué par le coefficient spearman_r

      • spearman_r = 0 -> pas de corrélation
      • 1 > spearman_r > 0 -> corrélation positive
      • 0 > spearman_r > -1 -> corrélation négative

      À nuancer avec le graphique de début

    In [ ]:
    spearman_r, p_value = st.spearmanr(df_tranches_ageCA["age"],df_tranches_ageCA["CA_total"])
    covar = np.cov(df_tranches_ageCA["age"],-df_tranches_ageCA["CA_total"],ddof=0)
    
    print("Le coefficient de Spearman est de {}".format(round(spearman_r, 3)))
    print("La p-value est de {}".format(p_value))
    
    Le coefficient de Spearman est de -0.185
    La p-value est de 1.0211908275536399e-66
    

    Force de la corrélation:

  • 0.0 < 0.1 pas de corrélation
  • 0.1 < 0.3 faible corrélation
  • 0.3 < 0.5 corrélation moyenne
  • 0.5 < 0.7 corrélation élevée
  • 0.7 < 1 corrélation très élevée

  • Source déterminant la force et la direction de la correlation

    Conclusion :

    • spearman_r = -0.19 = faible corrélation (inversée) plus l'age augmente plus le montant total diminue
    • p-value < 0.05* (seuil conventionnel) = correlation significative

    - Lien entre l'âge des clients et la fréquence d’achat¶

    Commençons par visualiser ces 2 variables :¶
    In [ ]:
    #Création du dataframe sur le nombre d'achat par client
    df_temp = df_btoc.groupby(["client_id", "age","session_id"]).agg(livres_session=("session_id","count")).reset_index()
    
    df_age_freq = df_temp.groupby(["client_id", "age"]).agg(session_total=("livres_session","count")).reset_index()
    
    df_age_freq.head()
    
    Out[ ]:
    client_id age session_total
    0 c_1 68 34
    1 c_10 67 34
    2 c_100 31 5
    3 c_1000 57 94
    4 c_1001 41 47
    In [ ]:
    df_verif = df_btoc.groupby(["client_id","session_id"]).agg(session_total=("session_id","count")).reset_index()
    
    df_verif.loc[df_verif["client_id"]=="c_100"]
    
    Out[ ]:
    client_id session_id session_total
    68 c_100 s_23060 1
    69 c_100 s_240961 1
    70 c_100 s_270695 1
    71 c_100 s_53051 2
    72 c_100 s_73929 3
    In [ ]:
    #Affichage de la fréquence d'achat global
    nb_clients = df_age_freq["client_id"].count()
    nb_session = df_age_freq["session_total"].sum()
    
    freq_globale = round(nb_session/nb_clients, 2) #similaire à df_age_freq["session_total"].mean()
    
    freq_globale
    
    Out[ ]:
    37.51

    Un client fait en moyenne 37.5 commandes

    In [ ]:
    #Création de la colonne de fréquence -> nombre de session / nombre total de session
    df_age_freq["freq_client"] = df_age_freq["session_total"]/df_age_freq["session_total"].sum()
    
    In [ ]:
    #Récupération des tranches d'ages
    df_age_freq = df_age_freq.merge(df_tranches_ageCA[["client_id", "tranche_age"]], on="client_id")
    
    In [ ]:
    df_age_freq.head()
    
    Out[ ]:
    client_id age session_total freq_client tranche_age
    0 c_1 68 34 0.000105 67-82
    1 c_10 67 34 0.000105 67-82
    2 c_100 31 5 0.000016 19-34
    3 c_1000 57 94 0.000292 51-66
    4 c_1001 41 47 0.000146 35-50
    In [ ]:
    #Création d'un dictionnaire du nombre d'effectifs par tranche
    groupes = []
    for tranche in sorted(df_age_freq["tranche_age"].unique()):
        nb_clients = df_age_freq.loc[df_age_freq["tranche_age"] == tranche]["tranche_age"].count()
    
        groupes.append(nb_clients)
    
    In [ ]:
    data = df_age_freq.sort_values("tranche_age", ascending=True)
    x = "tranche_age"
    y = "freq_client"
    
    sns.boxplot(data=data , x="tranche_age", y="freq_client")
    
    i=0
    for g in groupes:
        plt.text(i,0,"(n={})".format(g), horizontalalignment='center', verticalalignment='top')  
        i+=1
    plt.show()
    

    Nous savons d'avance que la variable age ne suit pas une loi normale

    Nous ne pouvons pas utiliser le test de Pearson (paramétrique) -> Test de Spearman :¶
    • Conditions de validité :
      • les 2 variables sont quantitatives
      • loi normale non-necessaire

    • Hypothèses p-value:
      • nulle H0: Il n'y a pas de corrélation monotone entre l'age et la fréquence d'achat (indépendance)
      • alternative H1: Il y a une corrélation monotone entre l'age et la fréquence d'achat (dépendance)

    • Risque accepté : 5%
    • Intensité et sens de corrélation indiqué par le coefficient spearman_r
      • spearman_r = 0 -> pas de corrélation
      • 1 > spearman_r > 0 -> corrélation positive
      • 0 > spearman_r > -1 -> corrélation négative À nuancer avec le graphique de début
    In [ ]:
    spearman_r, p_value = st.spearmanr(df_age_freq["age"], df_age_freq["freq_client"])
    
    print("Le coefficient de Spearman est de {}".format(round(spearman_r, 3)))
    print("La p-value est de {}".format(p_value))
    
    Le coefficient de Spearman est de 0.212
    La p-value est de 6.629168433162815e-88
    

    Force de la corrélation:

  • 0.0 < 0.1 pas de corrélation
  • 0.1 < 0.3 faible corrélation
  • 0.3 < 0.5 corrélation moyenne
  • 0.5 < 0.7 corrélation élevée
  • 0.7 < 1 corrélation très élevée

  • Source déterminant la force et la direction de la correlation

    Conclusion préliminaire:

    • spearman_r = + 0.21 = faible corrélation positive plus l'age augmente plus la frequence d'achats augmente
    • p-value < 0.05 (seuil conventionnel) = H0 rejeté -> resultat significatif

    Ce résultat est étonnant au vu du boxplot au dessus, vérifions en groupant les ages avec leur moyenne de fréquence :

    In [ ]:
    #Création du data frame necessaire
    df_test= df_age_freq.groupby("age")["freq_client"].mean().reset_index()
    df_test.head()
    
    Out[ ]:
    age freq_client
    0 19 0.000062
    1 20 0.000056
    2 21 0.000057
    3 22 0.000062
    4 23 0.000056
    In [ ]:
    #Création des variables necessaire à l'affichage de la regression linaire - Méthode des moindres carrés (OLS)
    slope, intercept, r_value, p_value, std_er = st.linregress(df_test["age"],df_test["freq_client"])
    
    def predict(x):
       return slope * x + intercept
    
    In [ ]:
    sns.scatterplot(data=df_test, x="age", y="freq_client")
    regress_lin(df_test["age"],df_test["freq_client"])
    
    plt.legend(["individu","OLS"])
    
    Out[ ]:
    <matplotlib.legend.Legend at 0x21595e98710>

    En effet, le résultat confirme l'hypothèse vu sur le boxplot : il semble y avoir une corrélation négative à partir de 32 ans environs et faussée par le premier groupe
    Vérifion en isolant les 2 groupes (19-à définir | à définir-94ans):

    In [ ]:
    #Determinons l'age bascule (autour des 32 ans) où toutes les premières valeurs sont supérieures à la moyenne des valeurss (choisi arbitrairement)
    agemini_groupe_valide = df_test.loc[df_test["freq_client"] > df_test["freq_client"].mean()]["age"].min()
    
    In [ ]:
    #Création du nouveau dataframe avec le bon groupe d'individu
    df_groupe_valide = df_age_freq.loc[df_age_freq["age"] >= agemini_groupe_valide]
    
    In [ ]:
    groupes = []
    for tranche in sorted(df_groupe_valide["tranche_age"].unique()):
        nb_clients = df_groupe_valide.loc[df_groupe_valide["tranche_age"] == tranche]["tranche_age"].count()
    
        groupes.append(nb_clients)
    
    sns.boxplot(data=df_groupe_valide.sort_values("age", ascending=True), x="tranche_age", y="freq_client")
    i=0
    for g in groupes:
        plt.text(i,0,"(n={})".format(g), horizontalalignment='center', verticalalignment='top')  
        i+=1
    plt.show()
    
    In [ ]:
    sns.scatterplot(data=df_groupe_valide, x="age", y="freq_client")
    regress_lin(df_groupe_valide["age"],df_groupe_valide["freq_client"])
    plt.show()
    
    In [ ]:
    spearman_r, p_value = st.spearmanr(df_groupe_valide["age"], df_groupe_valide["freq_client"])
    
    print("Le coefficient de Spearman est de {} sur les individus de {} à {} ans".format(round(spearman_r, 3), agemini_groupe_valide,df_groupe_valide["age"].max() ))
    print("La p-value est de {}".format(p_value))
    
    Le coefficient de Spearman est de -0.148 sur les individus de 32 à 94 ans
    La p-value est de 3.6670140717793306e-32
    

    Conclusion finale:

    Sur les 32-94 ans

    • Coefficient de Spearman = -0.15 -> corrélation faible et négative entre l'age et la fréquence d'achat
    • p-value fiable à 99% -> H0 rejeté -> test significatif

    - Lien entre l'âge des clients et la taille du panier moyen¶

    Commençons par visualiser ces 2 variables :¶
    In [ ]:
    #Création d'un premier dataframe avec les paniers pour chaque client
    df_panier = df_btoc.groupby(["client_id", "age", "session_id"]).agg(CA_session=("price","sum")).reset_index()
    
    df_panier.head()
    
    Out[ ]:
    client_id age session_id CA_session
    0 c_1 68 s_101417 15.87
    1 c_1 68 s_105105 62.96
    2 c_1 68 s_114737 92.62
    3 c_1 68 s_120172 44.29
    4 c_1 68 s_134971 10.30
    In [ ]:
    #Création du dataframe aggrégé sur les clients
    df_panier_moy = df_panier.groupby(["client_id", "age"]).agg(nb_paniers=("session_id","count"), CA_total_paniers=("CA_session","sum")).reset_index()
    
    df_panier_moy.head()
    
    Out[ ]:
    client_id age nb_paniers CA_total_paniers
    0 c_1 68 34 629.02
    1 c_10 67 34 1353.60
    2 c_100 31 5 254.85
    3 c_1000 57 94 2291.88
    4 c_1001 41 47 1823.85
    In [ ]:
    #Création de la colonne avec le panier moyen (CA / nb de panier)
    df_panier_moy["panier_moyen"] = df_panier_moy["CA_total_paniers"] / df_panier_moy["nb_paniers"]
    
    In [ ]:
    #Rapprochement des tranches d'ages
    df_panier_moy = df_panier_moy.merge(df_tranches_ageCA[["client_id", "tranche_age"]], on="client_id")
    
    In [ ]:
    df_panier_moy.head()
    
    Out[ ]:
    client_id age nb_paniers CA_total_paniers panier_moyen tranche_age
    0 c_1 68 34 629.02 18.500588 67-82
    1 c_10 67 34 1353.60 39.811765 67-82
    2 c_100 31 5 254.85 50.970000 19-34
    3 c_1000 57 94 2291.88 24.381702 51-66
    4 c_1001 41 47 1823.85 38.805319 35-50
    In [ ]:
    sns.scatterplot(data=df_panier_moy.sort_values("age",ascending=True), x="age", y="panier_moyen", hue="tranche_age")
    regress_lin(df_panier_moy["age"], df_panier_moy["panier_moyen"])
    
    Out[ ]:
    [<matplotlib.lines.Line2D at 0x2159720d750>]

    Premier aperçu: les 19-32 ans on un panier moyen nettement supérieur aux autres tranches d'ages ce qui a un impact sur la corrélation qui sera potentiellement fortement négative

    In [ ]:
    sns.boxplot(df_panier_moy.sort_values("age",ascending=True), x="tranche_age", y="panier_moyen", showmeans=True, showfliers = False )
    
    i=0
    for g in groupes:
        plt.text(i,5,"(n={})".format(g), horizontalalignment='center', verticalalignment='top')  
        i+=1
    plt.show()
    
    Nous ne pouvons pas utiliser le test de Pearson (paramétrique) -> Test de Spearman :¶
    • Conditions de validité :
      • les 2 variables sont quantitatives
      • loi normale non-necessaire

    • Hypothèses p-value:
      • nulle H0: Il n'y a pas de corrélation monotone entre l'age et la fréquence d'achat (indépendance)
      • alternative H1: Il y a une corrélation monotone entre l'age et la fréquence d'achat (dépendance)

    • Risque accepté : 5%
    • Intensité et sens de corrélation indiqué par le coefficient spearman_r
      • spearman_r = 0 -> pas de corrélation
      • 1 > spearman_r > 0 -> corrélation positive
      • 0 > spearman_r > -1 -> corrélation négative À nuancer avec le graphique de début
    In [ ]:
    spearman_r, p_value= st.spearmanr(df_panier_moy["age"],df_panier_moy["panier_moyen"])
    
    print("Le coefficient de spearman est de {}".format(round(spearman_r, 3)))
    print("La p-value est de {}".format(p_value))
    
    Le coefficient de spearman est de -0.701
    La p-value est de 0.0
    

    Conclusion :

    • spearman_r = - 0.70 = forte corrélation négative -> plus l'age augmente plus le panier moyen diminu
    • p-value = 0 < 0.05* (seuil conventionnel) = H0 rejeté -> test significative -> variables dépendantes

    Force de la corrélation:

  • 0.0 < 0.1 pas de corrélation
  • 0.1 < 0.3 faible corrélation
  • 0.3 < 0.5 corrélation moyenne
  • 0.5 < 0.7 corrélation élevée
  • 0.7 < 1 corrélation très élevée

  • Source déterminant la force et la direction de la correlation
    Vérifion cette corrélation en ne prenant pas en compte le groupe jeune (19-32ans)¶
    In [ ]:
    #Création du df concerné
    df_groupe_valide = df_panier_moy.loc[df_panier_moy["age"] >= agemini_groupe_valide]
    df_groupe_valide.min()
    
    Out[ ]:
    client_id             c_1
    age                    32
    nb_paniers              1
    CA_total_paniers     6.31
    panier_moyen         4.15
    tranche_age         19-34
    dtype: object
    In [ ]:
    sns.scatterplot(data=df_groupe_valide.sort_values("age",ascending=True), x="age", y="panier_moyen", hue="tranche_age")
    regress_lin(df_groupe_valide["age"], df_groupe_valide["panier_moyen"])
    
    Out[ ]:
    [<matplotlib.lines.Line2D at 0x215953f6d90>]
    In [ ]:
    spearman_r, p_value= st.spearmanr(df_groupe_valide["age"],df_groupe_valide["panier_moyen"])
    
    print("Le coefficient de Pearson est de {}".format(round(spearman_r, 3)))
    print("La p-value est de {}".format(p_value))
    
    Le coefficient de Pearson est de -0.402
    La p-value est de 1.2270547101185366e-244
    

    Conclusion finale :
    Sur les 32-94 ans

    • Spearman_r = - 0.40 -> corrélation négative d'intensité moyenne
    • p-value fiable à 99% -> H0 rejeté -> test statistique fiable

    - Lien entre l'âge des clients et la catégorie des livres achetés¶

    Commençons par visualiser ces 3 variables :¶
    In [ ]:
    #Création du dataframe concerné
    x = "categ"
    y = "age"
    
    df_age_cat = df_btoc[[x,y]]
    
    In [ ]:
    sns.boxplot(df_age_cat, x="categ", y="age", showfliers=True, showmeans=True)
    
    i=0
    for cat in sorted(df_age_cat["categ"].unique()):
        n_cat = len(df_age_cat.loc[df_age_cat["categ"] == cat])
    
        plt.text(i,0,"(n={})".format(n_cat), horizontalalignment='center', verticalalignment='top')
        i+=1
    
    plt.show()
    

    Les 3 catégories semblent assez différentes, même si l'ordre de grandeur de ces écarts n'est pas très grand, la question est de savoir si ces écarts sont significatifs ou pas.

    C'est l'ANOVA qui nous permettra de répondre à cette question.

    Teste d'association entre une variable quantitative et 1 variables qualitatives (age - categ) -> Test ANOVA :¶

    (OneWay ANOVA)

    • Conditions de validité :
      • une variable est quantitative
      • une variable est qualitatives
      • plus de 2 modalités
      • les individus sont indépendants
      • égalité des variances entre les modalités (à vérifier)
      • loi normale
    • Hypothèses p-value:
      • nulle H0: Les moyennes de tous les groupes sont égales entre l'age et les catégories (variables non associées)
      • alternative H1: La moyenne de au moins 1 groupe est significativement différent entre l'age et les catégories (certaines variables associées)

    • Risque accepté : 5%
    In [ ]:
    len(df_age_cat)
    
    Out[ ]:
    640734

    Je vais outre-passer le test de Shapiro pour 2 raisons:

  • Je sais que la variable quantitative age ne suit pas une loi normale
  • Mes données comprennent 640498 individus et si "[...] le modèle linéaire est robuste, c'est-à-dire que sous réserve qu'il y ait une taille d'échantillon suffisante, les résultats du modèle linéaire restent valables (propriétés asymptotiques)"
  • Vaudor L (2015). “Non-respect des hypothèses du modèle linéaire (ANOVA, régression): c'est grave, docteur??”
    In [ ]:
    #Création des variables par categorie
    cat_0 = df_age_cat.loc[df_age_cat["categ"] == 0]["age"]
    cat_1 = df_age_cat.loc[df_age_cat["categ"] == 1]["age"]
    cat_2 = df_age_cat.loc[df_age_cat["categ"] == 2]["age"]
    
    In [ ]:
    #ANOVA test
    st.f_oneway(cat_0, cat_1, cat_2)
    
    Out[ ]:
    F_onewayResult(statistic=39705.51969324281, pvalue=0.0)

    Conclusion :

    • p-value < 5% -> H0 rejeté -> l'age a bien un effet sur au moins une catégorie de livres achetés
    La vérification de ce test ce valide suivant ces différentes hypothèses:¶
  • l’indépendance entre les échantillons de chaque groupe
  • l’égalité des variances | Test de Levene (car pas de variable suivant une loi normale)
  • la normalité des résidus | Test de Shapiro -> inutile avec autant d'individus
  • L'indépendance :¶

    Il y a bien une indépendance entre les catégories 0, 1 et 2

    L'égalité des variances :¶
    In [ ]:
    sns.boxplot(df_age_cat, x="categ", y="age", showfliers=False, showmeans=True)
    
    i=0
    for cat in sorted(df_age_cat["categ"].unique()):
        n_cat = len(df_age_cat.loc[df_age_cat["categ"] == cat])
    
        plt.text(i,0,"(n={})".format(n_cat), horizontalalignment='center', verticalalignment='top')
        i+=1
    
    plt.show()
    
    In [ ]:
    #Création d'un dataframe pour vérifier si les variances sont égales
    df_age_cat.groupby("categ")['age'].agg('var')
    
    Out[ ]:
    categ
    0    132.620783
    1    250.414686
    2     98.007304
    Name: age, dtype: float64

    Vérification si les variances sont significativement différentes avec le test de Levene (distribution non Gaussienne) :

  • H0 : Les variances de chaque groupe sont égales si p-value > 5%
  • H1 : La variances d'au moins un groupe n'est pas égale au autres < 5%
  • In [ ]:
    cat_0 = df_btoc["age"][df_btoc["categ"] == 0]
    cat_1 = df_btoc["age"][df_btoc["categ"] == 1]
    cat_2 = df_btoc["age"][df_btoc["categ"] == 2]
    st.levene(cat_0, cat_1, cat_2)
    
    Out[ ]:
    LeveneResult(statistic=24651.780367284304, pvalue=0.0)

    Conclusion :

    • p-value fiable à 99% -> H0 rejeté -> il y a au moins un groupe qui à une variance différente
    • Pas d'égalité des variances -> Test de Kruskal-Wallis (test non-paramétrique) necessaire
    Test final de Kruskal-Wallis¶
    • Conditions de validité :
      • une variable est quantitative
      • une variable est qualitatives
      • plus de 2 modalités
      • loi normale non-necessaire
    • Hypothèses p-value:
      • nulle H0: La médiane de tous les groupes sont égales entre l'age et les catégories (variables non associées)
      • alternative H1: La médiane de au moins 1 groupe est significativement différent entre l'age et les catégories (certaines variables associées)

    • Risque accepté : 5%
    In [ ]:
    st.kruskal(cat_0, cat_1, cat_2)
    
    Out[ ]:
    KruskalResult(statistic=71359.73412120914, pvalue=0.0)

    Conclusion :

    • p-value = 0 < 0.5 -> H0 rejeté -> Il y a une différence de médiane entre au moins 2 groupe et par conséquence il y a une association entre l'age et la catégorie de livre choisi

    Optionnel pour la présentation powerpoint¶

    Proportion catégorie / tranche d'age¶
    In [ ]:
    #Création d'un dataframe pour visualiser des informations sur powerpoint -> proportion catégorie par tranche d'age
    df_temp = df_btoc.merge(df_age_freq[["client_id","tranche_age"]], on="client_id")
    
    df_temp.head()
    
    Out[ ]:
    client_id date monthly session_id id_prod price categ age sex jour heure tranche_age
    0 c_329 2021-03-01 2021-03-01 s_1 0_1259 11.99 0 56 f Monday 00 51-66
    1 c_329 2022-10-01 2022-10-01 s_275943 0_1259 11.99 0 56 f Saturday 00 51-66
    2 c_329 2022-12-01 2022-12-01 s_305291 0_1259 11.99 0 56 f Thursday 00 51-66
    3 c_329 2023-01-01 2023-01-01 s_320153 0_1259 11.99 0 56 f Sunday 00 51-66
    4 c_329 2021-11-23 2021-11-01 s_123998 1_397 18.99 1 56 f Tuesday 18 51-66
    In [ ]:
    df_tempT = df_temp.pivot_table(index="tranche_age", values="price", aggfunc="count", columns="categ").reset_index()
    df_tempT.head()
    
    Out[ ]:
    categ tranche_age 0 1 2
    0 19-34 55809 42164 30100
    1 35-50 248250 80318 1190
    2 51-66 58422 65062 1037
    3 67-82 21550 28838 441
    4 83-94 3250 4223 80
    Proportion CA / tranche d'age | age¶
    In [ ]:
    df_tranches_ageCA
    
    Out[ ]:
    client_id age CA_total tranche_age
    6086 c_6491 27.0 302.71 19-34
    2295 c_3068 33.0 664.12 19-34
    7664 c_7916 19.0 487.36 19-34
    4675 c_5218 26.0 684.68 19-34
    4674 c_5217 19.0 2039.50 19-34
    ... ... ... ... ...
    5285 c_577 94.0 1829.96 83-94
    7173 c_7473 87.0 1434.75 83-94
    5306 c_5789 84.0 1151.39 83-94
    401 c_1360 89.0 1643.92 83-94
    8159 c_8362 94.0 842.87 83-94

    8596 rows × 4 columns

    In [ ]:
    #Top CA
    df_temp = df_tranches_ageCA.groupby("age").agg(panier_moyen=("CA_total","mean"),nb_client=("client_id","count")).sort_values("panier_moyen", ascending=False)
    df_temp.head()
    
    Out[ ]:
    panier_moyen nb_client
    age
    39.0 1769.851677 155
    40.0 1765.272469 162
    38.0 1705.251739 115
    45.0 1655.286131 199
    49.0 1633.616625 160
    In [ ]:
    sns.scatterplot(df_temp, x="age", y="panier_moyen", hue="nb_client", size="nb_client", palette="dark:red")
    
    plt.ylabel("Panier moyen (en €)")
    plt.title("Panier moyen selon l'age")
    plt.legend(title="Nombre de clients")
    plt.grid(which="both")
    
    In [ ]:
    #Flop CA
    df_tranches_ageCA.groupby("age").agg(CA_total=("CA_total","sum")).sort_values("CA_total", ascending=True).head()
    
    Out[ ]:
    CA_total
    age
    92.0 2815.45
    94.0 3249.84
    93.0 4251.22
    88.0 4945.03
    91.0 5059.18
    In [ ]:
    df_tranches_ageCA.groupby("tranche_age").agg(CA_total=("CA_total","sum"))
    
    Out[ ]:
    CA_total
    tranche_age
    19-34 3732990.03
    35-50 4386638.92
    51-66 2041793.00
    67-82 854416.21
    83-94 127528.85
    Proportion volume / age¶
    In [ ]:
    #Création d'un dataframe pour visualiser des informations sur powerpoint -> proportion catégorie par tranche d'age
    df_temp = df_btoc.merge(df_age_freq[["client_id","tranche_age"]], on="client_id")
    
    df_temp.head()
    
    Out[ ]:
    client_id date monthly session_id id_prod price categ age sex jour heure tranche_age
    0 c_329 2021-03-01 2021-03-01 s_1 0_1259 11.99 0 56 f Monday 00 51-66
    1 c_329 2022-10-01 2022-10-01 s_275943 0_1259 11.99 0 56 f Saturday 00 51-66
    2 c_329 2022-12-01 2022-12-01 s_305291 0_1259 11.99 0 56 f Thursday 00 51-66
    3 c_329 2023-01-01 2023-01-01 s_320153 0_1259 11.99 0 56 f Sunday 00 51-66
    4 c_329 2021-11-23 2021-11-01 s_123998 1_397 18.99 1 56 f Tuesday 18 51-66
    In [ ]:
    df_temp = df_temp.groupby("age").agg(nb_book=("price","count"))
    df_temp.sort_values("nb_book",ascending=False).head()
    
    Out[ ]:
    nb_book
    age
    35 25245
    44 25101
    45 24905
    37 23693
    43 22116
    In [ ]:
    df_temp.sort_values("nb_book",ascending=True).head()
    
    Out[ ]:
    nb_book
    age
    92 170
    94 202
    93 238
    88 278
    91 312
    Précisions B2B¶
    In [ ]:
    sns.pairplot(data=df_btob)
    
    Out[ ]:
    <seaborn.axisgrid.PairGrid at 0x215952dc450>
    In [ ]:
    df_btob.head()
    
    Out[ ]:
    date monthly session_id id_prod price categ client_id age sex
    84 2021-03-04 07:26:01 2021-03-01 s_1519 0_1259 11.99 0 c_1609 43 m
    85 2021-06-24 09:40:37 2021-06-01 s_53058 0_1259 11.99 0 c_1609 43 m
    86 2021-06-24 14:40:37 2021-06-01 s_53165 0_1259 11.99 0 c_1609 43 m
    87 2021-09-26 21:30:08 2021-09-01 s_96014 0_1259 11.99 0 c_1609 43 m
    88 2021-10-16 03:22:15 2021-10-01 s_105418 0_1259 11.99 0 c_1609 43 m
    In [ ]:
    df_btob.set_index("date")["price"].resample("m").sum().plot(kind="line")
    plt.grid(which="both")
    
    In [ ]:
    df_custom_btob = df_btob.groupby(["client_id", "session_id", "age"]).agg(
        CA_panier=("price","sum"), nb_book=("price","count")).reset_index()
    
    df_custom_btob.head()
    
    Out[ ]:
    client_id session_id age CA_panier nb_book
    0 c_1609 s_10008 43 9.04 1
    1 c_1609 s_100109 43 23.37 2
    2 c_1609 s_100157 43 15.99 1
    3 c_1609 s_100170 43 9.80 1
    4 c_1609 s_100243 43 48.28 4
    In [ ]:
    df_custom_btob = df_custom_btob.groupby(["client_id", "age"]).agg(
        nb_session=("session_id","count"),
        CA_total=("CA_panier","sum"), 
        panier_moyen=("CA_panier","mean"), 
        total_book=("nb_book","sum"), 
        nb_book_mean=("nb_book","mean")).reset_index()
    
    df_custom_btob.head()
    
    Out[ ]:
    client_id age nb_session CA_total panier_moyen total_book nb_book_mean
    0 c_1609 43 10997 326039.89 29.648076 25586 2.326635
    1 c_3454 54 5571 114110.57 20.482960 6793 1.219350
    2 c_4958 24 3851 290227.03 75.364069 5222 1.356011
    3 c_6714 55 2620 153918.60 58.747557 9199 3.511069
    In [ ]:
    #Références particulières?
    df_btob.groupby("id_prod").agg(
        nb_book=("price","count"), 
        CA_total=("price","sum")).sort_values("CA_total",ascending=False).head(5)
    
    Out[ ]:
    nb_book CA_total
    id_prod
    2_112 98 6621.86
    2_209 89 6229.11
    2_110 96 5976.00
    2_135 85 5864.15
    2_39 96 5567.04
    In [ ]:
    #Références particulières?
    df_btob.groupby("id_prod").agg(
        nb_book=("price","count"), 
        CA_total=("price","sum")).sort_values("nb_book",ascending=False).head(5)
    
    Out[ ]:
    nb_book CA_total
    id_prod
    1_403 166 2986.34
    1_498 160 3739.20
    1_395 153 4435.47
    1_417 146 3064.54
    1_400 141 2340.60
    In [ ]:
    df_temp = df_btob.groupby("categ").agg(ca_total=("price","sum"), nb_livre=("price","count"), prix_moy_livre=("price","mean"))
    
    df_temp
    
    Out[ ]:
    ca_total nb_livre prix_moy_livre
    categ
    0 300530.28 28178 10.665423
    1 307555.25 14987 20.521469
    2 276210.56 3635 75.986399
    In [ ]:
    df_temp["ca_total"] = df_temp["ca_total"].round(0)
    
    In [ ]:
    plt.subplot(121)
    plt.pie(x=df_temp["ca_total"], autopct="%.2f", pctdistance=0.5, colors=["black", "grey","red"]);
    
    plt.subplot(122)
    plt.pie(x=df_temp["nb_livre"], autopct="%.2f", pctdistance=0.5,colors=["black", "grey","red"]);
    
    plt.legend(["0","1","2"], bbox_to_anchor=(1.05, 1))
    
    Out[ ]:
    <matplotlib.legend.Legend at 0x21597885010>
    In [ ]: