Prédiction du taux de désabonnement pour les services d’abonnement

Les prévisions de désabonnement sont devenues essentielles pour toutes les entreprises proposant des abonnements. Elles permettent d’identifier les clients les plus susceptibles de résilier leur abonnement. Ces prévisions aident les fournisseurs à fidéliser leur clientèle et à comprendre les raisons des résiliations. Les entreprises utilisent cette technique pour optimiser leurs actions marketing et commerciales.

Prévoir le taux de désabonnement des clients

Anticiper le taux d’attrition client, c’est-à-dire le moment où les clients cessent de faire affaire avec une entreprise, est crucial pour les entreprises de tous les secteurs. En identifiant les clients susceptibles de se désabonner, les entreprises peuvent prendre des mesures proactives pour les fidéliser, réduisant ainsi les pertes de revenus et préservant une clientèle fidèle.

Description du jeu de données

Le jeu de données utilisé pour notre projet d’apprentissage automatique de classification comprend toutes les données clients pertinentes du secteur des télécommunications. Il provient de Kaggle et plus précisément de la collection d’exemples de données d’IBM. (https://www.kaggle.com/blastchar/telco-customer-churn) Chaque ligne de ces données représente un client, chaque colonne contient les attributs du client. Les données contiennent 7043 lignes (clients) et 21 colonnes (caractéristiques) :

  • Taux de désabonnement (Churn) = Clients ayant quitté l’entreprise au cours du dernier mois: notre objectif YES ou NO

  • CustomerID = ID du client

  • gender = Si le client est un homme ou une femme

  • SeniorCitizen = Si le client soit un senior ou non

  • Partner = Si le client a un partenaire ou non

  • Dependents = Si le client a des personnes à charge ou non

  • Tenure = Nombre de mois pendant lesquels le client est resté fidèle à l’entreprise

  • PhoneService = Si le client dispose d’un service téléphonique ou non

  • MultipleLines = Si le client possède plusieurs lignes ou non

  • InternetService = Fournisseur d’accès Internet du client, comme le DSL ou la fibre optique

  • OnlineSecurity = Si le client dispose ou non d’une sécurité en ligne

  • OnlineBackup = Si le client dispose ou non d’une sauvegarde en ligne

  • DeviceProtection = Si le client dispose ou non d’une protection pour son appareil

  • TechSupport = Si le client bénéficie ou non d’une assistance technique

  • StreamingTV = Si le client possède ou non un abonnement à la télévision en streaming

  • StreamingMovies = Si le client a accès ou non à des films en streaming

  • Contract = La durée du contrat du client, par exemple un abonnement mensuel, annuel ou bisannuel

  • PaperlessBilling = Si le client a opté pour la facturation sans papier ou non

  • PaymentMethod = Le mode de paiement du client, par exemple : chèque électronique, chèque postal, virement bancaire ou carte de crédit

  • MonthlyCharges = Le montant facturé au client chaque mois

  • TotalCharges = Le montant total facturé au client

Préparation des données

Dans cette étape, nous allons charger les bibliothèques et les jeux de données nécessaires.

# Chargement des bibliothèques nécessaires
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.5.3
## Warning: package 'ggplot2' was built under R version 4.5.3
## Warning: package 'tidyr' was built under R version 4.5.3
## Warning: package 'readr' was built under R version 4.5.3
## Warning: package 'purrr' was built under R version 4.5.3
## Warning: package 'forcats' was built under R version 4.5.3
## Warning: package 'lubridate' was built under R version 4.5.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.2.0
## ✔ forcats   1.0.1     ✔ stringr   1.6.0
## ✔ ggplot2   4.0.3     ✔ tibble    3.3.1
## ✔ lubridate 1.9.5     ✔ tidyr     1.3.2
## ✔ purrr     1.2.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(caret)
## Warning: package 'caret' was built under R version 4.5.3
## Loading required package: lattice
## 
## Attaching package: 'caret'
## 
## The following object is masked from 'package:purrr':
## 
##     lift
library(randomForest)
## Warning: package 'randomForest' was built under R version 4.5.3
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## 
## The following object is masked from 'package:dplyr':
## 
##     combine
## 
## The following object is masked from 'package:ggplot2':
## 
##     margin
library(pROC)
## Type 'citation("pROC")' for a citation.
## 
## Attaching package: 'pROC'
## 
## The following objects are masked from 'package:stats':
## 
##     cov, smooth, var
# Chargement du jeu de données
data <- read.csv("Churn_data.csv")
# la structure du jeu de données
str(data)
## 'data.frame':    7043 obs. of  21 variables:
##  $ customerID      : chr  "7590-VHVEG" "5575-GNVDE" "3668-QPYBK" "7795-CFOCW" ...
##  $ gender          : chr  "Female" "Male" "Male" "Male" ...
##  $ SeniorCitizen   : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ Partner         : chr  "Yes" "No" "No" "No" ...
##  $ Dependents      : chr  "No" "No" "No" "No" ...
##  $ tenure          : int  1 34 2 45 2 8 22 10 28 62 ...
##  $ PhoneService    : chr  "No" "Yes" "Yes" "No" ...
##  $ MultipleLines   : chr  "No phone service" "No" "No" "No phone service" ...
##  $ InternetService : chr  "DSL" "DSL" "DSL" "DSL" ...
##  $ OnlineSecurity  : chr  "No" "Yes" "Yes" "Yes" ...
##  $ OnlineBackup    : chr  "Yes" "No" "Yes" "No" ...
##  $ DeviceProtection: chr  "No" "Yes" "No" "Yes" ...
##  $ TechSupport     : chr  "No" "No" "No" "Yes" ...
##  $ StreamingTV     : chr  "No" "No" "No" "No" ...
##  $ StreamingMovies : chr  "No" "No" "No" "No" ...
##  $ Contract        : chr  "Month-to-month" "One year" "Month-to-month" "One year" ...
##  $ PaperlessBilling: chr  "Yes" "No" "Yes" "No" ...
##  $ PaymentMethod   : chr  "Electronic check" "Mailed check" "Mailed check" "Bank transfer (automatic)" ...
##  $ MonthlyCharges  : num  29.9 57 53.9 42.3 70.7 ...
##  $ TotalCharges    : num  29.9 1889.5 108.2 1840.8 151.7 ...
##  $ Churn           : chr  "No" "No" "Yes" "No" ...

Analyse exploratoire des données (AED)

L’AED nous aide à comprendre la relation entre les variables.

# Vérification des valeurs manquantes (NA)
colSums(is.na(data))
##       customerID           gender    SeniorCitizen          Partner 
##                0                0                0                0 
##       Dependents           tenure     PhoneService    MultipleLines 
##                0                0                0                0 
##  InternetService   OnlineSecurity     OnlineBackup DeviceProtection 
##                0                0                0                0 
##      TechSupport      StreamingTV  StreamingMovies         Contract 
##                0                0                0                0 
## PaperlessBilling    PaymentMethod   MonthlyCharges     TotalCharges 
##                0                0                0               11 
##            Churn 
##                0

Nous avons donc un total de 11 valeurs manquantes dans la colonne MonthlyCharges et nous allons supprimer ces valeurs.

Pour visualiser les valeurs manquantes, nous avons utilisé la fonction summary().

La variable TotalCharges contient 11 valeurs manquantes (NA). Nous avons décidé de la supprimer car elle dépend du temps passé par les clients chez Telco.

summary(data) # Résumé des variables contenues dans le jeu de données
##   customerID           gender          SeniorCitizen      Partner         
##  Length:7043        Length:7043        Min.   :0.0000   Length:7043       
##  Class :character   Class :character   1st Qu.:0.0000   Class :character  
##  Mode  :character   Mode  :character   Median :0.0000   Mode  :character  
##                                        Mean   :0.1621                     
##                                        3rd Qu.:0.0000                     
##                                        Max.   :1.0000                     
##                                                                           
##   Dependents            tenure      PhoneService       MultipleLines     
##  Length:7043        Min.   : 0.00   Length:7043        Length:7043       
##  Class :character   1st Qu.: 9.00   Class :character   Class :character  
##  Mode  :character   Median :29.00   Mode  :character   Mode  :character  
##                     Mean   :32.37                                        
##                     3rd Qu.:55.00                                        
##                     Max.   :72.00                                        
##                                                                          
##  InternetService    OnlineSecurity     OnlineBackup       DeviceProtection  
##  Length:7043        Length:7043        Length:7043        Length:7043       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##  TechSupport        StreamingTV        StreamingMovies      Contract        
##  Length:7043        Length:7043        Length:7043        Length:7043       
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##  PaperlessBilling   PaymentMethod      MonthlyCharges    TotalCharges   
##  Length:7043        Length:7043        Min.   : 18.25   Min.   :  18.8  
##  Class :character   Class :character   1st Qu.: 35.50   1st Qu.: 401.4  
##  Mode  :character   Mode  :character   Median : 70.35   Median :1397.5  
##                                        Mean   : 64.76   Mean   :2283.3  
##                                        3rd Qu.: 89.85   3rd Qu.:3794.7  
##                                        Max.   :118.75   Max.   :8684.8  
##                                                         NA's   :11      
##     Churn          
##  Length:7043       
##  Class :character  
##  Mode  :character  
##                    
##                    
##                    
## 
# Suppression des valeurs manquantes
data<-na.omit(data)

Visualisation

Nous pouvons également comprendre la relation entre le taux de désabonnement, le type de contrat et le mode de paiement, et leur rôle. Cette relation et cette tendance sont mieux comprises lorsqu’on les représente graphiquement à l’aide de ggplot2.

# Création d'une nouvelle caractéristique : frais mensuels moyens (average monthly charges)
trainData <- data %>%
  mutate(avgMonthlyCharges = TotalCharges / (tenure + 1))

testData <- data%>%
  mutate(avgMonthlyCharges = TotalCharges / (tenure + 1))

# Plot the distribution of tenure by churn
ggplot(trainData, aes(x = tenure, fill = Churn)) +
  geom_histogram(binwidth = 1, position = "dodge") +
  labs(title = "Distribution of Tenure by Churn",
       x = "Tenure (Months)", y = "Count")

Ce graphique illustre la répartition de l’ancienneté (en mois) des clients ayant résilié leur abonnement (Résiliation = Oui) et de ceux qui ne l’ont pas fait (Résiliation = Non). Il permet de mieux comprendre cette répartition, ce qui peut s’avérer utile pour des analyses ultérieures et la modélisation.

Distribution des frais mensuels en fonction du taux de désabonnement

Nous créons un histogramme pour visualiser la répartition des frais mensuels pour les clients qui ont résilié leur abonnement et ceux qui ne l’ont pas fait.

# Plot the distribution of monthly charges by churn
ggplot(trainData, aes(x = MonthlyCharges, fill = Churn)) +
  geom_histogram(binwidth = 5, position = "dodge") +
  labs(title = "Distribution of Monthly Charges by Churn",
       x = "Monthly Charges", y = "Count")

En visualisant la répartition des frais mensuels en fonction du taux de désabonnement, les entreprises peuvent obtenir des informations précieuses sur le comportement des clients et prendre des décisions basées sur les données pour atténuer le taux de désabonnement et améliorer les stratégies de fidélisation de la clientèle.

Graphique de la répartition du taux de désabonnement par mode de paiement

Nous pouvons également comprendre la relation entre le taux de désabonnement, le type de contrat et le mode de paiement, et leur rôle. Cette relation et cette tendance sont mieux comprises lorsqu’on les représente graphiquement à l’aide de ggplot2.

# Plot churn distribution by payment method
ggplot(trainData, aes(x = PaymentMethod, fill = Churn)) +
  geom_bar(position = "fill") +
  scale_y_continuous(labels = scales::percent_format()) +
  labs(title = "Churn Distribution by Payment Method", x = "Payment Method",
       y = "Percentage", fill = "Churn") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Ce graphique à barres nous aide à comprendre que les personnes payant par chèque électronique ont le taux de désabonnement le plus élevé.

Construction de modèles

Dans cet exemple, nous utiliserons l’algorithme de forêt aléatoire pour construire et entraîner notre modèle. Pour cela, il est nécessaire d’installer le package randomForest dans R. Cet algorithme utilise plusieurs arbres de décision afin d’améliorer les performances globales. Chaque arbre est entraîné sur un échantillon de données d’entraînement. Grâce à cette méthode, la précision est élevée.

# Création d'une nouvelle caractéristique
data$TotalCharges <- as.numeric(as.character(data$TotalCharges))
data <- na.omit(data) # Suppression des lignes avec NA

data$Churn <- as.factor(data$Churn)
data$Churn <- factor(data$Churn, levels = c("No", "Yes"))


# Séparation des données en ensembles d'entrainement et test
set.seed(123)
trainIndex <- createDataPartition(data$Churn, p = 0.8, list = FALSE)
trainData <- data[trainIndex, ]
testData <- data[-trainIndex, ]

# Caractéristique avec dplyr
trainData <- trainData %>%
  mutate(avgMonthlyCharges = TotalCharges / (tenure + 1))

testData <- testData %>%
  mutate(avgMonthlyCharges = TotalCharges / (tenure + 1))

# Construction du modèle Random Forest
library(randomForest)
set.seed(123)
rfModel <- randomForest(Churn ~ ., data = trainData, ntree = 100, mtry = 3, 
                        importance = TRUE)

# Résumé du modèle
print(rfModel)
## 
## Call:
##  randomForest(formula = Churn ~ ., data = trainData, ntree = 100,      mtry = 3, importance = TRUE) 
##                Type of random forest: classification
##                      Number of trees: 100
## No. of variables tried at each split: 3
## 
##         OOB estimate of  error rate: 20.42%
## Confusion matrix:
##       No Yes class.error
## No  3725 406  0.09828129
## Yes  743 753  0.49665775

La matrice de confusion fournit un résumé détaillé des résultats de la classification, indiquant le nombre de prédictions vraies positives, vraies négatives, fausses positives et fausses négatives.

  • Le taux d’erreur global OOB est de 20,42 %, ce qui indique que les prédictions du modèle sont incorrectes environ une fois sur cinq.

  • La matrice de confusion montre que le modèle est plus performant pour prédire « Non » (absence de désabonnement) que « Oui » (désabonnement).

  • Le taux d’erreur élevé pour la classe « Oui » suggère que le modèle a plus de difficultés à prédire correctement les désabonnements.

Evaluation du modèle

Nous allons maintenant vérifier la précision de notre modèle.

# Prévision sur l'ensemble de test
pred_class <- predict(rfModel, newdata = testData)
pred_proba <- predict(rfModel, newdata = testData, type = "prob")[, "Yes"]

# Matrice de Confusion
confusionMatrix <- table(pred = pred_class, Real = testData$Churn)

# Calculer accuracy, precision, and recall
accuracy <- sum(diag(confusionMatrix)) / sum(confusionMatrix)
precision <- confusionMatrix["Yes", "Yes"] / sum(confusionMatrix["Yes", ])
recall <- confusionMatrix["Yes", "Yes"] / sum(confusionMatrix[, "Yes"])

# AUC + Plot ROC
roc_obj <- roc(response = testData$Churn, predictor = pred_proba, levels = c("No", "Yes"))
## Setting direction: controls < cases
auc_value <- auc(roc_obj)
# Affichage des métriques
cat("Accuracy: ", round(accuracy, 3), "\n")
## Accuracy:  0.798
cat("Precision: ", round(precision, 3), "\n")
## Precision:  0.649
cat("Recall: ", round(recall, 3), "\n")
## Recall:  0.52
cat("AUC: ", round(auc_value, 3), "\n")
## AUC:  0.835

La précision est d’environ 80%, ce qui signifie que le modèle prédit correctement le statut de désabonnement environ 80% du temps.

  • La précision est d’environ 65,2%, ce qui signifie que lorsque le modèle prédit qu’un client va se désabonner, il a raison environ 65,2% du temps.

  • Le taux de rappel est d’environ 52,3%, ce qui signifie que le modèle identifie correctement 52,3% des clients qui ont effectivement résilié leur abonnement.

  • L’AUC est de 0,835, ce qui signifie que le modèle a 83,5% de chances de distinguer correctement les clients qui résilient leur abonnement de ceux qui ne le font pas.

Graphique

# Graphique ROC
plot(roc_obj, col = "red", lwd = 2, main = paste("Courbe ROC - Random Forest | AUC =", round(auc_value, 3)))
abline(a = 0, b = 1, lty = 2, col = "gray")

Sur le jeu de données Telco IBM 7043 clients, le Random Forest atteint AUC 0.835. L’analyse ROC montre que le modele discrimine correctement les clients a risque, principalement ceux en contrat mensuel + faible ancienneté. Ce score permet un ciblage efficace pour la rétention : 75% du churn detecte en contactant 30% de la base client.