| """ |
| Module de transformation des données pour le modèle de prédiction du churn employé. |
| |
| Ce code reproduit les transformations faites dans le notebook Jupyter. |
| Les données sont d'abord validées par Pydantic (dans l'API), puis transformées ici. |
| """ |
|
|
| import pandas as pd |
|
|
|
|
| def _get_ordinal_mappings() -> dict: |
| """ |
| Reproduit les mappings du notebook pour l'OrdinalEncoder. |
| """ |
| return { |
| |
| "experience_categorie": {"Junior": 1, "Confirmé": 2, "Senior": 3, "Expert": 4}, |
| |
| "ecart_revenu_categorie": { |
| "Revenu bien inférieur à la médiane": 1, |
| "Revenu inférieur à la médiane": 2, |
| "Revenu supérieur à la médiane": 3, |
| "Revenu bien supérieur à la médiane": 4, |
| }, |
| |
| "age_categorie": {"Jeune": 1, "Jeune adulte": 2, "Adulte": 3, "Mature": 4}, |
| |
| "poste": { |
| "Représentant Commercial": 1, |
| "Ressources Humaines": 2, |
| "Assistant de Direction": 3, |
| "Consultant": 4, |
| "Manager": 5, |
| "Cadre Commercial": 6, |
| "Tech Lead": 7, |
| "Senior Manager": 8, |
| "Directeur Technique": 9, |
| }, |
| |
| "frequence_deplacement": {"Aucun": 1, "Occasionnel": 2, "Frequent": 3}, |
| } |
|
|
|
|
| def _get_bins_and_labels() -> dict: |
| """ |
| Reproduit les seuils du notebook pour les bins et labels des catégories calculées. |
| """ |
| return { |
| |
| "experience_categorie_bins": { |
| "bins": [0, 6, 10, 15, 47], |
| "labels": ["Junior", "Confirmé", "Senior", "Expert"], |
| }, |
| |
| "ecart_revenu_categorie_bins": { |
| "bins": [-20000, -744, 0, 1027.25, 20000], |
| "labels": [ |
| "Revenu bien inférieur à la médiane", |
| "Revenu inférieur à la médiane", |
| "Revenu supérieur à la médiane", |
| "Revenu bien supérieur à la médiane", |
| ], |
| }, |
| |
| "age_categorie_bins": { |
| "bins": [0, 30, 36, 43, 65], |
| "labels": ["Jeune", "Jeune adulte", "Adulte", "Mature"], |
| }, |
| } |
|
|
|
|
| def transformer_donnees(donnees_employe: dict) -> pd.DataFrame: |
| """ |
| Transforme les données d'un employé pour le modèle. |
| |
| Cette fonction reproduit les étapes du notebook : |
| 1. Calculs préliminaires : diff_note_evaluation, ratio_experience |
| 2. Création des catégories (bins) |
| 3. Suppression des colonnes brutes utilisées pour les calculs |
| 4. Application des OrdinalEncoder |
| 5. Binarization pour genre et heure_supplementaires |
| 6. OneHotEncoder pour statut_marital et departement |
| 7. Ordonnancement final des colonnes |
| 8. Contrôle de sécurité |
| |
| Args: |
| donnees_employe: Dict avec les données de l'employé (validées par Pydantic) |
| |
| Returns: |
| DataFrame prétraité avec les 24 features exactes pour le modèle |
| """ |
| |
| df = pd.DataFrame([donnees_employe]) |
|
|
| |
| df = df.drop(columns=["id_employee"], errors="ignore") |
|
|
| |
| |
|
|
| |
| df["diff_note_evaluation"] = ( |
| df["note_evaluation_actuelle"] - df["note_evaluation_precedente"] |
| ) |
|
|
| |
| df["ratio_experience"] = round( |
| df["annee_experience_totale"] / (df["annees_dans_l_entreprise"] + 1), 2 |
| ) |
|
|
| |
|
|
| bins_config = _get_bins_and_labels() |
|
|
| |
| df["experience_categorie"] = pd.cut( |
| df["annee_experience_totale"], |
| bins=bins_config["experience_categorie_bins"]["bins"], |
| labels=bins_config["experience_categorie_bins"]["labels"], |
| include_lowest=True, |
| ) |
|
|
| |
| df["age_categorie"] = pd.cut( |
| df["age"], |
| bins=bins_config["age_categorie_bins"]["bins"], |
| labels=bins_config["age_categorie_bins"]["labels"], |
| include_lowest=True, |
| ) |
|
|
| |
| df["ecart_revenu"] = df["revenu_mensuel"] - df.groupby( |
| ["poste", "experience_categorie"] |
| )["revenu_mensuel"].transform("median") |
|
|
| |
| df["ecart_revenu_categorie"] = pd.cut( |
| df["ecart_revenu"], |
| bins=bins_config["ecart_revenu_categorie_bins"]["bins"], |
| labels=bins_config["ecart_revenu_categorie_bins"]["labels"], |
| include_lowest=True, |
| ) |
|
|
| |
|
|
| |
| df = df.drop( |
| columns=[ |
| "note_evaluation_actuelle", |
| "note_evaluation_precedente", |
| "annee_experience_totale", |
| "revenu_mensuel", |
| "age", |
| "ecart_revenu", |
| ], |
| errors="ignore", |
| ) |
|
|
| |
|
|
| mappings = _get_ordinal_mappings() |
|
|
| |
| df["experience_categorie"] = df["experience_categorie"].map( |
| mappings["experience_categorie"] |
| ) |
|
|
| |
| df["age_categorie"] = df["age_categorie"].map(mappings["age_categorie"]) |
|
|
| |
| df["ecart_revenu_categorie"] = df["ecart_revenu_categorie"].map( |
| mappings["ecart_revenu_categorie"] |
| ) |
|
|
| |
| df["poste"] = df["poste"].map(mappings["poste"]) |
|
|
| |
| df["frequence_deplacement"] = df["frequence_deplacement"].map( |
| mappings["frequence_deplacement"] |
| ) |
|
|
| |
|
|
| |
| df["genre"] = df["genre"].replace({"M": 0, "F": 1}) |
|
|
| |
| df["heure_supplementaires"] = df["heure_supplementaires"].replace( |
| {"Non": 0, "Oui": 1} |
| ) |
|
|
| |
|
|
| |
| |
|
|
| |
| if "statut_marital" in df.columns: |
| |
| statut_cat = pd.Categorical( |
| df["statut_marital"], categories=["Célibataire", "Divorcé(e)", "Marié(e)"] |
| ) |
| df_statut = pd.get_dummies( |
| statut_cat, prefix="statut_marital", drop_first=True, dtype=int |
| ) |
| df = pd.concat([df.drop(columns=["statut_marital"]), df_statut], axis=1) |
|
|
| |
| if "departement" in df.columns: |
| dept_cat = pd.Categorical( |
| df["departement"], |
| categories=["Consulting", "Commercial", "Ressources Humaines"], |
| ) |
| df_dept = pd.get_dummies( |
| dept_cat, prefix="departement", drop_first=True, dtype=int |
| ) |
| df = pd.concat([df.drop(columns=["departement"]), df_dept], axis=1) |
|
|
| |
|
|
| |
| colonnes_finales = [ |
| "genre", |
| "poste", |
| "annees_dans_l_entreprise", |
| "satisfaction_employee_environnement", |
| "satisfaction_employee_nature_travail", |
| "satisfaction_employee_equipe", |
| "satisfaction_employee_equilibre_pro_perso", |
| "heure_supplementaires", |
| "augementation_salaire_precedente", |
| "nombre_participation_pee", |
| "nb_formations_suivies", |
| "distance_domicile_travail", |
| "niveau_education", |
| "frequence_deplacement", |
| "annees_depuis_la_derniere_promotion", |
| "diff_note_evaluation", |
| "ratio_experience", |
| "experience_categorie", |
| "ecart_revenu_categorie", |
| "age_categorie", |
| "statut_marital_Divorcé(e)", |
| "statut_marital_Marié(e)", |
| "departement_Commercial", |
| "departement_Ressources Humaines", |
| ] |
|
|
| |
| for col in colonnes_finales: |
| if col not in df.columns: |
| |
| df.loc[:, col] = 0 |
|
|
| |
| df = df.loc[:, colonnes_finales] |
|
|
| |
|
|
| |
| |
| for col in df.columns: |
| if df[col].dtype.name == "category": |
| df.loc[:, col] = pd.Categorical(df[col]).codes |
|
|
| |
| df = df.fillna(0) |
|
|
| |
| df = df.astype(float) |
|
|
| return df |
|
|
|
|
| def get_liste_features() -> list: |
| """ |
| Retourne la liste des 24 features attendues par le modèle. |
| """ |
| return [ |
| "genre", |
| "poste", |
| "annees_dans_l_entreprise", |
| "satisfaction_employee_environnement", |
| "satisfaction_employee_nature_travail", |
| "satisfaction_employee_equipe", |
| "satisfaction_employee_equilibre_pro_perso", |
| "heure_supplementaires", |
| "augementation_salaire_precedente", |
| "nombre_participation_pee", |
| "nb_formations_suivies", |
| "distance_domicile_travail", |
| "niveau_education", |
| "frequence_deplacement", |
| "annees_depuis_la_derniere_promotion", |
| "diff_note_evaluation", |
| "ratio_experience", |
| "experience_categorie", |
| "ecart_revenu_categorie", |
| "age_categorie", |
| "statut_marital_Divorcé(e)", |
| "statut_marital_Marié(e)", |
| "departement_Commercial", |
| "departement_Ressources Humaines", |
| ] |
|
|