Cours
L’économiste italien Vilfredo Pareto énonce que 80 % des effets proviennent de 20 % des causes : c’est la règle du 80/20, ou principe de Pareto. De la même façon, 80 % du chiffre d’affaires de nombreuses entreprises proviennent de 20 % de leurs clients. Les entreprises doivent donc identifier ces clients clés et entretenir la relation avec eux pour sécuriser des revenus récurrents. Pour bâtir une relation durable, elles peuvent mettre en place des programmes de fidélité comme des remises, offres, coupons, points bonus et cadeaux.
Cibler un nouveau client coûte plus cher que fidéliser un client existant, car vous n’avez pas à consacrer autant de ressources, de temps et d’efforts pour l’acquérir. Il s’agit surtout de conserver la satisfaction des clients actuels. Les analystes mesurent précisément le coût d’acquisition client via la CLTV (Customer Lifetime Value). La CLTV indique le revenu total généré par un client sur l’ensemble de la relation. Elle aide les entreprises à se concentrer sur les clients à fort potentiel, susceptibles de générer davantage de revenus à l’avenir.
Customer Lifetime Value (CLTV)
« La Customer Lifetime Value est une valeur monétaire représentant le revenu ou le profit qu’un client apportera à l’entreprise sur toute la durée de la relation ». La CLTV met en lumière les avantages d’acquérir des clients sur le long terme plutôt que des clients à court terme. La customer lifetime value (CLV) permet de répondre aux questions commerciales essentielles pour toute entreprise :
- Comment identifier les clients les plus rentables ?
- Comment proposer la meilleure offre et maximiser la marge ?
- Comment segmenter les clients rentables ?
- Quel budget allouer à l’acquisition de clients ?
Calculer la Customer Lifetime Value
Il existe de nombreuses approches pour calculer la CLTV. Chacun peut avoir sa vision. Pour la calculer, il faut des données historiques client, ce qui ne permet pas d’estimer la valeur de nouveaux clients. Pour contourner ce problème, les analystes conçoivent des modèles de machine learning afin de prédire la CLTV des nouveaux clients. Voici quelques approches de calcul de la CLTV :
1) Additionner les profits/recettes par client sur une période donnée. Par exemple, si un client est avec vous depuis 3 ans, vous pouvez sommer tous les profits sur ces 3 ans. Vous pouvez ensuite en calculer la moyenne annuelle, semestrielle ou mensuelle. En revanche, cette méthode ne permet pas de créer un modèle prédictif pour de nouveaux clients.
2) Construire un modèle de régression pour les clients existants. Utilisez les données des six derniers mois comme variables explicatives et le revenu total sur trois ans comme variable cible, puis entraînez un modèle de régression.


