Appliquer le Target Record Swapping

Objectif:

La fiche pratique présente comment appliquer le Target Record Swapping à un jeu de données individuelles afin d’assurer une protection optimale des informations confidentielles des individus (ou ménages ou entreprises) qui le composent.

La première partie s’attache à présenter les différentes étapes à suivre pour que cette application soit pertinente au regard des données utilisées.

La seconde partie montre comment utiliser la fonction sdcMicro::recordSwap() pour appliquer la méthode à un jeu de données dont on aura repérer les individus à risque préalablement.

Pour s’exercer

Pour reproduire les résultats et réaliser les exercices, il est possible de travailler directement sur le datalab de l’Insee. Si vous disposez d’un compte, vous pouvez Ouvrir un service RStudio ou Ouvrir un service vscode R-Python, à votre convenance. Ces services installeront automatiquement les packages nécessaires pour réaliser les exercices de l’ensemble des fiches pratiques.

Rappels sur la méthode:

Le Target Record Swapping (TRS) est une méthode s’appliquant directement sur un jeu de données individuelles pour réduire principalement les risques de ré-identification. Il ciblera donc les individus pour lesquels ce risque est jugé trop élevé, c’est-à-dire
ceux partageant des caractéristiques trop rares sur un ensemble de variables dites quasi-identifiantes. Les variables quasi-identifiantes sont celles qui sont susceptibles d’être déjà publiques ou du moins à la disposition d’un attaquant.

D’où son nom: le swapping, c’est-à-dire l’échange, va concerner des unités de la base (les records) détectés à l’avance, devenant ainsi les cibles (target) sur lesquelles on va se concentrer.

Bien qu’elle soit appliquée sur des microdonnées, la méthode a principalement vocation à protéger la diffusion de données tabulées (Hundepool et al. 2024, section 5.6). Dans ce type d’utilisation, en tant que méthode pré-tabulée, elle permet de produire des tableaux parfaitement additifs.

Hundepool, Anco, Josep Domingo-Ferre, Luisa Franconi, Sarah Giessing, Rainer Lenz, Jane Longhurst, Eric Schulte Nordholt, et al. 2024. Handbook on Statistical Disclosure Control. 2nd Edition. ESSNet SDC. https://sdctools.github.io/HandbookSDC/.

En quoi consiste la méthode ?

Après avoir détecté un individu dont le risque de ré-identification est jugé trop élevé, un autre individu est tiré aléatoirement parmi les individus au risque de ré-identification les plus faibles et certaines des caractéristiques de ces deux individus sont échangées (swapped).

Après l’échange, la ré-identification de l’individu est rendue beaucoup plus difficile et ne permet plus d’obtenir d’informations pertinentes à son sujet.

Un exemple très simple.

Soit un territoire donné découpé en six zones où résident un certain nombre d’individus. Avec les informations disponibles à leur sujet, on considère qu’un seul individu a un risque de ré-identification trop élevé (croix rouge sur la Figure 1).

Avant swapping
Figure 1

On tire alors au hasard un autre individu présent sur le territoire pour échanger certaines de leurs caractéristiques. Ce tirage pourrait être purement aléatoire. Mais, cela conduirait à détruire certaines statistiques bien utiles. Par exemple, en trouvant un individu d’une autre zone partageant certaines caractéristiques (par exemple le sexe, l’âge et la catégorie sociale, Figure 2), on s’assure de conserver les marges sur ces variables au niveau géographique le plus fin.

En revanche, les autres caractéristiques sont bruitées de façon non contrôlée.

Après swapping
Figure 2

Ainsi, l’éventuelle ré-identification de l’individu à risque ne pourrait conduire l’attaquant qu’à obtenir des informations très probablement fausses à son sujet.

Les variables de similarité

La méthode est très souple puisqu’elle permet de s’adapter aux données dont on dispose et aux besoins en termes de diffusion. Les variables de similarité entre individus échangés peuvent ainsi être choisies parmi les variables dont on veut conserver le mieux possible l’information marginale. En effet, en échangeant des individus parfaitement similaires sur certaines caractéristiques (les variables de similarité), l’échange ne produira aucune perte d’information sur celles-ci.

Similarité et ré-identification

Si l’objectif est de réduire le risque de ré-identification, sélectionner toutes les variables quasi-identifiantes comme variables de similarité ne perturberait aucunement la capacité de l’attaquant à ré-identifier les individus. Ce choix produirait en revanche du bruit sur les autres variables: donc en ré-identifiant, l’attaquant ne serait pas en mesure d’obtenir des informations supplémentaires fiables.

