Download src/utils/transformer.py from Alexis-Ravet/Employee-Churn-Prediction: direct link, hf CLI and curl.
- Browser
- Download file 10.8 kB
-
https://huggingface.co/spaces/Alexis-Ravet/Employee-Churn-Prediction/resolve/main/src/utils/transformer.py
- Command line
-
hf download hf://spaces/Alexis-Ravet/Employee-Churn-Prediction/src/utils/transformer.py
-
curl -L -o transformer.py https://huggingface.co/spaces/Alexis-Ravet/Employee-Churn-Prediction/resolve/main/src/utils/transformer.py
10.8 kB
| """ | |
| 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 | |
| "experience_categorie": {"Junior": 1, "Confirmé": 2, "Senior": 3, "Expert": 4}, | |
| # ecart_revenu_categorie: Revenu bien inférieur=1 à Revenu bien supérieur=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 | |
| "age_categorie": {"Jeune": 1, "Jeune adulte": 2, "Adulte": 3, "Mature": 4}, | |
| # poste: hiérarchie de 1 à 9 | |
| "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 | |
| "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 { | |
| # Quartiles de annee_experience_totale (min, Q1=6, Médiane=10, Q3=15, max) | |
| "experience_categorie_bins": { | |
| "bins": [0, 6, 10, 15, 47], | |
| "labels": ["Junior", "Confirmé", "Senior", "Expert"], | |
| }, | |
| # Quartiles de ecart_revenu (min, Q1=-744, Médiane=0, Q3=1027.25, max) | |
| "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", | |
| ], | |
| }, | |
| # Quartiles de age (min, Q1=30, Médiane=36, Q3=43, max) | |
| "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 | |
| """ | |
| # Créer un DataFrame pandas | |
| df = pd.DataFrame([donnees_employe]) | |
| # Supprimer l'ID avant toute transformation (non utilisé par le modèle) | |
| df = df.drop(columns=["id_employee"], errors="ignore") | |
| # ÉTAPE 1: CALCULS PRÉLIMINAIRES | |
| # Ces calculs utilisent les colonnes brutes avant suppression | |
| # diff_note_evaluation = note_evaluation_actuelle - note_evaluation_precedente | |
| df["diff_note_evaluation"] = ( | |
| df["note_evaluation_actuelle"] - df["note_evaluation_precedente"] | |
| ) | |
| # ratio_experience = annee_experience_totale / (annees_dans_l_entreprise + 1) | |
| df["ratio_experience"] = round( | |
| df["annee_experience_totale"] / (df["annees_dans_l_entreprise"] + 1), 2 | |
| ) | |
| # ÉTAPE 2: CRÉATION DES CATÉGORIES (BINS) | |
| bins_config = _get_bins_and_labels() | |
| # experience_categorie: Junior/Confirmé/Senior/Expert | |
| 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, | |
| ) | |
| # age_categorie: Jeune/Jeune adulte/Adulte/Mature | |
| df["age_categorie"] = pd.cut( | |
| df["age"], | |
| bins=bins_config["age_categorie_bins"]["bins"], | |
| labels=bins_config["age_categorie_bins"]["labels"], | |
| include_lowest=True, | |
| ) | |
| # ecart_revenu: différence avec le revenu médian par poste et expérience | |
| df["ecart_revenu"] = df["revenu_mensuel"] - df.groupby( | |
| ["poste", "experience_categorie"] | |
| )["revenu_mensuel"].transform("median") | |
| # ecart_revenu_categorie | |
| 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, | |
| ) | |
| # ÉTAPE 3: SUPPRESSION DES COLONNES BRUTES | |
| # Colonnes utilisées pour les calculs, désormais inutiles | |
| df = df.drop( | |
| columns=[ | |
| "note_evaluation_actuelle", | |
| "note_evaluation_precedente", | |
| "annee_experience_totale", | |
| "revenu_mensuel", | |
| "age", | |
| "ecart_revenu", # colonne temporaire | |
| ], | |
| errors="ignore", | |
| ) | |
| # ÉTAPE 4: ORDINAL ENCODING | |
| mappings = _get_ordinal_mappings() | |
| # experience_categorie: Junior=1, Confirmé=2, Senior=3, Expert=4 | |
| df["experience_categorie"] = df["experience_categorie"].map( | |
| mappings["experience_categorie"] | |
| ) | |
| # age_categorie: Jeune=1, Jeune adulte=2, Adulte=3, Mature=4 | |
| df["age_categorie"] = df["age_categorie"].map(mappings["age_categorie"]) | |
| # ecart_revenu_categorie: 1 à 4 | |
| df["ecart_revenu_categorie"] = df["ecart_revenu_categorie"].map( | |
| mappings["ecart_revenu_categorie"] | |
| ) | |
| # poste: 1 à 9 (hiérarchie) | |
| df["poste"] = df["poste"].map(mappings["poste"]) | |
| # frequence_deplacement: Aucun=1, Occasionnel=2, Frequent=3 | |
| df["frequence_deplacement"] = df["frequence_deplacement"].map( | |
| mappings["frequence_deplacement"] | |
| ) | |
| # ÉTAPE 5: BINARIZATION (TRANSFORMATIONS SIMPLES) | |
| # genre: M=0, F=1 | |
| df["genre"] = df["genre"].replace({"M": 0, "F": 1}) | |
| # heure_supplementaires: Non=0, Oui=1 | |
| df["heure_supplementaires"] = df["heure_supplementaires"].replace( | |
| {"Non": 0, "Oui": 1} | |
| ) | |
| # ÉTAPE 6: ONE HOT ENCODING | |
| # Utilisation de pd.Categorical pour forcer toutes les catégories | |
| # Cela garantit que toutes les colonnes sont créées même avec une seule ligne | |
| # statut_marital: drop_first=True (Célibataire supprimé) | |
| if "statut_marital" in df.columns: | |
| # Définir toutes les catégories pour drop_first=True | |
| 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) | |
| # departement: drop_first=True (Consulting supprimé) | |
| 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) | |
| # ÉTAPE 7: ORDONNANCEMENT FINAL DES COLONNES | |
| # Les 24 features finales telles qu'ordonnées dans le notebook | |
| 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", | |
| ] | |
| # Vérification que toutes les colonnes existent | |
| for col in colonnes_finales: | |
| if col not in df.columns: | |
| # Ajouter la colonne avec des zéros si pas présente (pour OneHot manquant) | |
| df.loc[:, col] = 0 | |
| # Garder seulement les colonnes finales dans le bon ordre | |
| df = df.loc[:, colonnes_finales] | |
| # ÉTAPE 8: CONTRÔLE DE SÉCURITÉ | |
| # Convertir les colonnes catégorielles en nombres | |
| # (pd.cut retourne un dtype category → cette boucle convertit chaque catégorie en son indice numérique) | |
| for col in df.columns: | |
| if df[col].dtype.name == "category": | |
| df.loc[:, col] = pd.Categorical(df[col]).codes | |
| # Remplir les valeurs NaN par 0 | |
| df = df.fillna(0) | |
| # S'assurer que tout est de type numérique | |
| 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", | |
| ] | |