3) La CLTV peut aussi être estimée avec les valeurs RFM (Récence, Fréquence, Montant). Pour plus de détails, consultez mon tutoriel.
4) À l’aide de l’équation suivante : CLTV = ((valeur moyenne de commande × fréquence d’achat)/taux d’attrition) × marge bénéficiaire.
Customer Value = Average Order Value * Purchase Frequency
-
Valeur moyenne de commande (AOV) : c’est le ratio entre le revenu total et le nombre total de commandes. Elle représente le montant moyen dépensé par commande.
Average Order Value = Total Revenue / Total Number of Orders -
Fréquence d’achat (PF) : c’est le ratio entre le nombre total de commandes et le nombre total de clients. Elle représente le nombre moyen de commandes par client.
Purchase Frequency = Total Number of Orders / Total Number of Customers -
Taux d’attrition (churn rate) : pourcentage de clients qui n’ont pas repassé commande.
-
Durée de vie client : période pendant laquelle le client passe des commandes de manière continue.
Customer Lifetime=1/Churn Rate -
Taux de réachat (repeat rate) : ratio entre le nombre de clients ayant passé plus d’une commande et le nombre total de clients uniques. Exemple : si vous avez 10 clients sur un mois et que 4 reviennent, le taux de réachat est de 40 %.
Churn Rate= 1-Repeat Rate
Implémentation de la CLTV en Python (avec la formule)
Importer les bibliothèques nécessaires
#import modules
import pandas as pd # for dataframes
import matplotlib.pyplot as plt # for plotting graphs
import seaborn as sns # for plotting graphs
import datetime as dt
import numpy as np
Charger le jeu de données
Commençons par charger le jeu de données Online Retail avec la fonction de lecture CSV de pandas. Vous pouvez télécharger les données ici.
data = pd.read_excel("Online_Retail.xlsx")
data.head()
| InvoiceNo | StockCode | Description | Quantity | InvoiceDate | UnitPrice | CustomerID | Country | |
|---|---|---|---|---|---|---|---|---|
| 0 | 536365 | 85123A | WHITE HANGING HEART T-LIGHT HOLDER | 6 | 2010-12-01 08:26:00 | 2.55 | 17850.0 | United Kingdom |
| 1 | 536365 | 71053 | WHITE METAL LANTERN | 6 | 2010-12-01 08:26:00 | 3.39 | 17850.0 | United Kingdom |
| 2 | 536365 | 84406B | CREAM CUPID HEARTS COAT HANGER | 8 | 2010-12-01 08:26:00 | 2.75 | 17850.0 | United Kingdom |
| 3 | 536365 | 84029G | KNITTED UNION FLAG HOT WATER BOTTLE | 6 | 2010-12-01 08:26:00 | 3.39 | 17850.0 | United Kingdom |
| 4 | 536365 | 84029E | RED WOOLLY HOTTIE WHITE HEART. | 6 | 2010-12-01 08:26:00 | 3.39 | 17850.0 | United Kingdom |
Supprimer les doublons
Il arrive que le jeu de données soit « bruité ». Vous pouvez avoir des doublons qui faussent l’analyse. En Python, pandas propose la fonction drop_duplicates(), qui supprime les enregistrements répétés.
filtered_data=data[['Country','CustomerID']].drop_duplicates()
Passons aux insights
#Top ten country's customer
filtered_data.Country.value_counts()[:10].plot(kind='bar')
<matplotlib.axes._subplots.AxesSubplot at 0x7fe677a887f0>

