Projet : Identifiez les causes d'attrition au sein d'une ESN
Bouzouita Hayette
Contexte : ESN (spécialisée dans la transformation digitaele + vente d'applications SaaS) avec une hausse de taux de démission.
But coté RH : Objectiver la situation via les données, identifier les causes/segments à risque ainsi que les leviers actionnables pour y remédier.
Mission 1 en 2 notebooks :
- EDA multi-fichiers (3 datasets) -> comparer les employés partants vs restants / faire ressortir les différences clés / formaliser des insights RH .
- Modèle de classification : Scorer la probabilité de démission par employé, puis expliquer les drivers avec SHAP (mais seulement après avoir stabilisé le pipeline de modélisation).
-> Présentation à l'issu de la mission
Mission 2 (plus tard) :
- Préparer le déploiement via PostgreSQL + requêtes SQL de test/validation.
- Déployer le modèle de ML.
-> Présentation à l'issu de la mission
Notebook 1 : EDA
Section 1 : Importation et Compréhension des données
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from pathlib import Path
pd.set_option("display.max_columns",200)
pd.set_option("display.max_rows",200)
#Chargement des fichiers csv
DATA_PATH = Path("..")/"data"/"raw"
sirh = pd.read_csv(DATA_PATH/"extrait_sirh.csv")
evals = pd.read_csv(DATA_PATH/"extrait_eval.csv")
sond = pd.read_csv(DATA_PATH/"extrait_sondage.csv")
Nous récupérons 3 fichiers csv:
#check rapide des 3 datasets : taille, typage, NaN
def quick_profile(df, name):
print(f"\n=== {name} ===")
print("shape:", df.shape)
print("\nDtypes:\n", df.dtypes)
na = (df.isna().mean().sort_values(ascending=False) * 100).round(1)
print("\n% NA (top 15):\n", na.head(15))
quick_profile(sirh, "SIRH")
quick_profile(evals, "EVALS")
quick_profile(sond, "SONDAGE")
=== SIRH === shape: (1470, 12) Dtypes: id_employee int64 age int64 genre str revenu_mensuel int64 statut_marital str departement str poste str nombre_experiences_precedentes int64 nombre_heures_travailless int64 annee_experience_totale int64 annees_dans_l_entreprise int64 annees_dans_le_poste_actuel int64 dtype: object % NA (top 15): id_employee 0.0 age 0.0 genre 0.0 revenu_mensuel 0.0 statut_marital 0.0 departement 0.0 poste 0.0 nombre_experiences_precedentes 0.0 nombre_heures_travailless 0.0 annee_experience_totale 0.0 annees_dans_l_entreprise 0.0 annees_dans_le_poste_actuel 0.0 dtype: float64 === EVALS === shape: (1470, 10) Dtypes: satisfaction_employee_environnement int64 note_evaluation_precedente int64 niveau_hierarchique_poste int64 satisfaction_employee_nature_travail int64 satisfaction_employee_equipe int64 satisfaction_employee_equilibre_pro_perso int64 eval_number str note_evaluation_actuelle int64 heure_supplementaires str augementation_salaire_precedente str dtype: object % NA (top 15): satisfaction_employee_environnement 0.0 note_evaluation_precedente 0.0 niveau_hierarchique_poste 0.0 satisfaction_employee_nature_travail 0.0 satisfaction_employee_equipe 0.0 satisfaction_employee_equilibre_pro_perso 0.0 eval_number 0.0 note_evaluation_actuelle 0.0 heure_supplementaires 0.0 augementation_salaire_precedente 0.0 dtype: float64 === SONDAGE === shape: (1470, 12) Dtypes: a_quitte_l_entreprise str nombre_participation_pee int64 nb_formations_suivies int64 nombre_employee_sous_responsabilite int64 code_sondage int64 distance_domicile_travail int64 niveau_education int64 domaine_etude str ayant_enfants str frequence_deplacement str annees_depuis_la_derniere_promotion int64 annes_sous_responsable_actuel int64 dtype: object % NA (top 15): a_quitte_l_entreprise 0.0 nombre_participation_pee 0.0 nb_formations_suivies 0.0 nombre_employee_sous_responsabilite 0.0 code_sondage 0.0 distance_domicile_travail 0.0 niveau_education 0.0 domaine_etude 0.0 ayant_enfants 0.0 frequence_deplacement 0.0 annees_depuis_la_derniere_promotion 0.0 annes_sous_responsable_actuel 0.0 dtype: float64
Les 3 datasets contiennent 1470 lignes. Une ligne = un employé.
Il n'y a pas de id_employee dans les datasets evals et sond. Il faudra donc vérifier si les 3 identifiants de chaque dataset correspondent entre eux.
Il n'y a pas de valeurs manquantes explicites dans les jeux de données.
Il y a des incohérences de typages que nous devront traiter dans la section de nettoyage et de préparation des données.
Il y a des erreurs de saisies dans les noms des colonnes, il faudra également vérifier la qualité des variables catégorielles.
La variable a_quitte_l_entreprise est la target (variable cible). Elle devra être convertie en variable numérique binaire (0/1).
sirh.head()
| id_employee | age | genre | revenu_mensuel | statut_marital | departement | poste | nombre_experiences_precedentes | nombre_heures_travailless | annee_experience_totale | annees_dans_l_entreprise | annees_dans_le_poste_actuel | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 41 | F | 5993 | Célibataire | Commercial | Cadre Commercial | 8 | 80 | 8 | 6 | 4 |
| 1 | 2 | 49 | M | 5130 | Marié(e) | Consulting | Assistant de Direction | 1 | 80 | 10 | 10 | 7 |
| 2 | 4 | 37 | M | 2090 | Célibataire | Consulting | Consultant | 6 | 80 | 7 | 0 | 0 |
| 3 | 5 | 33 | F | 2909 | Marié(e) | Consulting | Assistant de Direction | 1 | 80 | 8 | 8 | 7 |
| 4 | 7 | 27 | M | 3468 | Marié(e) | Consulting | Consultant | 9 | 80 | 6 | 2 | 2 |
evals.head()
| satisfaction_employee_environnement | note_evaluation_precedente | niveau_hierarchique_poste | satisfaction_employee_nature_travail | satisfaction_employee_equipe | satisfaction_employee_equilibre_pro_perso | eval_number | note_evaluation_actuelle | heure_supplementaires | augementation_salaire_precedente | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2 | 3 | 2 | 4 | 1 | 1 | E_1 | 3 | Oui | 11 % |
| 1 | 3 | 2 | 2 | 2 | 4 | 3 | E_2 | 4 | Non | 23 % |
| 2 | 4 | 2 | 1 | 3 | 2 | 3 | E_4 | 3 | Oui | 15 % |
| 3 | 4 | 3 | 1 | 3 | 3 | 3 | E_5 | 3 | Oui | 11 % |
| 4 | 1 | 3 | 1 | 2 | 4 | 3 | E_7 | 3 | Non | 12 % |
evals['eval_number'].value_counts()
eval_number
E_1 1
E_2 1
E_4 1
E_5 1
E_7 1
..
E_2061 1
E_2062 1
E_2064 1
E_2065 1
E_2068 1
Name: count, Length: 1470, dtype: int64
sond.head()
| a_quitte_l_entreprise | nombre_participation_pee | nb_formations_suivies | nombre_employee_sous_responsabilite | code_sondage | distance_domicile_travail | niveau_education | domaine_etude | ayant_enfants | frequence_deplacement | annees_depuis_la_derniere_promotion | annes_sous_responsable_actuel | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Oui | 0 | 0 | 1 | 1 | 1 | 2 | Infra & Cloud | Y | Occasionnel | 0 | 5 |
| 1 | Non | 1 | 3 | 1 | 2 | 8 | 1 | Infra & Cloud | Y | Frequent | 1 | 7 |
| 2 | Oui | 0 | 3 | 1 | 4 | 2 | 2 | Autre | Y | Occasionnel | 0 | 0 |
| 3 | Non | 0 | 3 | 1 | 5 | 3 | 4 | Infra & Cloud | Y | Frequent | 3 | 0 |
| 4 | Non | 1 | 3 | 1 | 7 | 2 | 1 | Transformation Digitale | Y | Occasionnel | 2 | 2 |
sond["code_sondage"].value_counts()
code_sondage
1 1
2 1
4 1
5 1
7 1
..
2061 1
2062 1
2064 1
2065 1
2068 1
Name: count, Length: 1470, dtype: int64
Section 2 : Nettoyage et Préparation des données
Typage incohérent :¶
- La variable cible
a_quitte_l_entreprisedans sond (str -> int 0/1)
sond['a_quitte_l_entreprise'].value_counts()
a_quitte_l_entreprise Non 1233 Oui 237 Name: count, dtype: int64
sond['a_quitte_l_entreprise'] = sond['a_quitte_l_entreprise'].map({
"Oui": 1,
"Non": 0
})
sond["a_quitte_l_entreprise"].value_counts(normalize=True)
a_quitte_l_entreprise 0 0.838776 1 0.161224 Name: proportion, dtype: float64
Si l'employé a quitté l'entreprise alors a_quitte_l_entreprise prend la valeur 1, sinon 0.
ayant_enfantsdans sond
sond["ayant_enfants"].value_counts()
ayant_enfants Y 1470 Name: count, dtype: int64
sond = sond.drop(columns=["ayant_enfants"], errors="ignore")
sond.shape
(1470, 11)
La variable ayant_enfants présente une variance nulle (100 % des employés ont la même modalité) et ne peut donc pas expliquer les différences de comportement de démission.
augementation_salaire_precedente(erreur de saissie dans le nom de la variable + erreur de typage) dans la table evals
evals['augementation_salaire_precedente'].value_counts()
augementation_salaire_precedente 11 % 210 13 % 209 14 % 201 12 % 198 15 % 101 18 % 89 17 % 82 16 % 78 19 % 76 22 % 56 20 % 55 21 % 48 23 % 28 24 % 21 25 % 18 Name: count, dtype: int64
evals["augmentation_salaire_precedente_pct"] = (
evals["augementation_salaire_precedente"]
.str.replace(" %", "", regex=False)
.astype(int)
)
evals = evals.drop(columns=["augementation_salaire_precedente"])
evals["augmentation_salaire_precedente_pct"].value_counts()
augmentation_salaire_precedente_pct 11 210 13 209 14 201 12 198 15 101 18 89 17 82 16 78 19 76 22 56 20 55 21 48 23 28 24 21 25 18 Name: count, dtype: int64
La variable a été renommé augmentation_salaire_precedente_pct une fois corrigée et convertie en variable numérique.
heure_supplementaires(str -> en binaire 0/1 serait plus pertinent) + rennomer colonne
evals["heure_supplementaires"].value_counts()
heure_supplementaires Non 1054 Oui 416 Name: count, dtype: int64
evals["heures_supplementaires"] = evals["heure_supplementaires"].map({
"Oui": 1,
"Non": 0
})
Si l'employé effectue des heures supplémentaire alors heures_supplementaires prend la valeur 1, sinon 0.
evals["heures_supplementaires_version_2"] = evals["heure_supplementaires"].apply(
lambda x: 1 if x == "Oui" else 0
)
evals = evals.drop(columns=["heures_supplementaires_version_2"])
evals = evals.drop(columns=["heure_supplementaires"])
Vérification des identifiants¶
Nous avons remarqué que id_employee est présente dans la table sirh mais ne l'est pas dans evals ni dans sond.
Deux hypothèses :
1- Les lignes sont déjà dans le même ordre pour les 3 tables.
2- Ordre non garanti : il manque une clé explicite.
Nous remarquons d'autre part que eval_number semble être une clé de la table evals.
De même, code_sondage pour sond.
On observe => id_employee : 2068 / eval_number : E_2068 / code_sondage : 2068
Il semblerait qu'un même identifiant employé soit stocké différemment selon les systèmes. Vérifions ainsi la correspondance entre ces 3 clés.
#normalisation des identifiants, création d'une clé commune temporaire
# SIRH
sirh["employee_key"] = sirh["id_employee"].astype(str)
# EVALS
evals["employee_key"] = evals["eval_number"].str.replace("E_", "", regex=False)
# SONDAGE
sond["employee_key"] = sond["code_sondage"].astype(str)
#vérification de l'alignement réel
#Unicité (il faut 1470 partout -> ok)
sirh["employee_key"].nunique(), evals["employee_key"].nunique(), sond["employee_key"].nunique()
#Correspondance des sets
set(sirh["employee_key"]) == set(evals["employee_key"]) == set(sond["employee_key"])
True
Une clé employé commune a été reconstruite à partir des identifiants techniques propres à chaque système afin de garantir une jointure fiable entre les sources.
df = (
sirh
.merge(evals, on="employee_key", how="inner")
.merge(sond, on="employee_key", how="inner")
)
df = df.drop(columns=["id_employee", "eval_number", "code_sondage", "employee_key"])
df
| age | genre | revenu_mensuel | statut_marital | departement | poste | nombre_experiences_precedentes | nombre_heures_travailless | annee_experience_totale | annees_dans_l_entreprise | annees_dans_le_poste_actuel | satisfaction_employee_environnement | note_evaluation_precedente | niveau_hierarchique_poste | satisfaction_employee_nature_travail | satisfaction_employee_equipe | satisfaction_employee_equilibre_pro_perso | note_evaluation_actuelle | augmentation_salaire_precedente_pct | heures_supplementaires | a_quitte_l_entreprise | nombre_participation_pee | nb_formations_suivies | nombre_employee_sous_responsabilite | distance_domicile_travail | niveau_education | domaine_etude | frequence_deplacement | annees_depuis_la_derniere_promotion | annes_sous_responsable_actuel | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 41 | F | 5993 | Célibataire | Commercial | Cadre Commercial | 8 | 80 | 8 | 6 | 4 | 2 | 3 | 2 | 4 | 1 | 1 | 3 | 11 | 1 | 1 | 0 | 0 | 1 | 1 | 2 | Infra & Cloud | Occasionnel | 0 | 5 |
| 1 | 49 | M | 5130 | Marié(e) | Consulting | Assistant de Direction | 1 | 80 | 10 | 10 | 7 | 3 | 2 | 2 | 2 | 4 | 3 | 4 | 23 | 0 | 0 | 1 | 3 | 1 | 8 | 1 | Infra & Cloud | Frequent | 1 | 7 |
| 2 | 37 | M | 2090 | Célibataire | Consulting | Consultant | 6 | 80 | 7 | 0 | 0 | 4 | 2 | 1 | 3 | 2 | 3 | 3 | 15 | 1 | 1 | 0 | 3 | 1 | 2 | 2 | Autre | Occasionnel | 0 | 0 |
| 3 | 33 | F | 2909 | Marié(e) | Consulting | Assistant de Direction | 1 | 80 | 8 | 8 | 7 | 4 | 3 | 1 | 3 | 3 | 3 | 3 | 11 | 1 | 0 | 0 | 3 | 1 | 3 | 4 | Infra & Cloud | Frequent | 3 | 0 |
| 4 | 27 | M | 3468 | Marié(e) | Consulting | Consultant | 9 | 80 | 6 | 2 | 2 | 1 | 3 | 1 | 2 | 4 | 3 | 3 | 12 | 0 | 0 | 1 | 3 | 1 | 2 | 1 | Transformation Digitale | Occasionnel | 2 | 2 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1465 | 36 | M | 2571 | Marié(e) | Consulting | Consultant | 4 | 80 | 17 | 5 | 2 | 3 | 4 | 2 | 4 | 3 | 3 | 3 | 17 | 0 | 0 | 1 | 3 | 1 | 23 | 2 | Transformation Digitale | Frequent | 0 | 3 |
| 1466 | 39 | M | 9991 | Marié(e) | Consulting | Manager | 4 | 80 | 9 | 7 | 7 | 4 | 2 | 3 | 1 | 1 | 3 | 3 | 15 | 0 | 0 | 1 | 5 | 1 | 6 | 1 | Transformation Digitale | Occasionnel | 1 | 7 |
| 1467 | 27 | M | 6142 | Marié(e) | Consulting | Tech Lead | 1 | 80 | 6 | 6 | 2 | 2 | 4 | 2 | 2 | 2 | 3 | 4 | 20 | 1 | 0 | 1 | 0 | 1 | 4 | 3 | Infra & Cloud | Occasionnel | 0 | 3 |
| 1468 | 49 | M | 5390 | Marié(e) | Commercial | Cadre Commercial | 2 | 80 | 17 | 9 | 6 | 4 | 2 | 2 | 2 | 4 | 2 | 3 | 14 | 0 | 0 | 0 | 3 | 1 | 2 | 3 | Transformation Digitale | Frequent | 0 | 8 |
| 1469 | 34 | M | 4404 | Marié(e) | Consulting | Consultant | 2 | 80 | 6 | 4 | 3 | 2 | 4 | 2 | 3 | 1 | 4 | 3 | 12 | 0 | 0 | 0 | 3 | 1 | 8 | 3 | Transformation Digitale | Occasionnel | 1 | 2 |
1470 rows × 30 columns
Afin de garantir la cohérence des analyses et la disponibilité de l’ensemble des variables explicatives et de la cible d’attrition, seules les observations présentes dans l’ensemble des sources de données ont été conservées via une jointure interne.
Nous supprimons ensuite les identifiants qui ne serviront pas à l'analyse et à la modélisation.
Nous obtenons bien, après jointure, 1470 employés décrit par 30 variables.
Vérification de la qualité des variables catégorielles¶
#identification des variables catégorielles
cat_cols = df.select_dtypes(include="str").columns
cat_cols
Index(['genre', 'statut_marital', 'departement', 'poste', 'domaine_etude',
'frequence_deplacement'],
dtype='str')
for col in cat_cols:
print("\n", col)
print(df[col].value_counts())
genre genre M 882 F 588 Name: count, dtype: int64 statut_marital statut_marital Marié(e) 673 Célibataire 470 Divorcé(e) 327 Name: count, dtype: int64 departement departement Consulting 961 Commercial 446 Ressources Humaines 63 Name: count, dtype: int64 poste poste Cadre Commercial 326 Assistant de Direction 292 Consultant 259 Tech Lead 145 Manager 131 Senior Manager 102 Représentant Commercial 83 Directeur Technique 80 Ressources Humaines 52 Name: count, dtype: int64 domaine_etude domaine_etude Infra & Cloud 606 Transformation Digitale 464 Marketing 159 Entrepreunariat 132 Autre 82 Ressources Humaines 27 Name: count, dtype: int64 frequence_deplacement frequence_deplacement Occasionnel 1043 Frequent 277 Aucun 150 Name: count, dtype: int64
Les variables catégorielles présentent une structure propre et cohérente.
Aucune correction syntaxique ou regroupement n’a été appliqué à ce stade.
Les éventuelles redondances sémantiques entre certaines variables (poste, département, domaine d’étude) seront analysées lors de l’analyse exploratoire et de la phase de modélisation.
Verification de la qualité des variables numériques¶
#identification des variables numériques
num_cols = df.select_dtypes(include="number").columns
num_cols
Index(['age', 'revenu_mensuel', 'nombre_experiences_precedentes',
'nombre_heures_travailless', 'annee_experience_totale',
'annees_dans_l_entreprise', 'annees_dans_le_poste_actuel',
'satisfaction_employee_environnement', 'note_evaluation_precedente',
'niveau_hierarchique_poste', 'satisfaction_employee_nature_travail',
'satisfaction_employee_equipe',
'satisfaction_employee_equilibre_pro_perso', 'note_evaluation_actuelle',
'augmentation_salaire_precedente_pct', 'heures_supplementaires',
'a_quitte_l_entreprise', 'nombre_participation_pee',
'nb_formations_suivies', 'nombre_employee_sous_responsabilite',
'distance_domicile_travail', 'niveau_education',
'annees_depuis_la_derniere_promotion', 'annes_sous_responsable_actuel'],
dtype='str')
df[num_cols].describe().T
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| age | 1470.0 | 36.923810 | 9.135373 | 18.0 | 30.0 | 36.0 | 43.0 | 60.0 |
| revenu_mensuel | 1470.0 | 6502.931293 | 4707.956783 | 1009.0 | 2911.0 | 4919.0 | 8379.0 | 19999.0 |
| nombre_experiences_precedentes | 1470.0 | 2.693197 | 2.498009 | 0.0 | 1.0 | 2.0 | 4.0 | 9.0 |
| nombre_heures_travailless | 1470.0 | 80.000000 | 0.000000 | 80.0 | 80.0 | 80.0 | 80.0 | 80.0 |
| annee_experience_totale | 1470.0 | 11.279592 | 7.780782 | 0.0 | 6.0 | 10.0 | 15.0 | 40.0 |
| annees_dans_l_entreprise | 1470.0 | 7.008163 | 6.126525 | 0.0 | 3.0 | 5.0 | 9.0 | 40.0 |
| annees_dans_le_poste_actuel | 1470.0 | 4.229252 | 3.623137 | 0.0 | 2.0 | 3.0 | 7.0 | 18.0 |
| satisfaction_employee_environnement | 1470.0 | 2.721769 | 1.093082 | 1.0 | 2.0 | 3.0 | 4.0 | 4.0 |
| note_evaluation_precedente | 1470.0 | 2.729932 | 0.711561 | 1.0 | 2.0 | 3.0 | 3.0 | 4.0 |
| niveau_hierarchique_poste | 1470.0 | 2.063946 | 1.106940 | 1.0 | 1.0 | 2.0 | 3.0 | 5.0 |
| satisfaction_employee_nature_travail | 1470.0 | 2.728571 | 1.102846 | 1.0 | 2.0 | 3.0 | 4.0 | 4.0 |
| satisfaction_employee_equipe | 1470.0 | 2.712245 | 1.081209 | 1.0 | 2.0 | 3.0 | 4.0 | 4.0 |
| satisfaction_employee_equilibre_pro_perso | 1470.0 | 2.761224 | 0.706476 | 1.0 | 2.0 | 3.0 | 3.0 | 4.0 |
| note_evaluation_actuelle | 1470.0 | 3.153741 | 0.360824 | 3.0 | 3.0 | 3.0 | 3.0 | 4.0 |
| augmentation_salaire_precedente_pct | 1470.0 | 15.209524 | 3.659938 | 11.0 | 12.0 | 14.0 | 18.0 | 25.0 |
| heures_supplementaires | 1470.0 | 0.282993 | 0.450606 | 0.0 | 0.0 | 0.0 | 1.0 | 1.0 |
| a_quitte_l_entreprise | 1470.0 | 0.161224 | 0.367863 | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 |
| nombre_participation_pee | 1470.0 | 0.793878 | 0.852077 | 0.0 | 0.0 | 1.0 | 1.0 | 3.0 |
| nb_formations_suivies | 1470.0 | 2.799320 | 1.289271 | 0.0 | 2.0 | 3.0 | 3.0 | 6.0 |
| nombre_employee_sous_responsabilite | 1470.0 | 1.000000 | 0.000000 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 |
| distance_domicile_travail | 1470.0 | 9.192517 | 8.106864 | 1.0 | 2.0 | 7.0 | 14.0 | 29.0 |
| niveau_education | 1470.0 | 2.912925 | 1.024165 | 1.0 | 2.0 | 3.0 | 4.0 | 5.0 |
| annees_depuis_la_derniere_promotion | 1470.0 | 2.187755 | 3.222430 | 0.0 | 0.0 | 1.0 | 3.0 | 15.0 |
| annes_sous_responsable_actuel | 1470.0 | 4.123129 | 3.568136 | 0.0 | 2.0 | 3.0 | 7.0 | 17.0 |
df = df.drop(columns=[
"nombre_heures_travailless",
"nombre_employee_sous_responsabilite"
])
(df["annees_dans_le_poste_actuel"] > df["annees_dans_l_entreprise"]).sum()
#(df["annees_dans_l_entreprise"] > df["annee_experience_totale"]).sum()
#etc.
#ok cohérent
np.int64(0)
Les variables numériques ont été contrôlées selon des règles métier simples.
Aucune incohérence majeure n’a été détectée.
Deux variables constantes ont été supprimées (nombre_employee_sous_responsabilite et nombre_heures_travailless).
Le jeu de données est désormais considéré comme prêt pour l’analyse exploratoire.
Section 3 : Analyse exploratoire (RH - oriented)
Objectif EDA : Identifier les différences clés entre les employés partis et restants.
sns.set_theme(
style="whitegrid",
palette=["#6ca57a"],
context="talk"
)
Photographie globale de l'attrition¶
Objectif : Comprendre la structure générale de la cible avant toute analyse détaillée.
attrition_rate = df["a_quitte_l_entreprise"].mean()
n_total = len(df)
n_depart = df["a_quitte_l_entreprise"].sum()
n_restant = n_total - n_depart
print(f"Le taux d'attrition global : {attrition_rate*100:.2f} %")
print(f"Sur un total de {n_total} employés, {n_depart} ont quitté l’entreprise.")
Le taux d'attrition global : 16.12 % Sur un total de 1470 employés, 237 ont quitté l’entreprise.
plt.figure(figsize=(8,8))
ax = sns.countplot(
data=df,
x="a_quitte_l_entreprise"
)
ax.set_xticklabels(["Restés", "Partis"])
# Ajouter les pourcentages
for i, count in enumerate([n_restant, n_depart]):
percentage = count / n_total * 100
ax.text(i, count + 10, f"{percentage:.2f}%", ha="center")
plt.title("Répartition globale des employés\n")
plt.xlabel("")
plt.ylabel("Nombre d'employés")
plt.show()
C:\Users\bouzo\AppData\Local\Temp\ipykernel_6572\3709298785.py:8: UserWarning: set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator. ax.set_xticklabels(["Restés", "Partis"])
Environ 16 % des employés ont quitté l’entreprise, ce qui confirme une situation d’attrition significative.
Notons que le jeu de données présente un désequilibre des classes.
Nous allons procéder à une EDA selon plusieurs blocs :
- Profil de l'employé
- Parcours académique & développement
- Expérience & trajectoire interne
- Satisfaction & engagement des employés
- Rémunération & performance
- Organisation & structure
- Charge & mobilité
Bloc 1 : Profil de l'employé¶
Variables : age, genre, statut_marital
Objectif : Identifier si le profil sociodémographique distingue les employés partis des employés restés.
- Âge — Les partants sont-ils plus jeunes ou plus âgés ?
df.groupby("a_quitte_l_entreprise")["age"].describe()
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| a_quitte_l_entreprise | ||||||||
| 0 | 1233.0 | 37.561233 | 8.88836 | 18.0 | 31.0 | 36.0 | 43.0 | 60.0 |
| 1 | 237.0 | 33.607595 | 9.68935 | 18.0 | 28.0 | 32.0 | 39.0 | 58.0 |
plt.figure(figsize=(6,5))
sns.boxplot(
data=df,
x="a_quitte_l_entreprise",
y="age"
)
plt.xticks([0,1], ["Restés", "Partis"])
plt.title("Distribution de l'âge selon le statut de départ\n")
plt.xlabel("")
plt.ylabel("Âge")
plt.show()
La différence est visible mais les distributions se chevauchent largement. L'âge ne semble pas être un déterminant seul. De plus, l'âge pourrait être un proxy de salaire/d'ancienneté/de niveau hierarchique.
df["age_groupe"] = df["age"].apply(
lambda x: "jeune" if x < 39 else "senior"
)
groupe_age_attrition = (
df.groupby("age_groupe")["a_quitte_l_entreprise"]
.mean()
.reset_index()
)
groupe_age_attrition
| age_groupe | a_quitte_l_entreprise | |
|---|---|---|
| 0 | jeune | 0.192053 |
| 1 | senior | 0.111702 |
df.drop(columns="age_groupe", inplace=True)
- Genre — Y a-t-il une différence réelle ?
genre_attrition = (
df.groupby("genre")["a_quitte_l_entreprise"]
.mean()
.reset_index()
)
genre_attrition
| genre | a_quitte_l_entreprise | |
|---|---|---|
| 0 | F | 0.147959 |
| 1 | M | 0.170068 |
plt.figure(figsize=(6,5))
sns.barplot(
data=genre_attrition,
x="genre",
y="a_quitte_l_entreprise"
)
plt.title("Taux d'attrition selon le genre\n")
plt.xlabel("Genre")
plt.ylabel("Taux d'attrition")
plt.show()
Le taux d’attrition est légèrement plus élevé chez les hommes (17,0 %) que chez les femmes (14,8 %).
Toutefois, l’écart reste modéré et ne suggère pas un déséquilibre majeur lié au genre.
- Statut marital — Le statut marital influence-t-il l'attrition ?
statut_attrition = (
df.groupby("statut_marital")["a_quitte_l_entreprise"]
.mean()
.sort_values(ascending=False)
.reset_index()
)
statut_attrition
| statut_marital | a_quitte_l_entreprise | |
|---|---|---|
| 0 | Célibataire | 0.255319 |
| 1 | Marié(e) | 0.124814 |
| 2 | Divorcé(e) | 0.100917 |
plt.figure(figsize=(7,5))
sns.barplot(
data=statut_attrition,
x="a_quitte_l_entreprise",
y="statut_marital"
)
plt.title("Taux d'attrition selon le statut marital\n")
plt.xlabel("Taux d'attrition")
plt.ylabel("")
plt.show()
Les employés célibataires présentent un taux d’attrition significativement plus élevé (25,5 %) que les employés mariés (12,5 %) ou divorcés (10,1 %).
Bloc 2 : Parcours académique & développement¶
Variables : niveau_education,domaine_etude,nb_formations_suivies , a_suivi_formation (nouvelle variable)
Objectif : évaluer dans quelle mesure le parcours académique et les dispositifs de développement des compétences constituent des facteurs de rétention, ou au contraire, de vulnérabilité au turnover.
- Niveau éducation
df["niveau_education"].value_counts(normalize=True).sort_index()
niveau_education 1 0.115646 2 0.191837 3 0.389116 4 0.270748 5 0.032653 Name: proportion, dtype: float64
attrition_edu = (
df.groupby("niveau_education")["a_quitte_l_entreprise"]
.mean()
.reset_index()
)
attrition_edu
| niveau_education | a_quitte_l_entreprise | |
|---|---|---|
| 0 | 1 | 0.182353 |
| 1 | 2 | 0.156028 |
| 2 | 3 | 0.173077 |
| 3 | 4 | 0.145729 |
| 4 | 5 | 0.104167 |
plt.figure(figsize=(8,5))
sns.barplot(
data=attrition_edu,
x="niveau_education",
y="a_quitte_l_entreprise"
)
plt.title("Taux d'attrition selon le niveau d'éducation")
plt.xlabel("Niveau d'éducation")
plt.ylabel("Taux d'attrition")
plt.show()
Bien que l’effet soit modéré, le niveau d’éducation semble associé à une plus grande stabilité professionnelle.
- Domaine d'études
df["domaine_etude"].value_counts(normalize=True)
domaine_etude Infra & Cloud 0.412245 Transformation Digitale 0.315646 Marketing 0.108163 Entrepreunariat 0.089796 Autre 0.055782 Ressources Humaines 0.018367 Name: proportion, dtype: float64
attrition_domain = (
df.groupby("domaine_etude")["a_quitte_l_entreprise"]
.mean()
.sort_values(ascending=False)
.reset_index()
)
attrition_domain
| domaine_etude | a_quitte_l_entreprise | |
|---|---|---|
| 0 | Ressources Humaines | 0.259259 |
| 1 | Entrepreunariat | 0.242424 |
| 2 | Marketing | 0.220126 |
| 3 | Infra & Cloud | 0.146865 |
| 4 | Transformation Digitale | 0.135776 |
| 5 | Autre | 0.134146 |
plt.figure(figsize=(10,6))
sns.barplot(
data=attrition_domain,
y="domaine_etude",
x="a_quitte_l_entreprise"
)
plt.title("Taux d'attrition par domaine d'étude")
plt.xlabel("Taux d'attrition")
plt.ylabel("Domaine d'étude")
plt.show()
df.groupby("domaine_etude")["a_quitte_l_entreprise"].agg(["count","sum"])
| count | sum | |
|---|---|---|
| domaine_etude | ||
| Autre | 82 | 11 |
| Entrepreunariat | 132 | 32 |
| Infra & Cloud | 606 | 89 |
| Marketing | 159 | 35 |
| Ressources Humaines | 27 | 7 |
| Transformation Digitale | 464 | 63 |
Le taux d’attrition varie selon le domaine d’étude.
Les profils issus du marketing, de l’entrepreneuriat et des ressources humaines présentent des taux d’attrition plus élevés, tandis que les profils techniques (Infra & Cloud, Transformation Digitale) apparaissent plus stables.
Toutefois, certaines catégories étant faiblement représentées (notamment RH), ces résultats doivent être interprétés avec prudence.
- Montée en compétence/ formation
df.groupby("a_quitte_l_entreprise")["nb_formations_suivies"].describe()
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| a_quitte_l_entreprise | ||||||||
| 0 | 1233.0 | 2.832928 | 1.293585 | 0.0 | 2.0 | 3.0 | 3.0 | 6.0 |
| 1 | 237.0 | 2.624473 | 1.254784 | 0.0 | 2.0 | 2.0 | 3.0 | 6.0 |
plt.figure(figsize=(8,5))
sns.boxplot(
data=df,
x="a_quitte_l_entreprise",
y="nb_formations_suivies"
)
plt.title("Nombre de formations suivies selon le statut de départ")
plt.xlabel("Statut de départ")
plt.ylabel("Nombre de formations")
plt.show()
La variable nb_formations_suivies peu informative. Création d'une nouvelle variable (binaire) :
df["a_suivi_formation"] = (df["nb_formations_suivies"] > 0).astype(int)
df.groupby("a_suivi_formation")["a_quitte_l_entreprise"].mean()
a_suivi_formation 0 0.277778 1 0.156780 Name: a_quitte_l_entreprise, dtype: float64
plt.figure(figsize=(6,4))
sns.barplot(
data=df,
x="a_suivi_formation",
y="a_quitte_l_entreprise"
)
plt.xticks([0,1], ["Aucune formation", "A suivi formation"])
plt.title("Taux d'attrition selon la participation aux formations")
plt.ylabel("Taux d'attrition")
plt.xlabel("")
plt.show()
Les employés n’ayant suivi aucune formation présentent un taux d’attrition de 27,8 %, contre 15,7 % pour ceux ayant bénéficié d’au moins une formation.
L’investissement en formation apparaît ainsi comme un levier potentiel de fidélisation.
Bloc 3 : Expérience & trajectoire (externe et interne)¶
Variables : nombre_experiences_precedentes, annee_experience_avant_entreprise (nouvelle variable), annees_dans_l_entreprise, mobilité_interne (nouvelle variable).
Objectif : Comprendre si le parcours professionnel (expérience passée et trajcetoire interne) distingue les employés partis des restants.
- Feature Engineering
#Création de la variable "annees_experience_avant_entreprise"
df["annee_experience_avant_entreprise"] = (
df["annee_experience_totale"] - df["annees_dans_l_entreprise"]
)
#Création de la variable "annees_interne_avant_poste_actuel"
df["annees_interne_avant_poste_actuel"] = (
df["annees_dans_l_entreprise"] - df["annees_dans_le_poste_actuel"]
)
#Création de la variable "mobilite_interne"
# 0 = jamais changé de poste ; 1 = a déjà évolué en interne
df["mobilite_interne"] = (df["annees_interne_avant_poste_actuel"] > 0).astype(int)
df
| age | genre | revenu_mensuel | statut_marital | departement | poste | nombre_experiences_precedentes | annee_experience_totale | annees_dans_l_entreprise | annees_dans_le_poste_actuel | satisfaction_employee_environnement | note_evaluation_precedente | niveau_hierarchique_poste | satisfaction_employee_nature_travail | satisfaction_employee_equipe | satisfaction_employee_equilibre_pro_perso | note_evaluation_actuelle | augmentation_salaire_precedente_pct | heures_supplementaires | a_quitte_l_entreprise | nombre_participation_pee | nb_formations_suivies | distance_domicile_travail | niveau_education | domaine_etude | frequence_deplacement | annees_depuis_la_derniere_promotion | annes_sous_responsable_actuel | a_suivi_formation | annee_experience_avant_entreprise | annees_interne_avant_poste_actuel | mobilite_interne | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 41 | F | 5993 | Célibataire | Commercial | Cadre Commercial | 8 | 8 | 6 | 4 | 2 | 3 | 2 | 4 | 1 | 1 | 3 | 11 | 1 | 1 | 0 | 0 | 1 | 2 | Infra & Cloud | Occasionnel | 0 | 5 | 0 | 2 | 2 | 1 |
| 1 | 49 | M | 5130 | Marié(e) | Consulting | Assistant de Direction | 1 | 10 | 10 | 7 | 3 | 2 | 2 | 2 | 4 | 3 | 4 | 23 | 0 | 0 | 1 | 3 | 8 | 1 | Infra & Cloud | Frequent | 1 | 7 | 1 | 0 | 3 | 1 |
| 2 | 37 | M | 2090 | Célibataire | Consulting | Consultant | 6 | 7 | 0 | 0 | 4 | 2 | 1 | 3 | 2 | 3 | 3 | 15 | 1 | 1 | 0 | 3 | 2 | 2 | Autre | Occasionnel | 0 | 0 | 1 | 7 | 0 | 0 |
| 3 | 33 | F | 2909 | Marié(e) | Consulting | Assistant de Direction | 1 | 8 | 8 | 7 | 4 | 3 | 1 | 3 | 3 | 3 | 3 | 11 | 1 | 0 | 0 | 3 | 3 | 4 | Infra & Cloud | Frequent | 3 | 0 | 1 | 0 | 1 | 1 |
| 4 | 27 | M | 3468 | Marié(e) | Consulting | Consultant | 9 | 6 | 2 | 2 | 1 | 3 | 1 | 2 | 4 | 3 | 3 | 12 | 0 | 0 | 1 | 3 | 2 | 1 | Transformation Digitale | Occasionnel | 2 | 2 | 1 | 4 | 0 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1465 | 36 | M | 2571 | Marié(e) | Consulting | Consultant | 4 | 17 | 5 | 2 | 3 | 4 | 2 | 4 | 3 | 3 | 3 | 17 | 0 | 0 | 1 | 3 | 23 | 2 | Transformation Digitale | Frequent | 0 | 3 | 1 | 12 | 3 | 1 |
| 1466 | 39 | M | 9991 | Marié(e) | Consulting | Manager | 4 | 9 | 7 | 7 | 4 | 2 | 3 | 1 | 1 | 3 | 3 | 15 | 0 | 0 | 1 | 5 | 6 | 1 | Transformation Digitale | Occasionnel | 1 | 7 | 1 | 2 | 0 | 0 |
| 1467 | 27 | M | 6142 | Marié(e) | Consulting | Tech Lead | 1 | 6 | 6 | 2 | 2 | 4 | 2 | 2 | 2 | 3 | 4 | 20 | 1 | 0 | 1 | 0 | 4 | 3 | Infra & Cloud | Occasionnel | 0 | 3 | 0 | 0 | 4 | 1 |
| 1468 | 49 | M | 5390 | Marié(e) | Commercial | Cadre Commercial | 2 | 17 | 9 | 6 | 4 | 2 | 2 | 2 | 4 | 2 | 3 | 14 | 0 | 0 | 0 | 3 | 2 | 3 | Transformation Digitale | Frequent | 0 | 8 | 1 | 8 | 3 | 1 |
| 1469 | 34 | M | 4404 | Marié(e) | Consulting | Consultant | 2 | 6 | 4 | 3 | 2 | 4 | 2 | 3 | 1 | 4 | 3 | 12 | 0 | 0 | 0 | 3 | 8 | 3 | Transformation Digitale | Occasionnel | 1 | 2 | 1 | 2 | 1 | 1 |
1470 rows × 32 columns
- Expérience externe (avant l'entreprise)
df.groupby("a_quitte_l_entreprise")["nombre_experiences_precedentes"].describe()
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| a_quitte_l_entreprise | ||||||||
| 0 | 1233.0 | 2.645580 | 2.460090 | 0.0 | 1.0 | 2.0 | 4.0 | 9.0 |
| 1 | 237.0 | 2.940928 | 2.678519 | 0.0 | 1.0 | 1.0 | 5.0 | 9.0 |
plt.figure(figsize=(6,5))
sns.boxplot(
data=df,
x="a_quitte_l_entreprise",
y="nombre_experiences_precedentes"
)
plt.xticks([0,1], ["Restés", "Partis"])
plt.title("Nombre d'expériences précédentes selon le statut de départ")
plt.xlabel("")
plt.ylabel("Nombre d'expériences")
plt.show()
La distribution est globalement similaire entre les deux groupes.
df.groupby("a_quitte_l_entreprise")["annee_experience_avant_entreprise"].describe()
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| a_quitte_l_entreprise | ||||||||
| 0 | 1233.0 | 4.493917 | 6.414283 | 0.0 | 0.0 | 2.0 | 6.0 | 33.0 |
| 1 | 237.0 | 3.113924 | 4.618462 | 0.0 | 0.0 | 1.0 | 4.0 | 22.0 |
plt.figure(figsize=(6,5))
sns.boxplot(
data=df,
x="a_quitte_l_entreprise",
y="annee_experience_avant_entreprise"
)
plt.xticks([0,1], ["Restés", "Partis"])
plt.title("Expérience avant l'entreprise selon le statut de départ")
plt.xlabel("")
plt.ylabel("Années")
plt.show()
- Expérience interne
df.groupby("a_quitte_l_entreprise")["annees_dans_l_entreprise"].describe()
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| a_quitte_l_entreprise | ||||||||
| 0 | 1233.0 | 7.369019 | 6.096298 | 0.0 | 3.0 | 6.0 | 10.0 | 37.0 |
| 1 | 237.0 | 5.130802 | 5.949984 | 0.0 | 1.0 | 3.0 | 7.0 | 40.0 |
sns.violinplot(
data=df,
x="a_quitte_l_entreprise",
y="annees_dans_l_entreprise",
inner="quartile"
)
plt.xticks([0, 1], ["Restés", "Partis"])
plt.title("Ancienneté dans l'entreprise selon le statut de départ")
plt.xlabel("")
plt.ylabel("Années dans l'entreprise")
plt.show()
Le risque de départ est fortement concentré dans les premières années.
df.groupby("mobilite_interne")["a_quitte_l_entreprise"].mean()
mobilite_interne 0 0.227848 1 0.148418 Name: a_quitte_l_entreprise, dtype: float64
plt.figure(figsize=(6,5))
sns.barplot(
data=df,
x="mobilite_interne",
y="a_quitte_l_entreprise"
)
plt.xticks([0,1], ["Pas de mobilité", "Mobilité interne"])
plt.title("Taux d'attrition selon la mobilité interne")
plt.xlabel("")
plt.ylabel("Taux d'attrition")
plt.show()
Les employés n’ayant jamais changé de poste présentent un taux d’attrition significativement plus élevé. La mobilité interne semble associée à une meilleure rétention des collaborateurs.
Bloc 4 : Satisfaction & engagement des employés¶
Variables : satisfaction_employee_environnement,satisfaction_employee_nature_travail,satisfaction_employee_equipe,satifaction_employee_equilibre_pro_perso
Objectif : Identifier si le ressenti des employés est associé au départ.
En effet, les variables de ce bloc reposent sur des évaluations déclaratives. Elles traduisent le ressenti des employés et peuvent refléter leur niveau d’engagement perçu.
satisfaction_cols = [
"satisfaction_employee_environnement",
"satisfaction_employee_nature_travail",
"satisfaction_employee_equipe",
"satisfaction_employee_equilibre_pro_perso"
]
# Moyennes par groupe
sat = df.groupby("a_quitte_l_entreprise")[satisfaction_cols].mean().T
sat.columns = ["Restés", "Partis"]
# Écarts
sat["Écart (Partis - Restés)"] = sat["Partis"] - sat["Restés"]
sat["% Écart"] = sat["Écart (Partis - Restés)"] / sat["Restés"] * 100
# Labels plus courts
rename_map = {
"satisfaction_employee_environnement": "Environnement",
"satisfaction_employee_nature_travail": "Nature du travail",
"satisfaction_employee_equipe": "Équipe",
"satisfaction_employee_equilibre_pro_perso": "Équilibre pro/perso",
}
sat = sat.rename(index=rename_map)
sat = sat.round(3)
sat
| Restés | Partis | Écart (Partis - Restés) | % Écart | |
|---|---|---|---|---|
| Environnement | 2.771 | 2.464 | -0.307 | -11.083 |
| Nature du travail | 2.779 | 2.468 | -0.310 | -11.165 |
| Équipe | 2.734 | 2.599 | -0.135 | -4.931 |
| Équilibre pro/perso | 2.781 | 2.658 | -0.123 | -4.415 |
Les employés ayant quitté l’entreprise présentent des niveaux de satisfaction systématiquement plus faibles sur l’ensemble des dimensions analysées.
# Trier par écart (du plus négatif au moins négatif)
plot_df = sat[["Écart (Partis - Restés)"]].sort_values("Écart (Partis - Restés)")
plt.figure(figsize=(16, 6))
ax = sns.barplot(
x="Écart (Partis - Restés)",
y=plot_df.index,
data=plot_df.reset_index().rename(columns={"index": "Dimension"}),
orient="h"
)
ax.axvline(0, linewidth=1) # ligne de référence
ax.set_title("Écart de satisfaction (Partis - Restés) par dimension")
ax.set_xlabel("Écart moyen (points sur échelle 1–4)")
ax.set_ylabel("")
# Ajouter les valeurs sur les barres
for container in ax.containers:
ax.bar_label(container, fmt="%.2f", padding=3)
plt.tight_layout()
plt.show()
cohen_d = {}
for col in satisfaction_cols:
group1 = df[df["a_quitte_l_entreprise"] == 0][col] # Restés
group2 = df[df["a_quitte_l_entreprise"] == 1][col] # Partis
mean_diff = group2.mean() - group1.mean()
pooled_std = np.sqrt(
((group1.std() ** 2) + (group2.std() ** 2)) / 2
)
cohen_d[col] = mean_diff / pooled_std
cohen_d
{'satisfaction_employee_environnement': np.float64(-0.27386678816210647),
'satisfaction_employee_nature_travail': np.float64(-0.2805679349258372),
'satisfaction_employee_equipe': np.float64(-0.12269740817644381),
'satisfaction_employee_equilibre_pro_perso': np.float64(-0.1632478303798571)}
Les écarts les plus marqués concernent :
La nature du travail (écart moyen = -0.31 ; Cohen’s d ≈ -0.28)
L’environnement de travail (écart moyen = -0.31 ; Cohen’s d ≈ -0.27)
Ces effets, bien que modérés, suggèrent que le contenu du travail et le cadre professionnel constituent des facteurs différenciants dans les départs.
À l’inverse, les dimensions liées à l’équipe et à l’équilibre vie pro-perso présentent des écarts plus faibles (Cohen’s d < 0.2), indiquant un pouvoir explicatif plus limité.
Ces résultats suggèrent que l’attrition semble davantage associée à l’expérience professionnelle vécue qu’aux relations humaines ou à l’équilibre personnel.
Bloc 5 : Performance, rémunération & évolution¶
Variables : note_evaluation_actuelle, evolution_note (nouvelle variable), variation_note_cat (nouvelle variable juste pour EDA), revenu_mensuel, augmentation_salaire_precedente_pct , nombre_participation_pee, utilisation_pee (nouvelle variable juste pour EDA), annees_depuis_la_dernière_promotion
Objectif : Evaluer si la reconnaissance, la progression et la rémunération sont associées aux départs.
- Performance
df.groupby("a_quitte_l_entreprise")["note_evaluation_actuelle"].describe()
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| a_quitte_l_entreprise | ||||||||
| 0 | 1233.0 | 3.153285 | 0.360408 | 3.0 | 3.0 | 3.0 | 3.0 | 4.0 |
| 1 | 237.0 | 3.156118 | 0.363735 | 3.0 | 3.0 | 3.0 | 3.0 | 4.0 |
L’analyse de la note d’évaluation actuelle ne révèle aucune différence significative entre les employés restés et ceux ayant quitté l’entreprise (moyenne ≈ 3.15 dans les deux groupes).
La performance actuelle ne semble donc pas constituer un facteur explicatif direct des départs.
df["evolution_note"] = (
df["note_evaluation_actuelle"] -
df["note_evaluation_precedente"]
)
df.groupby("a_quitte_l_entreprise")["evolution_note"].describe()
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| a_quitte_l_entreprise | ||||||||
| 0 | 1233.0 | 0.382806 | 0.789190 | -1.0 | 0.0 | 0.0 | 1.0 | 3.0 |
| 1 | 237.0 | 0.637131 | 0.865519 | -1.0 | 0.0 | 0.0 | 1.0 | 3.0 |
En revanche, l’évolution de la note entre deux évaluations apporte un éclairage intéressant. Les employés ayant quitté l’entreprise présentent en moyenne une progression plus importante de leur note (+0.64 contre +0.38).
df["variation_note_cat"] = pd.cut(
df["evolution_note"],
bins=[-10, -0.1, 0.1, 10],
labels=["Baisse", "Stable", "Hausse"]
)
pd.crosstab(
df["variation_note_cat"],
df["a_quitte_l_entreprise"],
normalize="index"
)
| a_quitte_l_entreprise | 0 | 1 |
|---|---|---|
| variation_note_cat | ||
| Baisse | 0.903226 | 0.096774 |
| Stable | 0.858653 | 0.141347 |
| Hausse | 0.799660 | 0.200340 |
attrition_by_variation = (
df.groupby("variation_note_cat")["a_quitte_l_entreprise"]
.mean()
.reset_index()
)
sns.barplot(
data=attrition_by_variation,
x="variation_note_cat",
y="a_quitte_l_entreprise"
)
plt.title("Taux d'attrition selon l'évolution de la note")
plt.ylabel("Taux d'attrition")
plt.xlabel("Variation de la note")
plt.show()
L’analyse catégorielle confirme ce phénomène : le taux d’attrition atteint environ 20 % chez les employés dont la note augmente, contre moins de 10 % chez ceux dont la note diminue.
Ces résultats suggèrent que les départs pourraient concerner des profils en progression, potentiellement plus attractifs sur le marché du travail. L’attrition ne semble donc pas uniquement liée à une sous-performance, mais pourrait également toucher des employés performants.
- Rémunération
df.groupby("a_quitte_l_entreprise")["revenu_mensuel"].median()
a_quitte_l_entreprise 0 5204.0 1 3202.0 Name: revenu_mensuel, dtype: float64
sns.boxplot(
data=df,
x="a_quitte_l_entreprise",
y="revenu_mensuel"
)
<Axes: xlabel='a_quitte_l_entreprise', ylabel='revenu_mensuel'>
L’analyse du revenu mensuel met en évidence une différence marquée entre les employés restés et ceux ayant quitté l’entreprise.
La médiane du revenu mensuel s’élève à 5 204 € chez les employés restés contre 3 202 € chez les employés partis, traduisant un écart significatif de niveau de rémunération.
La distribution confirme que les départs concernent majoritairement des salariés situés dans les niveaux de rémunération les plus faibles.
df.groupby("a_quitte_l_entreprise")[
"augmentation_salaire_precedente_pct"
].mean()
a_quitte_l_entreprise 0 15.231144 1 15.097046 Name: augmentation_salaire_precedente_pct, dtype: float64
sns.boxplot(
data=df,
x="a_quitte_l_entreprise",
y="augmentation_salaire_precedente_pct"
)
<Axes: xlabel='a_quitte_l_entreprise', ylabel='augmentation_salaire_precedente_pct'>
L’augmentation salariale précédente ne présente pas de différence notable entre les deux groupes (≈ 15 % dans les deux cas). Cela suggère que ce n’est pas l’évolution récente du salaire qui influence le départ, mais plutôt le niveau de rémunération global.
- Evolution & engagement
df.groupby("a_quitte_l_entreprise")["annees_depuis_la_derniere_promotion"].describe()
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| a_quitte_l_entreprise | ||||||||
| 0 | 1233.0 | 2.234388 | 3.234762 | 0.0 | 0.0 | 1.0 | 3.0 | 15.0 |
| 1 | 237.0 | 1.945148 | 3.153077 | 0.0 | 0.0 | 1.0 | 2.0 | 15.0 |
sns.boxplot(
data=df,
x="a_quitte_l_entreprise",
y="annees_depuis_la_derniere_promotion"
)
plt.title("Ancienneté depuis la dernière promotion selon le statut de départ")
plt.xlabel("")
plt.ylabel("Années depuis la dernière promotion")
plt.show()
L’analyse des années écoulées depuis la dernière promotion ne révèle pas de différence notable entre les employés restés et ceux ayant quitté l’entreprise.
La médiane est identique (1 an) et les distributions apparaissent similaires, suggérant que la stagnation promotionnelle ne constitue pas un facteur déterminant dans les départs.
df.groupby("a_quitte_l_entreprise")[
"nombre_participation_pee"
].mean()
a_quitte_l_entreprise 0 0.845093 1 0.527426 Name: nombre_participation_pee, dtype: float64
df["utilisation_pee"] = (
df["nombre_participation_pee"] > 0
).astype(int)
df.groupby("utilisation_pee")[
"a_quitte_l_entreprise"
].mean()
utilisation_pee 0 0.244057 1 0.098927 Name: a_quitte_l_entreprise, dtype: float64
attrition_pee = (
df.groupby("utilisation_pee")["a_quitte_l_entreprise"]
.mean()
.reset_index()
)
sns.barplot(
data=attrition_pee,
x="utilisation_pee",
y="a_quitte_l_entreprise"
)
plt.xticks([0,1], ["Pas de PEE", "Utilise le PEE"])
plt.ylabel("Taux d'attrition")
plt.title("Taux d'attrition selon l'utilisation du PEE")
plt.show()
En revanche, l’utilisation du Plan d’Épargne Entreprise (PEE) présente un effet marqué. Le taux d’attrition atteint environ 24 % chez les employés ne participant pas au PEE, contre moins de 10 % chez ceux qui y contribuent.
Ces résultats indiquent qu’un engagement financier à long terme au sein de l’entreprise semble fortement associé à une plus grande rétention.
Bloc 6 : Organisation & Structure du poste¶
Variables : department, poste, annees_dans_le_poste_actuel, niveau_hierarchique_poste, annees_sous_responsable_actuel
Objectif : Identifier si la position hiérarchique, le département ou encore la stabilité managériale influencent l’attrition.
- Département
attrition_dept = (
df.groupby("departement")["a_quitte_l_entreprise"]
.mean()
.sort_values(ascending=False)
.reset_index()
)
attrition_dept
| departement | a_quitte_l_entreprise | |
|---|---|---|
| 0 | Commercial | 0.206278 |
| 1 | Ressources Humaines | 0.190476 |
| 2 | Consulting | 0.138398 |
sns.barplot(
data=attrition_dept,
x="a_quitte_l_entreprise",
y="departement"
)
plt.title("Taux d'attrition par département")
plt.xlabel("Taux d'attrition")
plt.ylabel("")
plt.xlim(0, attrition_dept["a_quitte_l_entreprise"].max() * 1.1)
plt.show()
df["departement"].value_counts(normalize=True)
departement Consulting 0.653741 Commercial 0.303401 Ressources Humaines 0.042857 Name: proportion, dtype: float64
Le département Commercial semble particulièrement exposé à l’attrition, possiblement en raison des spécificités du métier (pression commerciale, mobilité externe, objectifs de performance).
Le département Consulting apparaît quant à lui plus stable.
Le département Ressources Humaines représente une part limitée de l’effectif total (≈ 4 %), ce qui peut rendre son taux d’attrition plus sensible aux variations individuelles.
- Poste
attrition_poste = (
df.groupby("poste")["a_quitte_l_entreprise"]
.mean()
.sort_values(ascending=False)
.reset_index()
)
plt.figure(figsize=(12, 6))
sns.barplot(
data=attrition_poste,
x="a_quitte_l_entreprise",
y="poste"
)
plt.title("Taux d'attrition par poste")
plt.xlabel("Taux d'attrition")
plt.ylabel("")
plt.show()
L’analyse par poste met en évidence des disparités importantes d’attrition. Les fonctions commerciales, en particulier le poste de Représentant Commercial, présentent les taux de départ les plus élevés.
df.groupby("niveau_hierarchique_poste")[
"a_quitte_l_entreprise"
].mean()
niveau_hierarchique_poste 1 0.263352 2 0.097378 3 0.146789 4 0.047170 5 0.072464 Name: a_quitte_l_entreprise, dtype: float64
sns.barplot(
data=df,
x="niveau_hierarchique_poste",
y="a_quitte_l_entreprise",
estimator="mean",
errorbar=None
)
plt.title("Taux d'attrition par niveau hiérarchique")
plt.ylabel("Taux d'attrition")
plt.xlabel("Niveau hiérarchique")
plt.show()
Ces résultats suggèrent que les positions les moins élevées dans la hiérarchie sont les plus exposées au risque de départ.
plt.figure(figsize=(8, 6))
sns.violinplot(
data=df,
x="a_quitte_l_entreprise",
y="annees_dans_le_poste_actuel",
inner="quartile"
)
plt.xticks([0, 1], ["Restés", "Partis"])
plt.title("Ancienneté dans le poste actuel")
plt.xlabel("")
plt.ylabel("Années")
plt.show()
Les départs sont plus fréquents chez les employés récemment positionnés dans leur poste.
- Stabilité managériale
sns.boxplot(
data=df,
x="a_quitte_l_entreprise",
y="annes_sous_responsable_actuel"
)
<Axes: xlabel='a_quitte_l_entreprise', ylabel='annes_sous_responsable_actuel'>
Les employés partis ont généralement une relation managériale plus récente.
Ces résultats suggèrent que les périodes de transition – prise de poste ou changement de manager – peuvent constituer des phases de vulnérabilité en matière de rétention.
Bloc 7 : Conditions de travail & contraintes professionnelles¶
Variabales : heures_supplementaires, distance_domicile_travail, distance_cat (nouvelle variable), frequence_deplacement
Objectif : Évaluer si les contraintes opérationnelles (charge, mobilité, déplacements) influencent l’attrition.
- Charge de travail
df.groupby("heures_supplementaires")[
"a_quitte_l_entreprise"
].mean()
heures_supplementaires 0 0.104364 1 0.305288 Name: a_quitte_l_entreprise, dtype: float64
attrition_heures = (
df.groupby("heures_supplementaires")["a_quitte_l_entreprise"]
.mean()
.reset_index()
)
sns.barplot(
data=attrition_heures,
x="heures_supplementaires",
y="a_quitte_l_entreprise"
)
plt.xticks([0,1], ["Pas d'heures supp", "Heures supp"])
plt.ylabel("Taux d'attrition")
plt.title("Taux d'attrition selon les heures supplémentaires")
plt.show()
L’analyse des heures supplémentaires révèle un écart marqué. Le taux d’attrition atteint 30,5 % chez les employés déclarant effectuer des heures supplémentaires, contre seulement 10,4 % chez ceux n’en effectuant pas.
Cette différence significative suggère que la charge de travail constitue un facteur majeur d’attrition. Les contraintes opérationnelles et le surcroît d’activité pourraient contribuer à un désengagement progressif ou à une recherche d’alternatives professionnelles.
df["heures_supplementaires"].value_counts(normalize=True)
heures_supplementaires 0 0.717007 1 0.282993 Name: proportion, dtype: float64
28 % des employés font des heures supplémentaires (= presque 1 employé sur 3 est concerné)
Et leur taux d’attrition est 3 fois plus élevé
- Mobilité
df.groupby("a_quitte_l_entreprise")[
"distance_domicile_travail"
].describe()
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| a_quitte_l_entreprise | ||||||||
| 0 | 1233.0 | 8.915653 | 8.012633 | 1.0 | 2.0 | 7.0 | 13.0 | 29.0 |
| 1 | 237.0 | 10.632911 | 8.452525 | 1.0 | 3.0 | 9.0 | 17.0 | 29.0 |
plt.figure(figsize=(8, 6))
sns.boxplot(
data=df,
x="a_quitte_l_entreprise",
y="distance_domicile_travail"
)
plt.xticks([0, 1], ["Restés", "Partis"])
plt.title("Distance domicile-travail selon le statut de départ")
plt.xlabel("")
plt.ylabel("Distance")
plt.show()
plt.figure(figsize=(10, 6))
sns.histplot(
data=df,
x="distance_domicile_travail",
hue="a_quitte_l_entreprise",
element="step",
stat="density",
common_norm=False
)
plt.title("Distribution de la distance domicile-travail")
plt.xlabel("Distance")
plt.ylabel("Densité")
plt.show()
Les employés ayant quitté l’entreprise habitent en moyenne plus loin (10,6 vs 8,9). La médiane est également plus élevée (9 vs 7), suggérant un effet réel bien que modéré. La distance domicile-travail semble donc être un facteur contributif, mais non déterminant à elle seule.
df["distance_cat"] = pd.cut(
df["distance_domicile_travail"],
bins=[0,5,10,20,30],
labels=["Proche","Moyen","Loin","Très loin"]
)
df.groupby("distance_cat")["a_quitte_l_entreprise"].mean()
distance_cat Proche 0.137658 Moyen 0.144670 Loin 0.200000 Très loin 0.220588 Name: a_quitte_l_entreprise, dtype: float64
sns.barplot(
data=df,
x="distance_cat",
y="a_quitte_l_entreprise",
estimator="mean",
errorbar=None
)
plt.title("Taux d'attrition par catégorie de distance")
plt.ylabel("Taux d'attrition")
plt.xlabel("Distance_cat")
plt.show()
Le taux d’attrition augmente progressivement avec la distance domicile-travail.
La distance apparaît donc comme un facteur de risque croissant, avec un effet particulièrement marqué au-delà de 10 km.
attrition_deplacement = (
df.groupby("frequence_deplacement")["a_quitte_l_entreprise"]
.mean()
.sort_values(ascending=False)
.reset_index()
)
attrition_deplacement = (
df.groupby("frequence_deplacement")["a_quitte_l_entreprise"]
.mean()
.sort_values(ascending=False)
.reset_index()
)
plt.figure(figsize=(8, 5))
sns.barplot(
data= attrition_deplacement,
x="a_quitte_l_entreprise",
y="frequence_deplacement"
)
plt.title("Taux d'attrition selon la fréquence des déplacements")
plt.xlabel("Taux d'attrition")
plt.ylabel("")
plt.show()
Le taux d’attrition augmente fortement avec la fréquence des déplacements professionnels. Les employés effectuant des déplacements fréquents présentent un taux d’attrition proche de 25 %, contre seulement 8 % pour ceux n’effectuant aucun déplacement. La mobilité professionnelle apparaît comme un facteur de risque majeur d’attrition.
Bilan EDA
L'attrition semble principalement liée à :
- Un manque d'investissement en formation
- Une absence de mobilité interne
- Une charge de travail élevée (heures supplémentaires, fréquence des déplacements)
- Une rémunération moins attractive
- Un positionnement hiérarchique peu élevé et une ancienneté faible dans le poste
- Une progression de performance non accompagnée
- Une faible inscription dans les dispositifs d'engagement long terme (ex: PEE)
Les leviers prioritaires seraient :
- Formation : renforcer l’accès à la formation + rendre les parcours de montée en compétences visibles.
- Mobilité interne : formaliser des trajectoires, ouvrir des passerelles, et définir des jalons d’évolution plus rapides.
- Charge de travail : encadrer les heures supplémentaires (pilotage, staffing, alerte managériale, prévention surcharge) ou encore les fréquence des déplacements.
- Rémunération : améliorer la compétitivité et l’équité (revue des salaires, correction des écarts, ajustements ciblés sur populations à risque / métiers en tension).
- Début de parcours : renforcer onboarding + points de suivi 30/60/90 jours + accompagnement managérial des nouveaux.
- Profils performants : mettre en place un dispositif “rétention talents” (reconnaissance, missions, évolution, package).
- Engagement long terme : augmenter l’adhésion aux dispositifs (PEE) via pédagogie, incitations, et communication RH.
Les constats précédents reposent sur une analyse descriptive des données. Ils mettent en évidence des tendances et des associations statistiques, sans permettre d’établir de relations causales.
Ces éléments constituent donc des signaux exploratoires et des hypothèses de travail.
Afin d’aller au-delà de cette lecture descriptive, une approche de modélisation prédictive sera mise en place. Un modèle de classification sera entraîné afin d’estimer, pour chaque employé, la probabilité de démission.
Cette démarche permettra de hiérarchiser les variables selon leur contribution réelle au risque d’attrition et de distinguer les facteurs structurants des simples corrélations observées.
L’interprétation des contributions des variables sera réalisée à l’aide de méthodes d’explicabilité du modèle, afin de garantir une lecture transparente et exploitable des résultats.
Exportation des données pour modélisation
- Suppression des variables redondantes :
df
| age | genre | revenu_mensuel | statut_marital | departement | poste | nombre_experiences_precedentes | annee_experience_totale | annees_dans_l_entreprise | annees_dans_le_poste_actuel | satisfaction_employee_environnement | note_evaluation_precedente | niveau_hierarchique_poste | satisfaction_employee_nature_travail | satisfaction_employee_equipe | satisfaction_employee_equilibre_pro_perso | note_evaluation_actuelle | augmentation_salaire_precedente_pct | heures_supplementaires | a_quitte_l_entreprise | nombre_participation_pee | nb_formations_suivies | distance_domicile_travail | niveau_education | domaine_etude | frequence_deplacement | annees_depuis_la_derniere_promotion | annes_sous_responsable_actuel | a_suivi_formation | annee_experience_avant_entreprise | annees_interne_avant_poste_actuel | mobilite_interne | evolution_note | variation_note_cat | utilisation_pee | distance_cat | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 41 | F | 5993 | Célibataire | Commercial | Cadre Commercial | 8 | 8 | 6 | 4 | 2 | 3 | 2 | 4 | 1 | 1 | 3 | 11 | 1 | 1 | 0 | 0 | 1 | 2 | Infra & Cloud | Occasionnel | 0 | 5 | 0 | 2 | 2 | 1 | 0 | Stable | 0 | Proche |
| 1 | 49 | M | 5130 | Marié(e) | Consulting | Assistant de Direction | 1 | 10 | 10 | 7 | 3 | 2 | 2 | 2 | 4 | 3 | 4 | 23 | 0 | 0 | 1 | 3 | 8 | 1 | Infra & Cloud | Frequent | 1 | 7 | 1 | 0 | 3 | 1 | 2 | Hausse | 1 | Moyen |
| 2 | 37 | M | 2090 | Célibataire | Consulting | Consultant | 6 | 7 | 0 | 0 | 4 | 2 | 1 | 3 | 2 | 3 | 3 | 15 | 1 | 1 | 0 | 3 | 2 | 2 | Autre | Occasionnel | 0 | 0 | 1 | 7 | 0 | 0 | 1 | Hausse | 0 | Proche |
| 3 | 33 | F | 2909 | Marié(e) | Consulting | Assistant de Direction | 1 | 8 | 8 | 7 | 4 | 3 | 1 | 3 | 3 | 3 | 3 | 11 | 1 | 0 | 0 | 3 | 3 | 4 | Infra & Cloud | Frequent | 3 | 0 | 1 | 0 | 1 | 1 | 0 | Stable | 0 | Proche |
| 4 | 27 | M | 3468 | Marié(e) | Consulting | Consultant | 9 | 6 | 2 | 2 | 1 | 3 | 1 | 2 | 4 | 3 | 3 | 12 | 0 | 0 | 1 | 3 | 2 | 1 | Transformation Digitale | Occasionnel | 2 | 2 | 1 | 4 | 0 | 0 | 0 | Stable | 1 | Proche |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1465 | 36 | M | 2571 | Marié(e) | Consulting | Consultant | 4 | 17 | 5 | 2 | 3 | 4 | 2 | 4 | 3 | 3 | 3 | 17 | 0 | 0 | 1 | 3 | 23 | 2 | Transformation Digitale | Frequent | 0 | 3 | 1 | 12 | 3 | 1 | -1 | Baisse | 1 | Très loin |
| 1466 | 39 | M | 9991 | Marié(e) | Consulting | Manager | 4 | 9 | 7 | 7 | 4 | 2 | 3 | 1 | 1 | 3 | 3 | 15 | 0 | 0 | 1 | 5 | 6 | 1 | Transformation Digitale | Occasionnel | 1 | 7 | 1 | 2 | 0 | 0 | 1 | Hausse | 1 | Moyen |
| 1467 | 27 | M | 6142 | Marié(e) | Consulting | Tech Lead | 1 | 6 | 6 | 2 | 2 | 4 | 2 | 2 | 2 | 3 | 4 | 20 | 1 | 0 | 1 | 0 | 4 | 3 | Infra & Cloud | Occasionnel | 0 | 3 | 0 | 0 | 4 | 1 | 0 | Stable | 1 | Proche |
| 1468 | 49 | M | 5390 | Marié(e) | Commercial | Cadre Commercial | 2 | 17 | 9 | 6 | 4 | 2 | 2 | 2 | 4 | 2 | 3 | 14 | 0 | 0 | 0 | 3 | 2 | 3 | Transformation Digitale | Frequent | 0 | 8 | 1 | 8 | 3 | 1 | 1 | Hausse | 0 | Proche |
| 1469 | 34 | M | 4404 | Marié(e) | Consulting | Consultant | 2 | 6 | 4 | 3 | 2 | 4 | 2 | 3 | 1 | 4 | 3 | 12 | 0 | 0 | 0 | 3 | 8 | 3 | Transformation Digitale | Occasionnel | 1 | 2 | 1 | 2 | 1 | 1 | -1 | Baisse | 0 | Moyen |
1470 rows × 36 columns
df_model = df.copy()
cols_to_drop = [
"nb_formations_suivies", #on a a_suivi_formation
"annee_experience_totale", #annee_experience_avant_entreprise + annee_dans_l_entreprise
"annees_interne_avant_poste_actuel", #utilisé uniquement pour créer mobilite_interne
"note_evaluation_precedente", #on a evolution_note + note_evolution_actuelle
"nombre_participation_pee", #on a utilisation_pee
"variation_note_cat", #on a evolution_note
"distance_cat" #on a distance_domicile_travail
]
df_model = df_model.drop(columns=cols_to_drop)
df_model
| age | genre | revenu_mensuel | statut_marital | departement | poste | nombre_experiences_precedentes | annees_dans_l_entreprise | annees_dans_le_poste_actuel | satisfaction_employee_environnement | niveau_hierarchique_poste | satisfaction_employee_nature_travail | satisfaction_employee_equipe | satisfaction_employee_equilibre_pro_perso | note_evaluation_actuelle | augmentation_salaire_precedente_pct | heures_supplementaires | a_quitte_l_entreprise | distance_domicile_travail | niveau_education | domaine_etude | frequence_deplacement | annees_depuis_la_derniere_promotion | annes_sous_responsable_actuel | a_suivi_formation | annee_experience_avant_entreprise | mobilite_interne | evolution_note | utilisation_pee | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 41 | F | 5993 | Célibataire | Commercial | Cadre Commercial | 8 | 6 | 4 | 2 | 2 | 4 | 1 | 1 | 3 | 11 | 1 | 1 | 1 | 2 | Infra & Cloud | Occasionnel | 0 | 5 | 0 | 2 | 1 | 0 | 0 |
| 1 | 49 | M | 5130 | Marié(e) | Consulting | Assistant de Direction | 1 | 10 | 7 | 3 | 2 | 2 | 4 | 3 | 4 | 23 | 0 | 0 | 8 | 1 | Infra & Cloud | Frequent | 1 | 7 | 1 | 0 | 1 | 2 | 1 |
| 2 | 37 | M | 2090 | Célibataire | Consulting | Consultant | 6 | 0 | 0 | 4 | 1 | 3 | 2 | 3 | 3 | 15 | 1 | 1 | 2 | 2 | Autre | Occasionnel | 0 | 0 | 1 | 7 | 0 | 1 | 0 |
| 3 | 33 | F | 2909 | Marié(e) | Consulting | Assistant de Direction | 1 | 8 | 7 | 4 | 1 | 3 | 3 | 3 | 3 | 11 | 1 | 0 | 3 | 4 | Infra & Cloud | Frequent | 3 | 0 | 1 | 0 | 1 | 0 | 0 |
| 4 | 27 | M | 3468 | Marié(e) | Consulting | Consultant | 9 | 2 | 2 | 1 | 1 | 2 | 4 | 3 | 3 | 12 | 0 | 0 | 2 | 1 | Transformation Digitale | Occasionnel | 2 | 2 | 1 | 4 | 0 | 0 | 1 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1465 | 36 | M | 2571 | Marié(e) | Consulting | Consultant | 4 | 5 | 2 | 3 | 2 | 4 | 3 | 3 | 3 | 17 | 0 | 0 | 23 | 2 | Transformation Digitale | Frequent | 0 | 3 | 1 | 12 | 1 | -1 | 1 |
| 1466 | 39 | M | 9991 | Marié(e) | Consulting | Manager | 4 | 7 | 7 | 4 | 3 | 1 | 1 | 3 | 3 | 15 | 0 | 0 | 6 | 1 | Transformation Digitale | Occasionnel | 1 | 7 | 1 | 2 | 0 | 1 | 1 |
| 1467 | 27 | M | 6142 | Marié(e) | Consulting | Tech Lead | 1 | 6 | 2 | 2 | 2 | 2 | 2 | 3 | 4 | 20 | 1 | 0 | 4 | 3 | Infra & Cloud | Occasionnel | 0 | 3 | 0 | 0 | 1 | 0 | 1 |
| 1468 | 49 | M | 5390 | Marié(e) | Commercial | Cadre Commercial | 2 | 9 | 6 | 4 | 2 | 2 | 4 | 2 | 3 | 14 | 0 | 0 | 2 | 3 | Transformation Digitale | Frequent | 0 | 8 | 1 | 8 | 1 | 1 | 0 |
| 1469 | 34 | M | 4404 | Marié(e) | Consulting | Consultant | 2 | 4 | 3 | 2 | 2 | 3 | 1 | 4 | 3 | 12 | 0 | 0 | 8 | 3 | Transformation Digitale | Occasionnel | 1 | 2 | 1 | 2 | 1 | -1 | 0 |
1470 rows × 29 columns
- Exportation des données :
df_model.to_csv("../data/processed/df_model_attrition.csv", index=False)