Afin de réaliser un de nos tutoriels (Le problème du voyageur de commerce) nous avons eu besoin de récupérer les coordonnées des communes françaises. La première source de données que nous avons trouvé sur Internet se trouve sur le site data.gouv.fr et nous n'avions aucune raison de douter de la qualité des données fournies. Ce fut une très grave erreur et une grosse perte de temps. Le tutoriel présente un algorithme que nous avons construit en javascript. Avec les difficultés rencontrées pour l'affiner il nous a fallu faire du pas à pas et rentrer dans le détail. C'est là que nous nous sommes rendu compte que les données que nous avions récupérées étaient de mauvaises qualités, 2 communes pouvant avoir les mêmes coordonnées par exemple, d'autres étaient manquantes. Ce tutoriel présente comment analyser des données afin de s'assurer qu'elles soient complètes et cohérentes. Pour cela nous allons utiliser le langage R qui est très efficace pour ce genre d'analyses bien qu'il soit possible de faire la même chose avec Excel.
Nous commençons par effectuer une analyse sur la nature des données récupérées et leur complétude. Les données que nous avions au départ proviennent du site
data.gouv.fr. Le fichier CSV peut
aussi être récupéré sur notre site eucircos_regions_departements_circonscriptions_communes_gps.zip.
Pour le charger nous utilisons le très performant package data.table
, ce n'est pas nécessaire ici vu le nombre de lignes limitées mais autant partir avec les meilleures bases.
Il nous faut juste préciser l'encodage du fichier. On affiche ensuite les premières lignes
de nos données.
> require(data.table)
> DT <- fread("c:/temp/eucircos_regions_departements_circonscriptions_communes_gps.csv", encoding = "UTF-8")
> head(DT)
EU_circo code_région nom_région chef.lieu_région numéro_département nom_département préfecture
1 Sud-Est 82 Rhône-Alpes Lyon 01 Ain Bourg-en-Bresse
2 Sud-Est 82 Rhône-Alpes Lyon 01 Ain Bourg-en-Bresse
3 Sud-Est 82 Rhône-Alpes Lyon 01 Ain Bourg-en-Bresse
4 Sud-Est 82 Rhône-Alpes Lyon 01 Ain Bourg-en-Bresse
5 Sud-Est 82 Rhône-Alpes Lyon 01 Ain Bourg-en-Bresse
6 Sud-Est 82 Rhône-Alpes Lyon 01 Ain Bourg-en-Bresse
numéro_circonscription nom_commune codes_postaux code_insee latitude longitude éloignement
1 1 Attignat 01340 1024 46.283333 5.166667 1.21
2 1 Beaupont 01270 1029 46.4 5.266667 1.91
3 1 Bény 01370 1038 46.333333 5.283333 1.51
4 1 Béreyziat 01340 1040 46.366667 5.05 1.71
5 1 Bohas-Meyriat-Rignat 01250 1245 46.133333 5.4 1.01
6 1 Bourg-en-Bresse 01000 1053 46.2 5.216667 1.00
On supprime ensuite les colonnes qui nous sont inutiles.
> DT[, c("EU_circo","code_région", "nom_région", "chef-lieu_région", "préfecture", "numéro_circonscription", "éloignement"):=NULL]
> head(DT)
numéro_département nom_département nom_commune codes_postaux code_insee latitude longitude
1: 01 Ain Attignat 01340 1024 46.283333 5.166667
2: 01 Ain Beaupont 01270 1029 46.4 5.266667
3: 01 Ain Bény 01370 1038 46.333333 5.283333
4: 01 Ain Béreyziat 01340 1040 46.366667 5.05
5: 01 Ain Bohas-Meyriat-Rignat 01250 1245 46.133333 5.4
6: 01 Ain Bourg-en-Bresse 01000 1053 46.2 5.216667
On peut commencer à voir l'intérêt du language R avec la fonction summary
> summary(DT)
numéro_département nom_département nom_commune codes_postaux code_insee latitude longitude
Length:36840 Length:36840 Length:36840 Length:36840 Min. : 1001 Length:36840 Length:36840
Class :character Class :character Class :character Class :character 1st Qu.:24577 Class :character Class :character
Mode :character Mode :character Mode :character Mode :character Median :48191 Mode :character Mode :character
Mean :46298
3rd Qu.:67043
Max. :97617
Il y a un souci sur la latitude et la longitude qui sont reconnues comme des caractères. De la même façon le code INSEE est reconnu comme un nombre alors qu'il s'agit de caractères. Nous effectuons les conversions nécessaires.
> DT <- DT[, latitude:=as.numeric(latitude)]
> DT <- DT[, longitude:=as.numeric(longitude)]
> DT <- DT[, code_insee:=as.character(code_insee)]
> summary(DT)
numéro_département nom_département nom_commune codes_postaux code_insee latitude longitude
Length:36840 Length:36840 Length:36840 Length:36840 Length:36840 Min. :41.39 Min. :-4.7667
Class :character Class :character Class :character Class :character Class :character 1st Qu.:45.22 1st Qu.: 0.6833
Mode :character Mode :character Mode :character Mode :character Mode :character Median :47.43 Median : 2.6167
Mean :47.00 Mean : 2.7324
3rd Qu.:48.85 3rd Qu.: 4.8500
Max. :51.08 Max. : 9.5167
NA's :2962 NA's :3024
C'est avec cette dernière commande summary
que tout l'intérêt de R se révèle. Elle permet de caractériser chacune des colonnes du fichier. Ainsi les 5 premières colonnes sont
renseignées pour chaque ligne. En revanche la colonne latitude contient 2962 NA's (qui signifie Not Available). De la même façon la colonne longitude possède 3024 NA's et étrangement ce
n'est pas le même nombre. Nous terminons ici cette première analyse de surface. Elle nous a déjà permise de constater que les données récupérées ne sont pas de très bonnes qualités,
certaines communes (3000 quand même, sur 36800) ne possèdent pas de coordonnées géographiques, soit presque 10%.
Nous verrons dans de futurs tutoriels sur l'analyse de données toute la puissance de R concernant l'analyse de données numériques. Les coordonnées sont bien des nombres mais ça ne ferait aucun sens d'étudier leur moyennes, distributions ou de faire des corrélations avec le nom des villes par exemple.
Nous poursuivons en nous concentrant de manière spécifique sur la latitude et la longitude, la fonction summary
nous fournit d'autres informations intéressantes : les valeurs Min.
et Max. de ces deux colonnes. Elles peuvent nous permettre de dessiner un rectangle par-dessus la France comme dans la carte ci-dessous.
Dans l'ensemble on encadre plutôt correctement la France métropolitaine (ce qui signifie qu'il n'y a pas de données pour les DOM-TOM par ailleurs). Mais un problème au niveau du Finistère se manifeste déjà. On peut être sûr que Ouessant n'est pas référencée ou ne possède pas de coordonnées.
> DT[DT$nom_commune == 'Ouessant']
numéro_département nom_département nom_commune codes_postaux code_insee latitude longitude
1: 29 Finistère Ouessant 29242 29155 NA NA
La commune est bien référencée mais sans latitude et longitude. Avec 3000 coordonnées manquantes on s'en doutait bien, mais on penchait plutôt pour de grosses villes comme Paris avec ses quartiers.
> DT[DT$nom_commune == 'Paris']
numéro_département nom_département nom_commune codes_postaux code_insee latitude longitude
1: 75 Paris Paris 75001 75002 75003 75004 75056 NA NA
2: 75 Paris Paris 75005 75006 75056 NA NA
3: 75 Paris Paris 75006 75007 75056 NA NA
4: 75 Paris Paris 75008 75009 75056 NA NA
5: 75 Paris Paris 75010 75056 NA NA
6: 75 Paris Paris 75011 75020 75056 NA NA
7: 75 Paris Paris 75011 75012 75056 NA NA
8: 75 Paris Paris 75012 75056 NA NA
9: 75 Paris Paris 75013 75056 NA NA
10: 75 Paris Paris 75013 75014 75056 NA NA
11: 75 Paris Paris 75014 75056 NA NA
12: 75 Paris Paris 75015 75056 NA NA
13: 75 Paris Paris 75015 75056 NA NA
14: 75 Paris Paris 75016 75056 NA NA
15: 75 Paris Paris 75016 75056 NA NA
16: 75 Paris Paris 75017 75056 NA NA
17: 75 Paris Paris 75017 75018 75056 NA NA
18: 75 Paris Paris 75018 75056 NA NA
19: 75 Paris Paris 75018 75019 75056 NA NA
20: 75 Paris Paris 75019 75056 NA NA
21: 75 Paris Paris 75020 75056 NA NA
C'est pas mal aussi sur Paris, 21 lignes mais 0 coordonnée. On apprend également que certaines lignes sont complètement en double comme les lignes 12 et 13 (c'est en fait la colonne numéro_circonscription supprimée au début qui les différencie). Comme l'usage que nous faisons de ces données nécessitent la présence des coordonnées, nous supprimons toutes les lignes qui possèdent au moins un NA.
> DT <- na.omit(DT)
> summary(DT)
numéro_département nom_département nom_commune codes_postaux code_insee latitude longitude
Length:33814 Length:33814 Length:33814 Length:33814 Length:33814 Min. :41.39 Min. :-4.7667
Class :character Class :character Class :character Class :character Class :character 1st Qu.:45.22 1st Qu.: 0.6833
Mode :character Mode :character Mode :character Mode :character Mode :character Median :47.45 Median : 2.6167
Mean :47.00 Mean : 2.7324
3rd Qu.:48.85 3rd Qu.: 4.8500
Max. :51.08 Max. : 9.5167
Poursuivons en vérifiant que tous les couples (latitude, longitude) sont bien distincts. A priori différentes communes doivent posséder des coordonnées différentes.
> dupe = DT[,c('latitude','longitude')]
> dupeDT <- DT[duplicated(dupe) | duplicated(dupe, fromLast=TRUE),]
> dupeDT <- dupeDT[order(dupeDT$latitude)]
> head(dupeDT, n = 20)
numéro_département nom_département nom_commune codes_postaux code_insee latitude longitude
1: 2A Corse-du-Sud Fozzano 20143 20118 41.70000 9.000000
2: 2A Corse-du-Sud Santa-Maria-Figaniella 20143 20310 41.70000 9.000000
3: 2A Corse-du-Sud Cargiaca 20164 20066 41.71667 9.050000
4: 2A Corse-du-Sud Loreto-di-Tallano 20165 20146 41.71667 9.050000
5: 2A Corse-du-Sud Serra-di-Scopamène 20127 20278 41.75000 9.100000
6: 2A Corse-du-Sud Sorbollano 20152 20285 41.75000 9.100000
7: 2A Corse-du-Sud Guargualé 20128 20132 41.83333 8.933333
8: 2A Corse-du-Sud Urbalacone 20128 20331 41.83333 8.933333
9: 2A Corse-du-Sud Cardo-Torgia 20190 20064 41.86667 8.983333
10: 2A Corse-du-Sud Santa-Maria-Siché 20190 20312 41.86667 8.983333
11: 2B Haute-Corse Santo-Pietro-di-Venaco 20250 20315 42.23333 9.166667
12: 2B Haute-Corse Venaco 20231 20341 42.23333 9.166667
13: 2B Haute-Corse Campi 20270 20053 42.26667 9.416667
14: 2B Haute-Corse Moïta 20270 20161 42.26667 9.416667
15: 2B Haute-Corse Canale-di-Verde 20230 20057 42.28333 9.466667
16: 2B Haute-Corse Chiatra 20230 20088 42.28333 9.466667
17: 2B Haute-Corse Santa-Maria-Poggio 20221 20311 42.33333 9.500000
18: 2B Haute-Corse Valle-di-Campoloro 20221 20335 42.33333 9.500000
19: 2B Haute-Corse Piedicroce 20229 20219 42.36667 9.350000
20: 2B Haute-Corse Piedipartino 20229 20221 42.36667 9.350000
Le tableau obtenu possède 2168 entrées et comme vous pouvez le voir avec les premières lignes de nombreuses communes possèdent les mêmes coordonnées. Est-ce normal ? Nous pouvons le vérifier en confrontant nos données à celles fournies par un autre service. En utilisant l'API de geocoding de Google (API Google) avec les deux premières villes. Pour Fozzano nous obtenons (41.69947, 9.001941) et pour Santa-Maria-Figaniella (41.715337, 9.001842). Les coordonnées sont certes proches mais les deux villes sont distantes d'environ 1 km. Nous pourrions nous intéresser à la définition du centre d'une commune mais ce qui est sûr c'est que peu importe la définition il n'y a aucune raison de trouver des doublons dans un jeu de données. Cela indique soit des arrondis trop fort soit des données mal construites.
Sur le site data.gouv.fr il est indiqué que les données proviennent du travail de http://www.galichon.com/codesgeo/. Il serait peut-être bon d'aller y jeter un oeil. On arrive sur une page qui propose plusieurs informations dont le référentiel des communes avec les coordonnées. On note aussi dans le bas de page un copyright 2000 - 2013 ce qui n'est pas très bon signe. Une page Avertissement attire notre attention. On peut y relever les deux blocs de texte suivant :
Les coordonnées géographiques sont exprimées dans le système géodésique international WGS84 et arrondies à la minute la plus proche. Celles disponibles sur le site de l'IGN sont exprimées dans le système ED50 et sont données avec une précision d'une seconde. La formule pour passer d'un système à l'autre n'est pas triviale mais étant donné les arrondis cités précédemment, on peut considérer que les deux systèmes ne font qu'un (l'écart entre les deux systèmes étant de l'ordre de 5 à 15 secondes). Pour plus d'information (en anglais), je vous conseille de visiter la page "Geographic and Projected Coordinate System Transformations" disponible sur http://www.petroconsultants.com/epsg/guid7.html.
Sources des données des coordonnées géographiques : GEOnet Names Server (http://www.nima.mil/gns/html/cntry_files.html) base mise à jour le 4 avril 2002
https://www.galichon.com/ - Coordonnées géographiques des villes Françaises - Avertissement
Sans rentrer dans les détails techniques on apprend que des arrondis ont été réalisés et que la base remonte à 2002 ! data.gouv.fr ne le mentionne pas directement alors qu'il s'agit pourtant d'informations importantes.
Après avoir perdu beaucoup de temps avec les données de data.gouv.fr nous avons trouvé un jeu de données fourni par La Poste et disponible sur ce site. L'accueil est déjà bien plus agréable, le site repose sur un framework PHP justement dédié au partage de données. Les différents onglets nous permettent facilement de voir les données fournies, leur couverture spatiale (et cette fois il y a les DOM-TOM) et la page d'accueil présente les principales informations. On peut relever qu'il n'est pas fait mention de la colonne coordonnees_gps. Nous pouvons refaire notre analyse sur le fichier exporté en CSV (laposte_hexasmal.zip).
> require(data.table)
> DT <- fread("c:/temp/laposte_hexasmal.csv", encoding = "UTF-8")
> head(DT)
Code_commune_INSEE Nom_commune Code_postal Libelle_acheminement Ligne_5 coordonnees_gps
1: 80355 FRESNEVILLE 80140 FRESNEVILLE 49.9469630616, 1.753960976
2: 80365 FRICAMPS 80290 FRICAMPS 49.7720118421, 1.95186211928
3: 80368 FRIVILLE ESCARBOTIN 80130 FRIVILLE ESCARBOTIN 50.0912781795, 1.52364516053
4: 80379 GLISY 80440 GLISY 49.8341850031, 2.39954269272
5: 80387 GRATTEPANCHE 80680 GRATTEPANCHE 49.8142899245, 2.29952854065
6: 80393 GRUNY 80700 GRUNY 49.7015900422, 2.77539756139
> DT[, c("latitude", "longitude") := tstrsplit(coordonnees_gps, ", ", fixed=TRUE)]
> DT[, c("Ligne_5", "coordonnees_gps"):=NULL]
> DT <- DT[, Code_postal:=as.character(Code_postal)]
> DT[, Code_postal:=str_pad(Code_postal, 5, "left", "0")]
> DT <- DT[, latitude:=as.numeric(latitude)]
> DT <- DT[, longitude:=as.numeric(longitude)]
> summary(DT)
Code_commune_INSEE Nom_commune Code_postal Libelle_acheminement latitude longitude
Length:39201 Length:39201 Length:39201 Length:39201 Min. :-21.34 Min. :-61.7800
Class :character Class :character Class :character Class :character 1st Qu.: 45.14 1st Qu.: 0.6835
Mode :character Mode :character Mode :character Mode :character Median : 47.39 Median : 2.6841
Mean : 46.70 Mean : 2.7568
3rd Qu.: 48.83 3rd Qu.: 4.9798
Max. : 51.07 Max. : 55.7545
NA's :267 NA's :267
Par rapport à l'analyse précédente nous avons juste dû séparer la colonne coordonnees_gps
en deux colonnes latitude et longitude et fait en sorte que tous les codes postaux aient bien 5
caractères. Après appel à la fonction summary
on constate que le fichier possède 39201 lignes (contre 36800 pour celui data.gouv.fr) mais qu'il existe encore des NA's pour la latitude et
la longitude (au moins ici leur nombre est identique). Remarquons aussi que les Min. et Max. sont bien différents puisque les données incluent les DOM-TOM. Nous avons analysé les NA's qui correspondent
uniquement à la Polynésie Française et à des îles comme Saint-Martin ou Saint-Barthélémy. Ca peut être gênant pour certains usages mais ça ne l'est pas dans notre contexte.
Si nous avons cherché un autre référentiel que celui de data.gouv.fr c'est qu'il n'était pas satisfaisant pour le département des Hauts-de-Seine comme vous pouvez le voir dans la carte ci-dessous. A côté celui de La Poste est bien complet pour ce département et nous avons également présenté les données renvoyées par l'API de geocoding de Google.
Si l'on considère les données en rouge de data.gouv.fr, on constate que certaines villes sont manquantes (Asnière-Sur-Seine et Courbevoie), plusieurs sont placées en dehors du polygone de la commune (Rueil-Malmaison par exemple) et enfin d'autres possèdent les mêmes coordonnées (Vaucresson et Marnes-La-Coquette ou bien Fontenay-Aux-Roses et Sceaux). Les deux autres jeux de données (Google et La Poste) possèdent des différences mais sont dans l'ensemble bien meilleurs et ne contiennent pas les erreurs de celles de data.gouv.fr. Par simplicité et parce que le référentiel est directement compilé, nous avons retenu celui de La Poste. Pour Google, il faut utiliser leur API et récupérer une à une les coordonnées des communes de France, ce qui avec les limitations imposées par l'API peut prendre un peu de temps.
A ce moment-là on s'est dit qu'on avait trouvé le bon référentiel mais c'était avant d'en faire l'analyse des doublons.
> DT <- na.omit(DT)
> DT <- unique(DT, by = "Code_commune_INSEE")
> dupe = DT[,c('latitude','longitude')]
> dupeDT <- DT[duplicated(dupe) | duplicated(dupe, fromLast=TRUE),]
> dupeDT <- dupeDT[order(dupeDT$latitude)]
> nrow(dupeDT)
[1] 34061
Vous ne rêvez pas, les données fournies par La Poste contiennent plus de 35923 lignes avec un code INSEE différent mais 34061 d'entres-elles ont leurs coordonnées géographiques dupliquées au moins une fois. Nous avons arrêté de sourire en regardant le département de la Meuse dont les données ont été extraites avec le code suivant.
> DT55 <- DT[substr(Code_postal, 1, 2) == "55", ]
> fwrite(DT55, "c:/temp/error-demo-55.csv")
Il y a plus de 500 communes dans la Meuse mais seulement 29 coordonnées géographiques différentes dans le référentiel de La Poste. On peut les retrouver en vert dans la carte ci-dessus avec le nombre de communes ayant les mêmes coordonnées. A côté en rouge on retrouve les données de data.gouv.fr qui semble d'une incroyable précision du coup même s'il manque beaucoup de communes.
Suite à un commentaire que nous avons laissé sur leur site les données fournies par La Poste ont été mises à jour, il y avait eu une erreur dans le traitement des coordonnées géographiques. Les nouvelles données sont ici : laposte_hexasmal_update.zip. Reprenons toute l'analyse avec ces nouvelles données.
> require(data.table)
> DT <- fread("c:/temp/laposte_hexasmal_update.csv", encoding = "UTF-8")
> # On refait tout le traitement jusqu'à l'affichage du résumé
> summary(DT)
Code_commune_INSEE Nom_commune Code_postal Libelle_acheminement latitude longitude
Length:39201 Length:39201 Length:39201 Length:39201 Min. :-21.34 Min. :-61.7796
Class :character Class :character Class :character Class :character 1st Qu.: 45.13 1st Qu.: 0.6662
Mode :character Mode :character Mode :character Mode :character Median : 47.38 Median : 2.6871
Mean : 46.70 Mean : 2.7569
3rd Qu.: 48.82 3rd Qu.: 4.9811
Max. : 51.07 Max. : 55.7545
NA's :269 NA's :269
> DT <- na.omit(DT)
> DT <- unique(DT, by = "Code_commune_INSEE")
> dupe = DT[,c('latitude','longitude')]
> dupeDT <- DT[duplicated(dupe) | duplicated(dupe, fromLast=TRUE),]
> nrow(dupeDT)
[1] 0
Nous retrouvons 269 NA's, 2 de plus qu'avant la mise à jour. En revanche après avoir enlevé les codes INSEE en double il reste 35921 communes dont aucune ne partage les mêmes coordonnées !
Le changement est logiquement radical. Toutes les communes existent et possèdent des coordonnées pour chacune des cellules. Il manque uniquement la commune de Culey (code INSEE 55138). Dans tous les cas on ne peut que saluer la réactivité de La Poste et la qualité des données qu'ils proposent.
Nous venons de voir ensemble comment analyser un jeu de données de manière générale (valeurs manquantes, Min et Max...) et de manière spécifique (en se concentrant sur les coordonnées). Il en ressort qu'aucun des jeux de données étudiés ne peut être considéré comme complet ni fiable. La Poste a fait des progrès considérables suite à un commentaire que nous avons laissé mais il manque toujours certaines communes et nous n'avons pas étudié dans le détail les autres départements. Si l'on veut utiliser ces données pour la présence de coordonnées géographiques, il faut simplement y renoncer ou bien faire une analyse précise sur les données de La Poste qui sont de bonnes qualités malgré tout. Dans notre cas et pour le tutoriel cité en introduction nous avons préféré partir sur le centroid du polygone de chaque commune.
En réalité nous avons à peine commencer notre analyse. Concernant spécifiquement les communes nous devons aussi analyser le fichier geoJSON, est-il correct ? Chaque année des communes sont créées, d'autres disparaissent par fusion. A quelle année correspond ce geoJSON ? Et du coup à quelle année correspond la liste des communes que nous avons récupérée ? Comment un jeu de données définit le centre d'une commune (église, mairie...) ? Nous avons vu dans un précédent tutoriel (Optimisation du geoJSON) qu'il est possible de réduire la taille de nos fichiers geoJSON, celui que nous avons récupéré est-il conforme à notre usage ? Peu de sites font cas de ces problèmes mais dans un contexte professionnel il est important de se poser ces questions. Elles feront l'objet de futurs tutoriels dans la section TECH.
COMMENTAIRES
Etienne
Simplement merci de partager ces informations.
ericfrigot
Recevoir un 25 décembre un tel message ! Merci, ça fait vraiment plaisir, j'avais bien galéré sur ce sujet.