Données RH : confidentialité, pseudonymisation et anonymisation

Première leçon du cours Analytics RH, en accès libre : environ 15 minutes, 3 exercices et 3 questions de quiz. C'est la leçon de base du catalogue ; dans l'espace étudiant, elle est réécrite pour ton profil.

Objectifs

  • Appliquer les principes de protection des données personnelles à une analyse RH
  • Distinguer pseudonymisation et anonymisation et pseudonymiser un fichier avec une clé secrète
  • Publier des agrégats sans permettre de réidentifier une personne (seuil de diffusion)

Confidentialité des données RHPseudonymisationSeuil de diffusion des agrégats

Des données qui concernent des personnes

Salaires, absences, évaluations, départs : l’analytics RH travaille sur les données les plus personnelles de l’entreprise. Une fuite ou une mauvaise diffusion peut nuire directement à un salarié, et la confiance des équipes dans le service RH en dépend. Ce cours commence donc par la protection des données, avant tout calcul.

Les données RH sont des données personnelles au sens du RGPD. Quelques principes établis guident toute analyse :

  • finalité : on sait pourquoi on traite les données (piloter le turnover, mesurer l’égalité salariale) et on ne les réutilise pas pour autre chose ;
  • minimisation : on ne garde que les colonnes nécessaires à cette finalité ;
  • accès limité : seules les personnes habilitées voient les données individuelles ;
  • conservation limitée et sécurité : fichiers protégés, pas de copie sur une clé USB ni dans un e-mail.

Certaines informations relèvent des catégories particulières de données (notamment la santé ou l’appartenance syndicale) et bénéficient d’une protection renforcée. Un motif d’absence médical en fait partie : on l’analyse uniquement agrégé, si c’est nécessaire, et on en parle avec le délégué à la protection des données (DPO) de l’entreprise.

Minimiser et pseudonymiser

Pour analyser des salaires par service, vous n’avez besoin ni du nom ni de l’adresse e-mail. Première étape : les supprimer. Deuxième étape : remplacer le matricule par un pseudonyme, pour pouvoir relier plusieurs fichiers sans exposer l’identifiant réel.

Un hachage simple (sha256 du matricule) ne suffit pas : les matricules sont peu nombreux et prévisibles, il suffit de hacher tous les numéros possibles pour retrouver la correspondance. On utilise une fonction à clé secrète, HMAC :

import hmac, hashlib

def pseudo(employee_id, secret):
    digest = hmac.new(secret.encode(), str(employee_id).encode(), hashlib.sha256).hexdigest()
    return digest[:12]

clean = raw.drop(columns=["name", "email"])
clean["pid"] = clean["employee_id"].map(lambda i: pseudo(i, SECRET))
clean = clean.drop(columns=["employee_id"])

Le même matricule donne toujours le même pseudonyme avec la même clé, ce qui permet les jointures. La clé est conservée séparément, par une personne habilitée, jamais dans le notebook ni dans le dépôt de code.

Pseudonymisation n’est pas anonymisation

  • Pseudonymisé : la personne n’est plus nommée, mais on peut la retrouver (avec la clé, ou en croisant des colonnes). Les données restent personnelles et le RGPD continue de s’appliquer.
  • Anonymisé : la réidentification est impossible, de façon irréversible, par quelque moyen raisonnable que ce soit. Les données sortent alors du champ du RGPD.

L’anonymisation réelle est difficile : dans une PME, « la seule femme cadre du service juridique de plus de 50 ans » est identifiable sans aucun nom. Le croisement de quelques colonnes (service, âge, sexe, date d’entrée) suffit souvent. Les techniques usuelles : généraliser (tranches d’âge plutôt qu’âge exact), supprimer les colonnes rares, et ne publier que des agrégats.

Le seuil de diffusion

Publier « salaire moyen du service juridique » quand ce service compte deux personnes revient presque à publier leurs salaires : chacun connaît le sien et déduit celui de l’autre. On applique une règle de seuil : tout indicateur calculé sur moins de N personnes (souvent 5, la valeur est une règle interne à fixer avec le DPO) est masqué.

def safe_aggregate(df, by, value, min_n=5):
    out = df.groupby(by)[value].agg(n="size", mean_value="mean")
    out["suppressed"] = out["n"] < min_n
    out.loc[out["suppressed"], "mean_value"] = float("nan")
    return out

Attention aux déductions par différence : si vous publiez le total de l’entreprise et tous les services sauf un, le service masqué se recalcule. Masquez alors un second groupe ou regroupez les petits services dans une catégorie « autres ».

À retenir

  • Minimisez : ne gardez que les colonnes utiles à la finalité.
  • Pseudonymisez avec une clé secrète conservée à part ; les données restent personnelles.
  • Ne diffusez jamais un agrégat calculé sur un trop petit groupe.