Utiliser la hiérarchie des emboîtements géographiques

Les données contiennent généralement des informations géographiques. Or, la géographie, en particulier à des niveaux fins, est très identifiante. On choisira en général d’échanger des individus résidant à des endroits différents, en tenant compte du niveau géographique auquel un individu est facilement ré-identifiable.

Par exemple, si un individu est facilement ré-identifiable dans sa commune mais pas dans son département de résidence, alors on privilégiera l’échange avec un autre individu du département en dehors de la commune de notre individu à risque. Si le niveau départemental posait lui aussi problème on remonterait au niveau régional, etc.

Ainsi, en général, la méthode consistera à échanger la localisation d’individus ou de ménages (Longhurst, Tromans, and Young, n.d.).

Comment définir des individus similaires ?

La similarité entre deux individus peut être envisagée de deux manières:

  • en cherchant la correspondance exacte sur les variables de similarité, quitte à relâcher des contraintes quand il n’y pas de donneur: un donneur est tiré aléatoirement parmi la liste des prétendants.
  • en calculant une distance entre le receveur et l’ensemble des donneurs: un donneur est tiré parmi les individus avec lesquels la distance est la plus faible.

La première option nécessite d’utiliser uniquement des variables catégorielles, quitte à créer des classes pour les variables continues qu’on souhaiterait inclure dans la liste.

Dans la pratique, cette première option est la moins gourmande en ressources de calcul, car elle ne nécessite pas de calculer une distance entre chaque receveur et chaque donneur potentiel. Elle est probablement la seule envisageable si le jeu de données contient plusieurs millions de lignes.

Une astuce pour gérer la présence de poids de sondage

Si nous disposons d’un échantillon, chaque individu dispose d’un poids de sondage. Avec le Target Record Swapping, on prend le risque d’échanger deux individus avec des poids très différents et de déséquilibrer des agrégats importants. Pour éviter ce genre de désagrément, il est possible d’ajouter le poids parmi les variables de similarité, sous la forme d’un arrondi à l’unité ou bien de tranches de valeurs. On pourra également exclure des donneurs potentiels, les individus ayant des poids très élevés comparés aux autres. Ceci permettra de limiter les déformations des distributions.

Individus ou ménages ?

Une autre souplesse de la méthode est de pouvoir prendre en compte des structures hiérarchiques entre les individus. Par exemple, lorsque le fichier d’individus contient également des informations sur la composition des ménages.

Dans ce cas, l’échange d’individus risque de déstabiliser la composition des ménages concernés, en termes d’âge en particulier: on pourrait voir se créer des ménages où il n’y a aucun individu majeur par exemple.

Une solution consisterait à échanger des ménages entiers plutôt que des individus. Mais cette solution n’est à retenir que si on dispose de suffisamment d’informations sur les ménages pour détecter des ménages similaires de bonne qualité. Si ce n’est pas le cas, il peut être préférable d’échanger des individus et de contrôler d’une autre manière, car un choix judicieux de variables de similarité peut limiter ces désagréments.

Le taux de swapping

Le taux d’échange ou taux de swapping est la part d’individus du jeu de données qui a pris part aux échanges, soit \(s=\frac{E+D}{N}\), où \(E\) est le nombre d’individus à échanger, \(D\) le nombre de donneurs (\(D=E\)) et \(N\) le nombre d’individus dans la base.

En pratique, on fixe un taux de swapping \(s\) de telle sorte que l’algorithme procède à un minimum d’échanges. Si le nombre d’individus à risque ne permet pas d’atteindre ce seuil, des individus supplémentaires seront également échangés pour atteindre \(s\). Sinon, le taux de swapping étant atteint ou dépassé, aucun individu additionnel n’est ajouté.

Les grandes étapes à suivre pour appliquer le TRS

  1. De quels risques cherche-t-on à se protéger ?
    1. Si c’est du risque de ré-identification, le TRS est une méthode adaptée
    2. Sinon, il faut peut-être aller voir ailleurs.
  2. Quelles sont les variables quasi-identifiantes dans mes données ?
  3. Mesurer le risque de ré-identification basé sur ces variables ?
  4. Choisir un seuil de risque de ré-identification maximal acceptable, au-delà duquel les individus devront être traités.
  5. Quelles sont les marges que la méthode devrait conserver intactes en priorité ?
  6. Un swapping des ménages est-il préférable à un swapping d’individus ?
  7. Sélection des paramètres de la méthode:
    1. Les variables de similarités, en tenant compte des risques et des objectifs de diffusion.
    2. Le taux de swapping.
  8. Procéder au swapping
  9. Mesurer la perte d’information
  10. Reprendre les étapes 4. à 9. si la perte d’information s’avère trop grande ou la protection trop faible