Dans ce jeu de données, on observe que la majorité des clients viennent du « United Kingdom ». Vous pouvez donc filtrer les clients du Royaume-Uni.
uk_data=data[data.Country=='United Kingdom']
uk_data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 495478 entries, 0 to 541893
Data columns (total 8 columns):
InvoiceNo 495478 non-null object
StockCode 495478 non-null object
Description 494024 non-null object
Quantity 495478 non-null int64
InvoiceDate 495478 non-null datetime64[ns]
UnitPrice 495478 non-null float64
CustomerID 361878 non-null float64
Country 495478 non-null object
dtypes: datetime64[ns](1), float64(2), int64(1), object(4)
memory usage: 34.0+ MB
La fonction describe() de pandas est très pratique pour obtenir des statistiques descriptives. Elle renvoie le nombre d’observations, la moyenne, l’écart type, les valeurs minimale et maximale, ainsi que les quartiles.
uk_data.describe()
| Quantity | UnitPrice | CustomerID | |
|---|---|---|---|
| count | 495478.000000 | 495478.000000 | 361878.000000 |
| mean | 8.605486 | 4.532422 | 15547.871368 |
| std | 227.588756 | 99.315438 | 1594.402590 |
| min | -80995.000000 | -11062.060000 | 12346.000000 |
| 25% | 1.000000 | 1.250000 | 14194.000000 |
| 50% | 3.000000 | 2.100000 | 15514.000000 |
| 75% | 10.000000 | 4.130000 | 16931.000000 |
| max | 80995.000000 | 38970.000000 | 18287.000000 |
Ici, on remarque que certains clients ont des quantités négatives, ce qui est impossible. Filtrons donc les lignes avec une quantité strictement positive.
uk_data = uk_data[(uk_data['Quantity']>0)]
uk_data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 486286 entries, 0 to 541893
Data columns (total 8 columns):
InvoiceNo 486286 non-null object
StockCode 486286 non-null object
Description 485694 non-null object
Quantity 486286 non-null int64
InvoiceDate 486286 non-null datetime64[ns]
UnitPrice 486286 non-null float64
CustomerID 354345 non-null float64
Country 486286 non-null object
dtypes: datetime64[ns](1), float64(2), int64(1), object(4)
memory usage: 33.4+ MB
Filtrer les colonnes nécessaires
Ici, nous sélectionnons les colonnes utiles au calcul de la CLTV. Cinq colonnes suffisent : CustomerID, InvoiceDate, InvoiceNo, Quantity et UnitPrice.
- CustomerID identifie de manière unique chaque client.
- InvoiceDate permet de calculer le nombre de jours pendant lesquels le client est resté actif.
- InvoiceNo permet de compter le nombre de transactions (fréquence).
- Quantity correspond au nombre d’unités achetées par transaction.
- UnitPrice du produit aide à calculer le montant total dépensé.
uk_data=uk_data[['CustomerID','InvoiceDate','InvoiceNo','Quantity','UnitPrice']]
#Calulate total purchase
uk_data['TotalPurchase'] = uk_data['Quantity'] * uk_data['UnitPrice']
Nous allons effectuer les opérations suivantes :
- Calculer le nombre de jours entre la date actuelle et la dernière date d’achat de chaque client.
- Calculer le nombre de commandes par client.
- Calculer la somme des montants dépensés par client.
uk_data_group=uk_data.groupby('CustomerID').agg({'InvoiceDate': lambda date: (date.max() - date.min()).days,
'InvoiceNo': lambda num: len(num),
'Quantity': lambda quant: quant.sum(),
'TotalPurchase': lambda price: price.sum()})
uk_data_group.head()
| InvoiceDate | InvoiceNo | Quantity | TotalPurchase | |
|---|---|---|---|---|
| CustomerID | ||||
| 12346.0 | 0 | 1 | 74215 | 77183.60 |
| 12747.0 | 366 | 103 | 1275 | 4196.01 |
| 12748.0 | 372 | 4596 | 25748 | 33719.73 |
| 12749.0 | 209 | 199 | 1471 | 4090.88 |
| 12820.0 | 323 | 59 | 722 | 942.34 |
Renommer les colonnes
# Change the name of columns
uk_data_group.columns=['num_days','num_transactions','num_units','spent_money']
uk_data_group.head()
| num_days | num_transactions | num_units | spent_money | |
|---|---|---|---|---|
| CustomerID | ||||
| 12346.0 | 0 | 1 | 74215 | 77183.60 |
| 12747.0 | 366 | 103 | 1275 | 4196.01 |
| 12748.0 | 372 | 4596 | 25748 | 33719.73 |
| 12749.0 | 209 | 199 | 1471 | 4090.88 |
| 12820.0 | 323 | 59 | 722 | 942.34 |
Calculer la CLTV avec la formule suivante :
CLTV = ((Average Order Value x Purchase Frequency)/Churn Rate) x Profit margin.
Customer Value = Average Order Value * Purchase Frequency
1. Calculer la valeur moyenne de commande
# Average Order Value
uk_data_group['avg_order_value']=uk_data_group['spent_money']/uk_data_group['num_transactions']
uk_data_group.head()
| num_days | num_transactions | num_units | spent_money | avg_order_value | |
|---|---|---|---|---|---|
| CustomerID | |||||
| 12346.0 | 0 | 1 | 74215 | 77183.60 | 77183.600000 |
| 12747.0 | 366 | 103 | 1275 | 4196.01 | 40.737961 |
| 12748.0 | 372 | 4596 | 25748 | 33719.73 | 7.336756 |
| 12749.0 | 209 | 199 | 1471 | 4090.88 | 20.557186 |
| 12820.0 | 323 | 59 | 722 | 942.34 | 15.971864 |
2. Calculer la fréquence d’achat
purchase_frequency=sum(uk_data_group['num_transactions'])/uk_data_group.shape[0]
3. Calculer le taux de réachat et le taux d’attrition
# Repeat Rate
repeat_rate=uk_data_group[uk_data_group.num_transactions > 1].shape[0]/uk_data_group.shape[0]
#Churn Rate
churn_rate=1-repeat_rate
purchase_frequency,repeat_rate,churn_rate
(90.37107880642694, 0.9818923743942872, 0.018107625605712774)
4. Calculer la marge bénéficiaire
La marge est le ratio de rentabilité le plus courant. Elle indique la part du chiffre d’affaires conservée en gain. Supposons ici une marge d’environ 5 % sur les ventes.
# Profit Margin
uk_data_group['profit_margin']=uk_data_group['spent_money']*0.05
uk_data_group.head()
| num_days | num_transactions | num_units | spent_money | avg_order_value | profit_margin | |
|---|---|---|---|---|---|---|
| CustomerID | ||||||
| 12346.0 | 0 | 1 | 74215 | 77183.60 | 77183.600000 | 3859.1800 |
| 12747.0 | 366 | 103 | 1275 | 4196.01 | 40.737961 | 209.8005 |
| 12748.0 | 372 | 4596 | 25748 | 33719.73 | 7.336756 | 1685.9865 |
| 12749.0 | 209 | 199 | 1471 | 4090.88 | 20.557186 | 204.5440 |
| 12820.0 | 323 | 59 | 722 | 942.34 | 15.971864 | 47.1170 |
5. Calculer la Customer Lifetime Value
# Customer Value
uk_data_group['CLV']=(uk_data_group['avg_order_value']*purchase_frequency)/churn_rate
#Customer Lifetime Value
uk_data_group['cust_lifetime_value']=uk_data_group['CLV']*uk_data_group['profit_margin']
uk_data_group.head()
| num_days | num_transactions | num_units | spent_money | avg_order_value | profit_margin | CLV | cust_lifetime_value | |
|---|---|---|---|---|---|---|---|---|
| CustomerID | ||||||||
| 12346.0 | 0 | 1 | 74215 | 77183.60 | 77183.600000 | 3859.1800 | 3.852060e+08 | 1.486579e+12 |
| 12747.0 | 366 | 103 | 1275 | 4196.01 | 40.737961 | 209.8005 | 2.033140e+05 | 4.265538e+07 |
| 12748.0 | 372 | 4596 | 25748 | 33719.73 | 7.336756 | 1685.9865 | 3.661610e+04 | 6.173424e+07 |
| 12749.0 | 209 | 199 | 1471 | 4090.88 | 20.557186 | 204.5440 | 1.025963e+05 | 2.098545e+07 |
| 12820.0 | 323 | 59 | 722 | 942.34 | 15.971864 | 47.1170 | 7.971198e+04 | 3.755789e+06 |
Modèle de prédiction de la CLTV
Construisons un modèle de prédiction de la CLTV.
Ici, nous allons prédire la CLTV avec une régression linéaire.
Repartons des données chargées et filtrées ci-dessus.
uk_data.head()
| CustomerID | InvoiceDate | InvoiceNo | Quantity | UnitPrice | TotalPurchase | month_yr | |
|---|---|---|---|---|---|---|---|
| 0 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 2.55 | 15.30 | Dec-2010 |
| 1 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 3.39 | 20.34 | Dec-2010 |
| 2 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 8 | 2.75 | 22.00 | Dec-2010 |
| 3 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 3.39 | 20.34 | Dec-2010 |
| 4 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 3.39 | 20.34 | Dec-2010 |
Extraire le mois et l’année depuis InvoiceDate.
uk_data['month_yr'] = uk_data['InvoiceDate'].apply(lambda x: x.strftime('%b-%Y'))
uk_data.head()
| CustomerID | InvoiceDate | InvoiceNo | Quantity | UnitPrice | TotalPurchase | month_yr | |
|---|---|---|---|---|---|---|---|
| 0 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 2.55 | 15.30 | Dec-2010 |
| 1 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 3.39 | 20.34 | Dec-2010 |
| 2 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 8 | 2.75 | 22.00 | Dec-2010 |
| 3 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 3.39 | 20.34 | Dec-2010 |
| 4 | 17850.0 | 2010-12-01 08:26:00 | 536365 | 6 | 3.39 | 20.34 | Dec-2010 |
Le tableau croisé dynamique agrège des colonnes en une table bidimensionnelle offrant une synthèse multidimensionnelle des données.
sale=uk_data.pivot_table(index=['CustomerID'],columns=['month_yr'],values='TotalPurchase',aggfunc='sum',fill_value=0).reset_index()
sale.head()
| month_yr | CustomerID | Apr-2011 | Aug-2011 | Dec-2010 | Dec-2011 | Feb-2011 | Jan-2011 | Jul-2011 | Jun-2011 | Mar-2011 | May-2011 | Nov-2011 | Oct-2011 | Sep-2011 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 12346.0 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 77183.60 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| 1 | 12747.0 | 0.00 | 301.70 | 706.27 | 438.50 | 0.00 | 303.04 | 0.00 | 376.30 | 310.78 | 771.31 | 312.73 | 675.38 | 0.00 |
| 2 | 12748.0 | 1100.37 | 898.24 | 4228.13 | 1070.27 | 389.64 | 418.77 | 1113.27 | 2006.26 | 1179.37 | 2234.50 | 10639.23 | 2292.84 | 6148.84 |
| 3 | 12749.0 | 0.00 | 1896.13 | 0.00 | 763.06 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 859.10 | 572.59 | 0.00 | 0.00 |
| 4 | 12820.0 | 0.00 | 0.00 | 0.00 | 210.35 | 0.00 | 170.46 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 343.76 | 217.77 |
Faisons la somme des ventes sur l’ensemble des mois.
sale['CLV']=sale.iloc[:,2:].sum(axis=1)
sale.head()
| month_yr | CustomerID | Apr-2011 | Aug-2011 | Dec-2010 | Dec-2011 | Feb-2011 | Jan-2011 | Jul-2011 | Jun-2011 | Mar-2011 | May-2011 | Nov-2011 | Oct-2011 | Sep-2011 | CLV |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 12346.0 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 77183.60 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 77183.60 |
| 1 | 12747.0 | 0.00 | 301.70 | 706.27 | 438.50 | 0.00 | 303.04 | 0.00 | 376.30 | 310.78 | 771.31 | 312.73 | 675.38 | 0.00 | 4196.01 |
| 2 | 12748.0 | 1100.37 | 898.24 | 4228.13 | 1070.27 | 389.64 | 418.77 | 1113.27 | 2006.26 | 1179.37 | 2234.50 | 10639.23 | 2292.84 | 6148.84 | 32619.36 |
| 3 | 12749.0 | 0.00 | 1896.13 | 0.00 | 763.06 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 859.10 | 572.59 | 0.00 | 0.00 | 4090.88 |
| 4 | 12820.0 | 0.00 | 0.00 | 0.00 | 210.35 | 0.00 | 170.46 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 343.76 | 217.77 | 942.34 |
Sélection des variables
Divisez les colonnes en deux types : variable cible (dépendante) et variables explicatives (features). Sélectionnez les 6 derniers mois comme variables explicatives.
X=sale[['Dec-2011','Nov-2011', 'Oct-2011','Sep-2011','Aug-2011','Jul-2011']]
y=sale[['CLV']]
Découper les données
Pour évaluer les performances du modèle, il est recommandé de diviser le jeu de données en ensemble d’entraînement et de test.
Scindons le jeu de données avec la fonction train_test_split(). Vous devez passer 3 paramètres : les features, la cible et la taille du test set. Vous pouvez aussi utiliser random_state comme graine pour la reproductibilité : chaque découpage donnera les mêmes résultats. Si random_state est None, le générateur aléatoire utilise np.random pour sélectionner les enregistrements, et le découpage variera à chaque exécution si vous ne fixez pas de graine.
#split training set and test set
from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y,random_state=0)
Développement du modèle
Importez d’abord le module de régression linéaire et créez un objet Linear Regression. Ajustez ensuite le modèle sur le jeu d’entraînement avec fit(), puis réalisez des prédictions sur le jeu de test avec predict().
# import model
from sklearn.linear_model import LinearRegression
# instantiate
linreg = LinearRegression()
# fit the model to the training data (learn the coefficients)
linreg.fit(X_train, y_train)
# make predictions on the testing set
y_pred = linreg.predict(X_test)
# print the intercept and coefficients
print(linreg.intercept_)
print(linreg.coef_)
[208.50969617]
[[0.99880551 0.80381254 1.60226829 1.67433228 1.52860813 2.87959449]]
Qualité d’ajustement du modèle
Pour évaluer l’ajustement global du modèle linéaire, on utilise le R². Le R² est la proportion de la variance expliquée par le modèle. Sa valeur est comprise entre 0 et 1. Plus le R² est élevé, mieux c’est, car le modèle explique une plus grande part de la variance.
from sklearn import metrics
# compute the R Square for model
print("R-Square:",metrics.r2_score(y_test, y_pred))
R-Square: 0.9666074402817512
Ce modèle affiche un R² élevé (0,96) et s’ajuste donc bien aux données.
Évaluation du modèle
Pour les problèmes de régression, on utilise généralement les métriques suivantes (Ritchie Ng) :
- Erreur absolue moyenne (MAE) : moyenne des valeurs absolues des erreurs.
- Erreur quadratique moyenne (MSE) : moyenne des carrés des erreurs.
- Racine de l’erreur quadratique moyenne (RMSE) : racine carrée du MSE.
# calculate MAE using scikit-learn
print("MAE:",metrics.mean_absolute_error(y_test,y_pred))
#calculate mean squared error
print("MSE",metrics.mean_squared_error(y_test, y_pred))
# compute the RMSE of our predictions
print("RMSE:",np.sqrt(metrics.mean_squared_error(y_test, y_pred)))
MAE: 595.0282284701234
MSE 2114139.8898678957
RMSE: 1454.0082151995896
La RMSE est souvent préférée au MSE et au MAE car elle s’exprime dans les mêmes unités que la variable cible y, ce qui la rend plus interprétable.
Avantages et limites de la CLTV
La CLTV vous aide à concevoir un plan d’affaires efficace et ouvre des perspectives de montée en échelle. Elle permet de dégager des segments clients pertinents, utiles pour identifier les besoins propres à chaque segment.
La Customer Lifetime Value est un outil, pas une stratégie. La CLTV aide à repérer les clients les plus rentables, mais la manière d’en tirer profit relève de votre stratégie. Les modèles de CLTV sont parfois mal compris ou mal utilisés. Une obsession pour la CLTV peut créer des œillères : l’entreprise se focalise sur un segment « premium » et cherche à répéter les ventes, alors qu’il est aussi important d’adresser les autres clients.
Conclusion
Félicitations, vous êtes arrivé au bout de ce tutoriel !
Vous avez passé en revue de nombreux aspects de la Customer Lifetime Value : définition, méthodes de calcul, implémentation from scratch en Python, modèle de prédiction, ainsi que ses avantages et ses limites. Vous avez également revu des notions de base de pandas comme groupby et les tableaux croisés pour synthétiser des colonnes et lignes de données.
Nous espérons que vous pourrez désormais mobiliser la CLTV pour analyser vos propres jeux de données. Merci de votre lecture !
Pour aller plus loin sur l’analyse client en Python, suivez le cours Customer Analytics & A/B Testing in Python de DataCamp.