Exercices

Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.

  1. CodeDifficulté 2Python dans le navigateur2 indices

    Pseudonymiser un extrait

    Écrivez pseudonymize(df, secret) qui renvoie une copie de df :

    • sans les colonnes name et email ;
    • avec une colonne pid = les 12 premiers caractères hexadécimaux du HMAC-SHA256 de str(employee_id) avec la clé secret ;
    • sans la colonne employee_id.

    Ne modifiez pas df.

    import pandas as pd
    
    raw = pd.DataFrame({
        "employee_id": [1001, 1002, 1003, 1004],
        "name": ["Camille Roux", "Sacha Morel", "Lou Perrin", "Noa Lemaire"],
        "email": ["c.roux@exemple.test", "s.morel@exemple.test", "l.perrin@exemple.test", "n.lemaire@exemple.test"],
        "department": ["Ventes", "Tech", "Tech", "Ventes"],
        "salary": [34000, 48000, 52000, 36000],
    })
    
    import hmac
    import hashlib
    
    
    def pseudonymize(df, secret):
        ...
  2. CodeDifficulté 2Python dans le navigateur2 indices

    Agrégats avec seuil de diffusion

    Écrivez safe_aggregate(df, by, value, min_n=5) qui renvoie, par groupe, n (effectif), mean_value (moyenne de value) et suppressed (True si n < min_n) ; la moyenne des groupes masqués est remplacée par NaN. Calculez ensuite report = safe_aggregate(staff, "department", "salary") et hidden, la liste des groupes masqués.

    import pandas as pd
    
    staff = pd.DataFrame({
        "department": ["Ventes"] * 6 + ["Tech"] * 7 + ["Juridique"] * 2,
        "salary": [32000, 34000, 36000, 38000, 40000, 42000,
                   45000, 47000, 50000, 52000, 55000, 58000, 60000,
                   48000, 70000],
    })
    
    
    def safe_aggregate(df, by, value, min_n=5):
        ...
    
    
    # report = ...
    # hidden = ...
  3. TâcheDifficulté 22 indices

    Préparer un extrait diffusable dans un tableur

    Saisissez à partir de A1 ce fichier fictif :

    Matricule;Nom;Service;Age;Salaire
    1001;Roux;Ventes;26;30000
    1002;Morel;Ventes;34;32000
    1003;Perrin;Ventes;41;34000
    1004;Lemaire;Ventes;52;36000
    1005;Garnier;Ventes;29;38000
    1006;Blanc;Juridique;38;45000
    1007;Faure;Juridique;47;50000
    1008;Robin;Juridique;31;55000
    1. Supprimez les colonnes Matricule et Nom.
    2. Ajoutez une colonne Tranche avec la formule =SI(B2<30;"<30";SI(B2<45;"30-44";"45+")) (SI (IF)), recopiez-la, puis supprimez la colonne Age après l’avoir convertie en valeurs (collage spécial, valeurs).
    3. Créez un tableau croisé dynamique par Service avec le nombre de lignes et la moyenne de Salaire.
    4. Appliquez la règle de seuil de 5 personnes : dans le tableau de synthèse à diffuser, remplacez la moyenne des services de moins de 5 personnes par « secret ».
    • L'extrait ne contient plus ni nom, ni matricule, ni âge exact (colonnes Service, Salaire, Tranche)
    • Les tranches comptent 2 personnes de moins de 30 ans, 4 de 30 à 44 ans et 2 de 45 ans et plus
    • Le tableau de synthèse affiche Ventes : 5 personnes, moyenne 34 000
    • La moyenne du service Juridique (3 personnes) est remplacée par « secret »

Quiz de fin de leçon

  1. Un fichier où le nom a été remplacé par un code, avec une table de correspondance conservée par les RH, est :

    • Anonymisé
    • Pseudonymisé : les données restent personnelles
    • Hors du champ du RGPD
  2. Pourquoi utiliser HMAC plutôt qu’un simple sha256 du matricule ?

    • C'est plus court
    • Sans clé secrète, on retrouve un matricule en hachant tous les numéros possibles
    • sha256 n'existe pas en Python
  3. Pourquoi masquer le salaire moyen d’un service de deux personnes ?

    • Parce que la moyenne est fausse
    • Parce qu'elle permet de déduire un salaire individuel
    • Parce que pandas ne sait pas la calculer

La suite du cours

  1. Effectifs et turnover18 min
  2. Absentéisme : taux, fréquence et durée15 min
  3. Recrutement : délais, tunnel de candidatures et sources15 min
  4. Égalité salariale : écart brut, écart à poste comparable et limites20 min

Analytics RH · Intermédiaire · 1 h 23