L’avantage de la méthode est sa grande capacité d’adaptation aux données dont on dispose. Même si cette fiche va présenter comment l’appliquer avec le package sdcMicro, il est en réalité tout à fait possible de développer son propre algorithme pour affiner la façon de tirer les individus ou les ménages à échanger.

On pourra se reporter à la fiche sur les mesures de risque pour en savoir plus sur les risques et la façon de les mesurer.

En pratique

Pour installer les packages nécessaires, vous trouverez les instructions à suivre dans la fiche Ressources / Installer les packages et les outils sur R.

Les packages


Attaching package: 'dplyr'
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union

Les données

Code
source("../R/fun_import_data.R")
lfs_2023 <- import_lfs()
Code
head(lfs_2023)
      REG    DEP    ARR   SEXE   AGE   AGE6  ACTEU   DIP7  PCS1Q ANCCHOM  HHID
   <fctr> <fctr> <fctr> <fctr> <int> <fctr> <fctr> <fctr> <fctr>  <fctr> <int>
1:     28     76    761      1    53     50      1      4     30      99  3558
2:     28     76    761      2    43     25      1      7     52      99  3558
3:     28     76    761      2    17     15      3      5     99      99  3558
4:     28     76    761      1    17     15      3      4     99      99  3558
5:     11     92    922      1    42     25      1      7     62      99  5973
6:     11     92    922      2    54     50      1      7     62      99  5973
   HH_TAILLE HH_AGE HH_DIP HH_PCS IS_CHOM
      <fctr> <fctr> <fctr> <fctr>   <int>
1:         4     53      4     30       0
2:         4     53      4     30       0
3:         4     53      4     30       0
4:         4     53      4     30       0
5:         2     54      7     62       0
6:         2     54      7     62       0

Pour plus d’informations sur les données, on pourra se reporter à la fiche “Présentation des données”.

Mesurons les risques du fichier

Les types de variables

On définit les variables selon les différentes catégories suivantes:

  • les variables quasi-identifiantes (key_vars)
  • les variables sensibles (sens_vars)
  • les variables identifiant la composition des ménages (hhid_vars)

Pour cet exercice, nous ferons les choix suivants:

Code
key_vars <- c("AGE6","SEXE","DEP","DIP7")
sens_vars <- c("ACTEU","ANCCHOM","IS_CHOM")
hhid_vars <- c("HHID")

Les comptages par croisement des modalités des variables quasi-identifiantes:

Code
lfs_sdc <- sdcMicro::createSdcObj(
  lfs_2023,
  keyVars = key_vars
)

Le tableau ci-dessous présente le nombre et la part d’individus du fichier ne répondant pas au critère du \(k\)-anonymat, en fonction de la valeur de \(k\).

Code
ks <- 2:10
data.frame(
  k = ks,
  nb_ind_risk = sapply(ks, \(k) sum(lfs_sdc@risk$individual[,"fk"] < k))
) |>
  mutate(part_ind_risk = nb_ind_risk / nrow(lfs_2023) * 100) |>
  knitr::kable(digits=1)
k nb_ind_risk part_ind_risk
2 77 0.2
3 267 0.8
4 465 1.4
5 825 2.4
6 1105 3.2
7 1387 4.1
8 1723 5.1
9 2075 6.1
10 2498 7.3

On dénombre 77 individus uniques et jusqu’à 7.3% d’individus à risque si nous nous fixons comme objectif de respecter un \(10\)-anonymat dans notre fichier.

Pour traiter les individus à risque, on se propose d’utiliser un targeted record swapping.

Préparation pour le swapping

Code
risk_variables <- c("DEP", "AGE6","SEXE","DIP7") # les variables permettant de mesurer le risque de ré-identification
hierarchy <- c("REG", "DEP") # permet de renseigner l'existence d'une hiérarchie entre les var géographiques
carry_along <- c("ARR")
similar <- list( # Contraintes de similarité sur les individus échangés
  c("REG","AGE6","SEXE"),  # priorité 1
  c("REG","SEXE"), # priorité 2 ...
  c("REG","AGE6"), 
  c("AGE6","SEXE"),
  "REG",
  "SEXE"
  )
