Pour divers clients, nous avons été à la recherche d’une solution permettant de nettoyer (standardiser) des adresses postales, principalement en Belgique. Nous avions besoin d’une solution « on-premise », idéalement en Open-Source. Nous avons voulu construire une solution basée sur OpenStreetMap, qui permettait de rencontrer ces deux contraintes. Mais il s’est vite avéré qu’OpenStreetMap n’était pas très robuste aux erreurs et aux données manquantes. Nous avons dès lors construit une solution combinant OpenStreetMap et d’autres outils. Cette solution nous a permis de faire passer le « matching rate » (proportion d’adresses trouvées dans OpenStreetMap) de 93 % (OpenStreetMap seule) à 98.8 % (notre combinaison) pour les adresses de la Banque Carrefour des Entreprises (Belgique).
Dans ce premier article, nous décrirons les difficultés que nous avons rencontrées. Suivra un second article détaillant la solution que nous avons élaborées.
Contexte
Lorsque l’on traite des données administratives ou commerciales, les adresses (de clients, de citoyens, d’entreprises…) sont probablement une des données pour lesquelles s’assurer une bonne qualité peut sembler facile, mais ne l’est dans la pratique pas du tout. Il est très difficile de s’assurer de deux choses :
- Que l’adresse soit standardisée, qualité essentielle pour pouvoir soit détecter des doublons (Matching), soit relier des entités entre deux bases de données (Entity Linking). On veut donc éviter à tout prix une base de données qui ressemblerait à ceci :
Rue |
Numéro |
Code Postal |
Ville |
Avenue Fonsny |
20 |
1160 |
Saint-Gilles |
Av. Fonsny |
20 bt 2 |
B-1160 |
Bruxelles |
AV FONSNY, 20 |
|
1160 |
BXL |
- Que l’adresse existe, autant la ville que la rue (dans ladite ville) et le numéro (dans la rue).
Ni la standardisation ni la vérification d’existence ne peut se faire sans une base de connaissance complexe et mise à jour, associée aux outils adéquats permettant de l’interroger intelligemment. Ceci peut principalement se faire par deux types d’outil :
- Soit des outils spécialisé en qualité de données : Trillium (SyncSort), RedPoint, ou le module “Data Quality” de la plupart des gros vendeurs de solution de gestion de données (Informatica, IBM, SAS, SAP, Talend, Oracle…)
- Soit des API publiques, souvent associées à un outils de visualisation cartographique (déjà présentées dans un blog précédent): google.com, www.here.com (Nokia), maps.bing.com (Microsoft), www.openstreetmap.org
Nous avons plusieurs fois été confrontés à des clients qui, d’une part, ne devaient – ou ne voulaient – que nettoyer des adresses, et pour qui investir dans une suite logicielle complexe, chère et difficile à prendre en main n’était pas une option. D’autre part, pour qui la législation ou la confidentialité des données ne leur permettaient pas d’envoyer des centaines de milliers, voire de millions d’adresses, à un gros acteur américain.
Il nous a donc fallu penser à une autre solution, et nous nous sommes tournés vers l’outil de cartographie Open Source et communautaire, OpenStreetMap, qui permettait d’installer une copie de leur géocodeur, intégrale ou localisée. Nous avons vite été confrontés à de nombreux obstacles, que nous allons partager ci-dessous, avec, dans le prochain article, la façon dont nous sommes parvenus à les contourner.
OpenStreetMap et Nominatim
OpenStreetMap (ou OSM) est un projet de cartographie libre, que l’on peut voir comme l’équivalent Open Source et collaboratif de Google Maps. Un des composant d’OSM est son géocodeur, qui permet, à partir d’une adresse ou du nom d’un lieu, d’obtenir un certain nombre d’informations, dont des coordonnées géographiques et un version standardisée de l’adresse.
L’outil de géocodage d’OpenStreetMap s’appelle Nominatim. Il peut soit s’utiliser via une page web (https://nominatim.openstreetmap.org/), soit via une API, en invoquant simplement une URL.
Nous pouvons par exemple obtenir des informations à propos du « 16, Rue de la Loi, Bruxelles », en appelant l’URL suivante :
On obtiendra :
[{
[...] "lat":"50.8461624",
"lon":"4.3665285",
"display_name": "16, Rue de la Loi, Quartier Royal, Brussels,
Ville de Bruxelles, Brussels-Capital, 1000, Belgium",
"place_rank":30,
"address":{
"house_number":"16",
"road":"Rue de la Loi",
"suburb":"Quartier Royal",
"state":"Brussels-Capital",
"postcode":"1000",
"country":"Belgium",
[...] }
[...]}]
Par la suite, nous utiliserons indistinctement les termes OpenStreetMap, OSM ou Nominatim.
Contraintes
Nous allons supposer pour la suite un certain nombre de contraintes, correspondant à notre expérience auprès de plusieurs clients, détaillées dans les sections suivantes.
Liste structurée
Nous avons en entrée une liste d’adresses structurée, à savoir avec un champ pour la rue, un autre pour le numéro, puis deux champs pour le code postal et la ville. Il va de soi qu’il arrive cependant fréquemment que la numéro du bâtiment soit renseigné dans la champ “rue”.
Cette découpe “rue-numéro-code postal-ville” correspond à la plupart des adresses postales dans les pays d’Europe occidentale, mais n’est pas universelle : elle ne permet pas de désigner un point telle qu’une borne kilométrique sur une autoroute, et n’est pas le standard dans de nombreux pays d’Asie ou d’Amérique.
Ça n’empêchera cependant pas certains commerçants de faire preuve de créativité pour désigner leur magasin, en indiquant dans le champ rue : “Centre commercial XXX, 2ème étage, aile droite”. Des valeurs de ce genre, difficile à géocoder automatiquement, pourront être laissées telles quelles.
Vérification du résultat
Nous choisissons de ne pas faire une confiance aveugle au géocodeur : si une adresse semble trop différente de l’adresse en input (et nous reviendrons plus pas sur la difficulté de ce test), nous la refusons.
Nous avons constaté que là où d’autres géocodeurs, comme Google Maps, donnent presque toujours une réponse, quitte à être éloignée de la réponse attendue, OpenStreetMap joue plutôt la carte de la prudence, et préférera ne pas renvoyer de réponse qu’une réponse fausse. Mais il nous est cependant arrivé de tomber sur des réponses incorrectes. Par exemple, pour “Chée de Bruxelles, 1780 Wemmel”, OpenStreetMap renverra les résultats ci-dessous, sur la requête suivante (en tout cas au moment d’écrire ces lignes) :
[
{ (...)
"lat":"50.8096144","lon":"4.4437879",
"address":
{"house_number":"2057",
"road":"Chaussée de Wavre",
"town":"Auderghem",
"postcode":"1160" (...)}
},
{(...)
"lat":"50.8571889","lon":"4.3324168",
"address":
{"road":"Chaussée de Gand",
"town":"Molenbeek-Saint-Jean",
"county":"Brussels-Capital",
"postcode":"1080" (...)}
]
Unicité du résultat
Alors que la plupart des géocodeurs, pour une adresse en entrée, proposent plusieurs résultats en sortie, nous ne pouvons dans notre contexte pas laisser à un être humain le choix du meilleur candidat et devrons le sélectionner automatiquement.
Environnement multilingue
Nous travaillons en Belgique dans un environnement multilingue, en particulier dans certaines régions de Belgique : “Avenue Fonsny, Bruxelles” (en français) est donc équivalent à “Fonsnylaan, Brussel” (en néerlandais).
Solution “on-premise”
Comme précisé plus haut, il nous fallait une solution tournant entièrement en interne, sans connexion à Internet, y compris au moment de la mise en place de la solution.
Limitation d’OpenStreetMap
Dans un premier temps, nous avons installé une instance Docker d’OpenStreetMap (https://hub.docker.com/r/mediagis/nominatim/) en y installant toutes les données de la Belgique (disponibles sur http://download.geofabrik.de), et y avons simplement, au travers d’un script en Python, envoyé toutes les adresses (entre 200.000 et 3.000.000 selon le dataset). Mais nous avons vite déchanté : le match rate (la proportion d’adresses pour lesquelles OSM proposait une adresse) se situait, en fonction du dataset, entre 65 et 93 %. Nous avons cependant rapidement eu le sentiment, en effectuant des légères modifications sur les adresses entrées, que ce taux pouvait facilement être augmenté. Voici quelques-unes des difficultés que avons rencontrées.
Manque de souplesse
Au niveau des numéros
OpenStreetMap, en général, connait les numéros des maisons, mais pas les informations de boite, appartement, étage… information que l’on retrouve très souvent dans le champ “numéro” des bases de données sur lesquelles nous avons travaillé.
On obtient donc bien un résultat pour “Avenue Fonsny 20, 1160 Bruxelles”, mais on ne reçoit aucun résultat pour “Avenue Fonsny 20 bte 2, 1160 Bruxelles”. Si nous écrivons, par contre, “Avenue Fonsny 20/2, 1160 Bruxelles”, OSM nous propose un résultat au niveau de la rue, en ignorant le numéro.
Au niveau des noms
Il s’agit d’être précis dans les noms que l’on fournit. OSM ne fournit par exemple aucun résultat pour les adresses suivantes :
- Avenue Fonsny 20, 1060 Bruxelle
- Avenue Fonsni 20, 1060 Bruxelles
- Avenu Fonsny 20, 1060 Bruxelles
Manque de consistance du résultat
L’inconvénient principal du modèle d’OpenStreetMap, où chaque contributeur peut ajouter des données à sa guise, est qu’il est difficile d’imposer une cohérence dans la façon d’introduire les données.
Le résultat d’une requête à Nominatim est un dictionnaire au format “json”, qui contient une entrée “address” dans laquelle on trouve les différentes parties de l’adresse. En analysant un grand nombre de ces résultats, nous avons trouvé le concept de “rue” dans pas moins de 7 champs: “road”, “pedestrian”,”footway”, “cycleway”, “path”, “address27”, “construction” (ainsi que, de façon plus marginale, dans “hamlet” ou “park”). Idem au niveau de la localité : “town”, “village”, “city_district”, “county”, “city”.
Si la distinction entre ces différents types de rue ou de localité a bien du sens, elle nous posait un problème : au final, nous devions produire un résultat avec une colonne “Rue” et une colonne “Localité”.
Manque de précision
Dans le résultat d’OSM, on trouve un champ “rank” indiquant le degré de précision : 30 si le bâtiment précis a été identifié ; 26 si seulement la rue a pu être localisée (plus de détails ici).
En général, nous avons constaté que seuls 50% des résultats avaient un rank à 30, les autres seulement 26. Donc seulement 50% des adresses ont pu être localisées au niveau du bâtiment, les autres l’ayant été uniquement au niveau de la rue, alors que plus de 99% des adresses en entrée contenaient un numéro.
Par ailleurs, nous avons constaté que quand le numéro de bâtiment n’est pas connu d’OSM, nous avons soit pas du tout de résultat, soit un résultat sans numéro (rank à 26). Et dans de très nombreux cas sans résultat, il a suffi de renvoyer à Nominatim l’adresse sans numéro pour obtenir un résultat (de rank 26 en général). Nous ne sommes pas parvenu à identifier de critère qui justifiait la différence de traitement.
Outils de nettoyage
Face à ces limitations, nous avons identifié deux outils qui nous permettait de préparer les données avant de les envoyer à Nominatim : Libpostal et Photon.
Libpostal
La librairie libpostal est un utilitaire permettant de parser un adresse, c’est-à-dire identifier ses différentes composantes. Si on lui soumet par exemple “Smals, Avenue Fonsny 20 bte 5, 1060 Saint-Gilles”, on obtiendra comme résultat :
[('smals', 'house'),
('avenue fonsny', 'road'),
('20', 'house_number'),
('bte 5', 'unit'),
('1060', 'postcode'),
('saint-gilles', 'city')]
Il devient alors facile d’ignorer les éléments qui ne font pas réellement partie de l’adresse (‘smals’, ‘house’) et ce qui décrit la boite ou l’étage (‘bte 5’, ‘unit’), de façon à ne pas les envoyer à OSM.
Photon
Photon est utilitaire qui combine OpenStreetMap et ElasticSearch. Il permet de donner beaucoup de flexibilité, notamment en étant nettement moins sensible aux fautes de frappe. Il s’avère très utile dans un environnement interactif, où l’utilisateur peut choisir parmi une liste proposée par Photon, mais nous avons trouvé que dans un environnement “batch” (sans interaction avec un être humain), il n’était pas vraiment une alternative viable à OpenStreetMap, essentiellement parce qu’il fournit souvent énormément de résultats, parfois très éloignés de ce qui était attendu.
Par contre, nous avons trouvé qu’il s’agissait d’un excellent partenaire d’OpenStreetMap, permettant de préparer les données. Nous avons envoyé à Photon toutes les adresses qu’OpenStreetMap ne reconnaissait pas. Cela nous a donné un grand nombre de résultats, mais en utilisant la technique décrite ci-dessous, nous n’en avons gardé que ceux étant “raisonnablement proches” des données en entrée. Les adresses qui ont ainsi été légèrement modifiées sont renvoyées à OpenStreetMap.
Vérification des résultats d’OSM
Comme déjà évoqué, nous voulions limiter les risques de recevoir pour une adresse un résultat ne correspondant pas du tout à nos attentes. Sachant que nos données en entrée sont relativement structurées, il semblait relativement aisé de comparer, par exemple, la rue en entrée avec la rue du résultat, avec un algorithme de distance entre deux chaines de texte, comme (Damerau-)Levenshtein ou Jaro(-Winkler).
Pour la comparaison de rue, nous avons cependant fait face à un certain nombre d’obstacles. En particulier, il est fréquent le nom de la rue du résultat soit (légitimement) très éloigné du nom fourni à OSM. Voici quelques exemples :
Input |
Output (result[“address”][“road”]) |
Korenbloemenstraat |
|
Rue J-B Stouffs |
|
Place de la Vieille Taille |
|
R20 |
Le premier cas s’explique par le fait que la langue dans laquelle l’adresse a été encodée (le français dans l’exemple) ne correspond pas à la langue officielle de la ville en question (le néerlandais).
Dans le deuxième cas, il arrive en particulier quand la rue porte le nom d’une personne, que le prénom soit complet, abrégé ou absent, dans l’adresse en entrée comme dans le résultat.
Le troisième cas est une conséquence de l’évolution des noms des rues et autre place. La place communale a en l’occurrence été rebaptisée, et OSM connait tant le nouveau nom que l’ancien, mais renverra toujours le nom actuel dans le résultat.
Le dernier cas concerne surtout les grands axes, qui peuvent à la fois avoir un nom local (un nom de rue) et un nom plus large, quand elles font partie d’un axe plus important, comme ici une nationale.
Tous ces exemples illustrent le fait qu’OSM connait dans de nombreux cas plusieurs dénominations pour une seule rue. Ces différentes dénominations sont utilisées pour reconnaître un nom, mais la dénomination du résultat ne correspond pas nécessairement à celle qui a permis de le trouver.
Heureusement, un simple ajout du paramètre “namedetails=1” à la requête vers Nominatim permettra d’obtenir toutes les dénominations connues pour l’axe en question. Pour le premier exemple, on aura dans la requête suivante :
un bloc supplémentaires (“namedetails”) contenant les valeurs suivantes :
{"name":"Korenbloemenstraat",
"name:fr":"Rue des Bluets",
"name:nl":"Korenbloemenstraat"}
Conclusion
À travers cet exercice, nous avons pu prendre mesure de la complexité que représentait le nettoyages d’adresses postales. Un outil tel qu’OpenStreetMap, optionnellement installé localement, permettra à moindre coût de corriger une grande partie des données. Mais il se peut, en fonction de la qualité des données à traiter, que le résultat (principalement en termes de proportion d’adresses correctement nettoyées) ne soit pas à la hauteur des attentes. Heureusement, nous verrons dans notre prochain article qu’avec un peu de travail et quelques outils adaptés, il est possible de notablement améliorer les performances du nettoyage.
____________
Ce post est une contribution individuelle de Vandy Berten, Data Science Expert chez Smals Research. Cet article est écrit en son nom propre et n’impacte en rien le point de vue de Smals.
Merci pour cet article instructif ! Nous rencontrons les mêmes difficultés que vous et allons l’article “partie 2” avec encore plus d’intérêt.
Interessant. Ik ben benieuwd naar deel 2!