Problématique
En démographie historique, on est souvent amenés à confronter des chaines de caractères pour relier des enregistrements nominatifs, par exemple chaîner des actes BMS (Baptême-Mariage-Sépulture) pour essayer de retrouver le parcours d’un individu. Dans les actes du 18ème siècle, l’identification des individus, lieux ou professions est souvent rédigée avec une forte hétérogénéité. Il en résulte une jointure par recherche floue, avec des chaînes de caractères identifiantes qui correspondent à une problématique de recherche approximative.
Pour ce faire, on va calculer une distance entre 2 chaînes de caractères, qui pourra être un nombre absolu représentant un degré de ressemblance des deux chaînes comparées, ou un nombre relatif, score variant de 0 à 1 par exemple, distance définie pour mesurer cette proximité.
Un des algorithmes les plus utilisés est l’algorithme de Lehvenshtein, qui calcule entre deux chaines de caractères la plus courte distance, en tenant compte des opérations suivantes:
- Insertion de motifs.
- Suppression de motifs (équivalent à la règle précédente).
- Substitution de motifs.
Packages | Fonctions |
---|---|
stringdist |
stringdist - amatch
|
tidyverse | inner_join |
fuzzyjoin | stringdist_inner_join |
Les packages utilisés seront les suivants:
Exemple et mise en oeuvre
Par exemple entre les chaînes “FROMONT” et “FROMON”, on aura un score de 2 : - substitution de “O” et “E” en position 2 - insertion de “T en dernière lettre de la première chaine
stringdist("FROMONT", "FREMON", method = "lv")
[1] 2
La comparaison peut se faire sur 2 ensembles de vecteurs, la comparaison sera effectuée terme à terme
stringdist(c("BLIER","FROMONT"),c("BELIER","FREMON"), method = "lv")
[1] 1 2
Exemples de distances
Quelques exemples de distances disponibles :
- Hamming distance;
- Levenshtein distance (weighted);
- Restricted Damerau-Levenshtein distance (weighted, a.k.a. Optimal String Alignment);
- Full Damerau-Levenshtein distance (weighted);
- Longest Common Substring distance;
- Q-gram distance
- cosine distance for q-gram count vectors (= 1-cosine similarity)
- Jaccard distance for q-gram count vectors (= 1-Jaccard similarity)
- Jaro, and Jaro-Winkler distance
- Soundex-based string distance.
Plus proche distance d’un élément à un vecteur
- La fonction amatch renvoie le premier élément du vecteur satisfaisant la condition de distance demandée. En cas de plusieurs éléments correspondants, seul le premier est retourné
Utilisation dans le cas d’appariements de fichiers
Nous disposons de deux fichiers d’analyse, un fichier de dénombrement recensant toutes les identités (nom-prenom) des individus présents dans un village en 1702 (nommé REC.Vars), et un fichier des baptêmes comptabilisés en 1703, contenant le nom et le prénom du père du nouveau né. On aimerait identifier les parents qui ont été recensés dans le village en 1702.
La fonction stringdist_join
, du package fuzzyjoin, permet de faire une jointure avec une incertitude sur la clé de jonction des fichiers.
Ci-dessous le début du fichier des Baptêmes
# Affichage début de fichier
kable(head(BAP.Vars,n=10)) %>%
kable_styling(bootstrap_options = c("striped"),full_width = T,font_size = 11) %>%
scroll_box(height = "300px")
PERE.Nom | PERE.Prenom | Nobs.BAP |
---|---|---|
ANTOINE | JEAN | 10 |
ALENE | JEAN | 12 |
AUBER | CHALLE | 16 |
ABEILLE | BARTHELLEMY | 27 |
ANTOINE | THOMAS | 29 |
AUDIFRETTE | PIERRE | 36 |
AUDON | JEAN | 40 |
ARNAUD | FRANCOIS | 42 |
ARNAUD | CLAUDE | 48 |
ANGLESY | ANDRE | 67 |
Ci-dessous le début du fichier de recensement
# Affichage début de fichier
kable(head(REC.Vars,n=10)) %>%
kable_styling(bootstrap_options = c("striped"),full_width = T,font_size = 11) %>%
scroll_box(height = "300px")
Ego.nom | Ego.prenom | Nobs.REC |
---|---|---|
ANDRE | . | 4 |
AMARE | FRANCOISE | 15 |
AMARE | CATHERINE | 17 |
ALEMANDE | CATHERINE | 50 |
ARNAUD | FRANCOIS | 68 |
ARNAUD | JOSEPH | 70 |
ARNAUD | URSULLE | 71 |
ARNAUD | MARGUERITE | 72 |
ARNAUD | JEANNE | 73 |
ANTOINE | ANNE | 85 |
Par une jointure classique, on veut sélectionner les individus portant le même nom et le même prénom dans les deux fichiers. On fait donc appel à une syntaxe inner_join
.
Z1 = BAP.Vars %>%
inner_join(REC.Vars,by=c("PERE.Nom"="Ego.nom","PERE.Prenom"="Ego.prenom"))
# Affichage début de fichier
kable(head(Z1,n=10)) %>%
kable_styling(bootstrap_options = c("striped"),full_width = T,font_size = 11) %>%
scroll_box(height = "300px")
PERE.Nom | PERE.Prenom | Nobs.BAP | Nobs.REC |
---|---|---|---|
ANTOINE | JEAN | 10 | 2953 |
AUDON | JEAN | 40 | 148 |
ARNAUD | FRANCOIS | 42 | 68 |
ARNAUD | CLAUDE | 48 | 1870 |
AUBERT | JEAN | 220 | 1227 |
AUDIBERT | LEON | 304 | 1157 |
AUDIBERT | JEAN | 333 | 3183 |
ABEILLE | JOSEPH | 1115 | 625 |
AUDIBERT | JACQUES | 1437 | 1156 |
ARDISSON | JEAN | 1441 | 4661 |
On souhaite maintenant unifier les individus portant le même prénom et différent d’une lettre sur le nom de famille, pour prendre en compte les variantes orthographiques minimes.
Z2 = BAP.Vars %>%
# max_dist = Distance maximale de égale à un entre les deux patronymes
# distance_col = nom de la variable créée contenant la valeur de distance
stringdist_inner_join(REC.Vars,by=c("PERE.Nom"="Ego.nom"),max_dist=1,distance_col="V_Dist") %>%
# On ne garde que les distances égales à 1
filter(V_Dist==1) %>%
# On ne conserve que les individus de même prénom
filter(stringdist(PERE.Prenom,Ego.prenom)==0) %>%
# Suppression variable de distance
select(-V_Dist)
# Affichage début de fichier
kable(head(Z2,n=30)) %>%
kable_styling(bootstrap_options = c("striped"),full_width = T,font_size = 11) %>%
scroll_box(height = "300px")
PERE.Nom | PERE.Prenom | Nobs.BAP | Ego.nom | Ego.prenom | Nobs.REC |
---|---|---|---|---|---|
AUBERTE | JOSEPH | 78 | AUBERT | JOSEPH | 5052 |
AMIEL | PONCET | 234 | AMIELH | PONCET | 328 |
ANTHOINE | JEAN | 518 | ANTOINE | JEAN | 2953 |
AUDIBERTE | PIERRE | 587 | AUDIBERT | PIERRE | 2677 |
ARNAUDE | CLAUDE | 673 | ARNAUD | CLAUDE | 1870 |
AUDIBERTE | LEON | 765 | AUDIBERT | LEON | 1157 |
AUBERT | PIERRE | 970 | AUBRET | PIERRE | 2998 |
AUTRAN | JEAN | 1026 | AUBRAN | JEAN | 5192 |
ACHARDE | CLAUDE | 1502 | ACHARD | CLAUDE | 2060 |
ANTHOINE | PIERRE | 2027 | ANTOINE | PIERRE | 2952 |
ACHARDE | JEAN | 2363 | ACHARD | JEAN | 2057 |
ARNAUDE | PIERRE | 2427 | ARNAUD | PIERRE | 1757 |
ALIS | JOSEPH | 2537 | ALIX | JOSEPH | 1499 |
AYNAUD | JOSEPH | 3176 | ARNAUD | JOSEPH | 70 |
AUBINE | JACQUES | 3703 | AUBIN | JACQUES | 2181 |
AUTRANE | ANTOINE | 3801 | AUTRAN | ANTOINE | 5040 |
AUDIBERTE | JEAN | 3844 | AUDIBERT | JEAN | 3183 |
ANTHEMAN | PAUL | 3872 | AUTHEMAN | PAUL | 5353 |
ALLEMAND | HONNORE | 4017 | ALEMAND | HONNORE | 2853 |
ARNAUDE | SEBASTIEN | 4075 | ARNAUD | SEBASTIEN | 3937 |
AUBERT | JEAN PIERRE | 4095 | AUBRET | JEAN PIERRE | 4843 |
AUDIBERTE | ANTOINE | 4112 | AUDIBERT | ANTOINE | 5017 |
ARMIOU | ANTOINE | 4130 | ARMIEU | ANTOINE | 4320 |
AUTIERE | ANDRE | 4195 | AUGIERE | ANDRE | 4856 |
AUTIERE | ANDRE | 4195 | AUZIERE | ANDRE | 7942 |
ARNAUDE | ESTIENNE | 4197 | ARNAUD | ESTIENNE | 5236 |
ACHART | JEAN | 4204 | ACHARD | JEAN | 2057 |
AUDIERE | JEAN | 4337 | AUDIER | JEAN | 5163 |
ACHARDE | LOUIS | 4342 | ACHARD | LOUIS | 3386 |
AUBRANT | JEAN | 4481 | AUBRAN | JEAN | 5192 |