hid <- "ID" # swapping d'individus

La fonction de swapping implémentée dans sdcMicro nécessite de transformer toutes les variables en integer.

Code
lfs_2023_pr_swap <- lfs_2023 |>
  mutate(across(everything(), as.integer))|>
  mutate(ID = 1:n())

Swapper les individus

On choisit de nous fixer comme objectif de traiter tous les individus ne respectant pas le \(5\)-anonymat et d’échanger au moins \(1\)% des individus.

Code
k_anonymity <- 5
swaprate <- .01
Code
lfs_swapped <- recordSwap(
  data = lfs_2023_pr_swap,
  hid = hid,
  hierarchy = hierarchy,
  similar = similar,
  risk_variables = risk_variables,
  carry_along = carry_along,
  k_anonymity = k_anonymity,
  swaprate = swaprate,
  return_swapped_id = TRUE,
  seed = 123456 # pour la reproductibilité
)
Recordswapping was successful!

Le taux de swapping réel est légèrement inférieur au taux de swapping attendu (c’est-à-dire \(2\times2.4=4.8\%\)):

Code
lfs_swapped %>% summarise(mean(ID != ID_swapped)) * 100
  mean(ID != ID_swapped)
1               4.510616

En réalité, un certain nombre d’individus à risque ont été échangés entre eux :

Code
ind_risk <- which(lfs_sdc@risk$individual[,"fk"] < 5)
lfs_swapped |>
  slice(ind_risk) |>
  filter(ID_swapped %in% ind_risk) |>
  nrow() /2
[1] 85
Code
lfs_swapped %>% 
  group_by(REG) %>%
  summarise(mean(ID != ID_swapped))
# A tibble: 4 × 2
    REG `mean(ID != ID_swapped)`
  <int>                    <dbl>
1     1                   0.0239
2     2                   0.0587
3     3                   0.0671
4     4                   0.295 

Un exemple d’échange

Code
premier_couple <- lfs_swapped |> slice(ind_risk[1]) |>
  bind_rows(lfs_swapped |> filter(ID_swapped == ind_risk[1]))
Code
premier_couple 
     REG   DEP   ARR  SEXE   AGE  AGE6 ACTEU  DIP7 PCS1Q ANCCHOM  HHID
   <int> <int> <int> <int> <int> <int> <int> <int> <int>   <int> <int>
1:     1    27    79     2    17     1     3     5    10      10  3558
2:     2    19    53     2    23     1     3     2    10      10  7043
   HH_TAILLE HH_AGE HH_DIP HH_PCS IS_CHOM    ID ID_swapped
       <int>  <int>  <int>  <int>   <int> <int>      <int>
1:         4     39      4      4       0     3      20669
2:         8     43      3     10       0 20669          3

On peut remarquer que ces individus sont similaires sur la région, le sexe et l’âge (AGE6).

La base swappée

Code
str(lfs_swapped)
Classes 'data.table' and 'data.frame':  34053 obs. of  18 variables:
 $ REG       : int  2 2 1 2 1 1 1 1 1 3 ...
 $ DEP       : int  19 19 27 19 25 25 26 21 21 9 ...
 $ ARR       : int  53 53 79 53 73 73 75 62 62 27 ...
 $ SEXE      : int  1 2 2 1 1 2 2 1 2 1 ...
 $ AGE       : int  53 43 17 17 42 54 57 20 17 28 ...
 $ AGE6      : int  3 2 1 1 2 3 3 1 1 2 ...
 $ ACTEU     : int  1 1 3 3 1 1 1 3 3 1 ...
 $ DIP7      : int  4 7 5 4 7 7 7 7 7 5 ...
 $ PCS1Q     : int  4 7 10 10 9 9 7 10 10 8 ...
 $ ANCCHOM   : int  10 10 10 10 10 10 10 10 10 10 ...
 $ HHID      : int  3558 3558 3558 3558 5973 5973 6779 4906 4906 1135 ...
 $ HH_TAILLE : int  4 4 4 4 2 2 1 2 2 3 ...
 $ HH_AGE    : int  39 39 39 39 40 40 43 6 6 14 ...
 $ HH_DIP    : int  4 4 4 4 7 7 7 7 7 5 ...
 $ HH_PCS    : int  4 4 4 4 9 9 7 10 10 8 ...
 $ IS_CHOM   : int  0 0 0 0 0 0 0 0 0 0 ...
 $ ID        : int  1 2 3 4 5 6 7 8 9 10 ...
 $ ID_swapped: int  1 2 20669 4 5 6 7 8 9 10 ...
 - attr(*, ".internal.selfref")=<externalptr> 

On procède à l’échange des géographies entre les individus:

Code
lfs_pert <- lfs_swapped |>
  select(-REG, -DEP, -ARR, -ID_swapped)|>
  full_join(
    lfs_swapped |> select(REG, DEP, ARR, ID=ID_swapped),
    by = "ID"
  )

Swapper les ménages

Code
hid <- "HHID" # swapping de ménages
similar <- list(c("HH_TAILLE", "HH_AGE"), c("HH_TAILLE"), c("HH_AGE"))

lfs_swapped_hh <- recordSwap(
  data = lfs_2023_pr_swap,
  hid = hid,
  hierarchy = hierarchy,
  similar = similar,
  risk_variables = risk_variables,
  carry_along = carry_along,
  k_anonymity = k_anonymity,
  swaprate = swaprate,
  return_swapped_id = TRUE,
  seed = 123456
)
Recordswapping was successful!

Taux de swapping des ménages vs taux de swapping des individus

Code
lfs_swapped_hh %>% summarise(mean(HHID != HHID_swapped)) 
  mean(HHID != HHID_swapped)
1                  0.1038381

Mesurer la perte d’utilité

Code
reg_sex_age <- lfs_swapped |> count(REG, SEXE, AGE6, name="n_p") |>
  left_join(lfs_2023_pr_swap |> count(REG, SEXE, AGE6, name = "n_o"))|>
  mutate(n_p = ifelse(is.na(n_p), 0, n_p))|>
  mutate(n_o = ifelse(is.na(n_o), 0, n_o))
Joining with `by = join_by(REG, SEXE, AGE6)`

Mesure de l’écart absolu moyen :

Code
mean(abs(reg_sex_age$n_p - reg_sex_age$n_o))
[1] 0.9473684

Visualisation des distributions:

Code
reg_sex_age |> mutate(cl = 1:n()) |>
  tidyr::pivot_longer(n_p:n_o, names_to = "type", values_to = "n") |>
  ggplot() +
  geom_bar(aes(x=cl, y=n, fill=type), stat = "identity", position = "dodge")

Code
dep_sex_age <- lfs_swapped |> count(DEP, SEXE, AGE6, name="n_p") |>
  left_join(lfs_2023_pr_swap |> count(DEP, SEXE, AGE6, name = "n_o")) |>
  mutate(n_p = ifelse(is.na(n_p), 0, n_p))|>
  mutate(n_o = ifelse(is.na(n_o), 0, n_o)) |>
  mutate(diff_abs = abs(n_p - n_o))
Joining with `by = join_by(DEP, SEXE, AGE6)`
Code
mean(dep_sex_age$diff_abs)
[1] 0.1311475
Code
summary(dep_sex_age$diff_abs)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.0000  0.0000  0.0000  0.1311  0.0000  3.0000 

On se propose de comparer les intervalles de confiance des coefficients d’un modèle logistique pour évaluer la perte d’information engendrée par le swapping.

Code
source("../R/fun_cio.R")

Étudions la stabilité d’un modèle relativement simple, cherchant à expliquer le fait d’être au chômage par différentes variables à notre disposition: l’âge, le diplôme et le sexe.

On entraînera ce modèle uniquement sur la population active. On retire donc notamment la population étudiante et retraitée.

Code
lfs_2023_pr_glm <- lfs_2023_pr_swap |> mutate(across(-IS_CHOM, as.character))
lfs_pert_pr_glm <- lfs_swapped |> mutate(across(-IS_CHOM, as.character))

m_original <- glm(IS_CHOM ~ REG + SEXE + AGE6 + DIP7, data = lfs_2023_pr_glm |> filter(ACTEU != 3))
m_swapped <- glm(IS_CHOM ~ REG + SEXE + AGE6 + DIP7, data = lfs_pert_pr_glm |> filter(ACTEU != 3))

mes_modeles <- list(
  "original" = m_original,
  "swapped" = m_swapped
)

all_cios <- get_all_confints(mes_modeles)
Joining with `by = join_by(vars)`
Code
graph_cios(all_cios, titre = "Superposition des intervalles de confiance\ndes Odds Ratio du modèle selon le jeu de données") +
  theme(axis.text.y =  element_text(size = 8))

Exercice

Comparer la perte d’information de différents taux de swapping. Vous utiliserez la mesure de perte d’information que vous souhaitez.