= "01450"
code_commune = 100
size = "https://koumoul.com/data-fair/api/v1/datasets/dpe-france/lines"
api_root = f"{api_root}?page=1&after=10&format=json&q_mode=simple&qs=code_insee_commune_actualise" + "%3A%22" + f"{code_commune}" + "%22" + f"&size={size}&select=" + "%2A&sampling=neighbors" url_api
La partie utilisant l’API DVF n’est plus à jour, elle sera mise à jour prochainement.
Introduction : Qu’est-ce qu’une API ?
Définition
Pour expliquer le principe d’une API, je vais reprendre le début de la fiche dédiée dans la documentation collaborative utilitR que je recommande de lire :
Une Application Programming Interface (ou API) est une interface de programmation qui permet d’utiliser une application existante pour restituer des données. Le terme d’API peut être paraître intimidant, mais il s’agit simplement d’une façon de mettre à disposition des données : plutôt que de laisser l’utilisateur consulter directement des bases de données (souvent volumineuses et complexes), l’API lui propose de formuler une requête qui est traitée par le serveur hébergeant la base de données, puis de recevoir des données en réponse à sa requête.
D’un point de vue informatique, une API est une porte d’entrée clairement identifiée par laquelle un logiciel offre des services à d’autres logiciels (ou utilisateurs). L’objectif d’une API est de fournir un point d’accès à une fonctionnalité qui soit facile à utiliser et qui masque les détails de la mise en oeuvre. Par exemple, l’API Sirene permet de récupérer la raison sociale d’une entreprise à partir de son identifiant Siren en interrogeant le référentiel disponible sur Internet directement depuis un script R, sans avoir à connaître tous les détails du répertoire Sirene.
À l’Insee comme ailleurs, la connexion entre les bases de données pour les nouveaux projets tend à se réaliser par des API. L’accès à des données par des API devient ainsi de plus en plus commun et est amené à devenir une compétence de base de tout utilisateur de données.
Avantages des API
A nouveau, citons la documentation utilitR :
Les API présentent de multiples avantages :
- Les API rendent les programmes plus reproductibles. En effet, grâce aux API, il est possible de mettre à jour facilement les données utilisées par un programme si celles-ci évoluent. Cette flexibilité accrue pour l’utilisateur évite au producteur de données d’avoir à réaliser de multiples extractions, et réduit le problème de la coexistence de versions différentes des données.
- Grâce aux API, l’utilisateur peut extraire facilement une petite partie d’une base de données plus conséquente.
- Les API permettent de mettre à disposition des données tout en limitant le nombre de personnes ayant accès aux bases de données elles-mêmes.
- Grâce aux API, il est possible de proposer des services sur mesure pour les utilisateurs (par exemple, un accès spécifique pour les gros utilisateurs).
L’utilisation accrue d’API dans le cadre de stratégies open-data est l’un des piliers des 15 feuilles de route ministérielles en matière d’ouverture, de circulation et de valorisation des données publiques.
Utilisation des API
Citons encore une fois
la documentation utilitR
:
Une API peut souvent être utilisée de deux façons : par une interface Web, et par l’intermédiaire d’un logiciel (R, Python…). Par ailleurs, les API peuvent être proposées avec un niveau de liberté variable pour l’utilisateur :
- soit en libre accès (l’utilisation n’est pas contrôlée et l’utilisateur peut utiliser le service comme bon lui semble) ;
- soit via la génération d’un compte et d’un jeton d’accès qui permettent de sécuriser l’utilisation de l’API et de limiter le nombre de requêtes.
De nombreuses API nécessitent une authentification, c’est-à-dire un
compte utilisateur afin de pouvoir accéder aux données.
Dans un premier temps,
nous regarderons exclusivement les API ouvertes sans restriction d’accès.
Certains exercices et exemples permettront néanmoins d’essayer des API
avec restrictions d’accès.
Requêter une API
Principe général
L’utilisation de l’interface Web est utile dans une démarche exploratoire mais trouve rapidement ses limites, notamment lorsqu’on consulte régulièrement l’API. L’utilisateur va rapidement se rendre compte qu’il est beaucoup plus commode d’utiliser une API via un logiciel de traitement pour automatiser la consultation ou pour réaliser du téléchargement de masse. De plus, l’interface Web n’existe pas systématiquement pour toutes les API.
Le mode principal de consultation d’une API consiste à adresser une requête à cette API via un logiciel adapté (R, Python, Java…). Comme pour l’utilisation d’une fonction, l’appel d’une API comprend des paramètres qui sont détaillées dans la documentation de l’API.
Voici les éléments importants à avoir en tête sur les requêtes (j’emprunte encore
à utilitR
) :
- Le point d’entrée d’un service offert par une API se présente sous la forme d’une URL (adresse web).
Chaque service proposé par une API a sa propre URL. Par exemple, dans le cas de l’OpenFood Facts,
l’URL à utiliser pour obtenir des informations sur un produit particulier (l’identifiant
737628064502
) est https://world.openfoodfacts.org/api/v0/product/737628064502.json - Cette URL doit être complétée avec différents paramètres qui précisent la requête (par exemple l’identifiant Siren). Ces paramètres viennent s’ajouter à l’URL, souvent à la suite de
?
. Chaque service proposé par une API a ses propres paramètres, détaillés dans la documentation. - Lorsque l’utilisateur soumet sa requête, l’API lui renvoie une réponse structurée contenant l’ensemble des informations demandées. Le résultat envoyé par une API est majoritairement aux formats
JSON
ouXML
(deux formats dans lesquels les informations sont hiérarchisées de manière emboitée). Plus rarement, certains services proposent une information sous forme plate (de type csv).
Du fait de la dimension hiérarchique des formats JSON
ou XML
,
le résultat n’est pas toujours facile à récupérer mais
Python
propose d’excellents outils pour cela (meilleurs que ceux de R
).
Certains packages, notamment json
, facilitent l’extraction de champs d’une sortie d’API.
Dans certains cas, des packages spécifiques à une API ont été créés pour simplifier l’écriture d’une requête ou la récupération du résultat. Par exemple, le package
pynsee
propose des options qui seront retranscrites automatiquement dans l’URL de
requête pour faciliter le travail sur les données Insee.
Illustration avec une API de l’Ademe pour obtenir des diagnostics energétiques
Le diagnostic de performance énergétique (DPE) renseigne sur la performance énergétique d’un logement ou d’un bâtiment, en évaluant sa consommation d’énergie et son impact en terme d’émissions de gaz à effet de serre.
Les données des performances énergétiques des bâtiments sont mises à disposition par l’Ademe. Comme ces données sont relativement volumineuses, une API peut être utile lorsqu’on ne s’intéresse qu’à un sous-champ des données. Une documentation et un espace de test de l’API sont disponibles sur le site API GOUV1.
Supposons qu’on désire récupérer une centaine de valeurs pour la commune de Villieu-Loyes-Mollon dans l’Ain (code Insee 01450).
L’API comporte plusieurs points d’entrée. Globalement, la racine commune est :
https://koumoul.com/data-fair/api/v1/datasets/dpe-france
Ensuite, en fonction de l’API désirée, on va ajouter des éléments
à cette racine. En l’occurrence, on va utiliser
l’API field
qui permet de récupérer des lignes en fonction d’un
ou plusieurs critères (pour nous, la localisation géographique):
L’exemple donné dans la documentation technique est
GET https://koumoul.com/data-fair/api/v1/datasets/dpe-france/values/{field}
ce qui en Python
se traduira par l’utilisation de la méthode get
du
package Request
sur un url dont la structure est la suivante :
- il commencera par
https://koumoul.com/data-fair/api/v1/datasets/dpe-france/values/
; - il sera ensuite suivi par des paramètres de recherche. Le champ
{field}
commence ainsi généralement par un?
qui permet ensuite de spécifier des paramètres sous la formenom_parameter=value
A la lecture de la documentation, les premiers paramètres qu’on désire :
- Le nombre de pages, ce qui nous permet d’obtenir un certain nombre d’échos. On va seulement récupérer 10 pages ce qui correspond à une centaine d’échos. On va néanmoins préciser qu’on veut 100 échos
- Le format de sortie. On va privilégier le
JSON
qui est un format standard dans le monde des API.Python
offre beaucoup de flexibilité grâce à l’un de ses objets de base, à savoir le dictionnaire (typedict
), pour manipuler de tels fichiers - Le code commune des données qu’on désire obtenir. Comme on l’a évoqué,
on va récupérer les données dont le code commune est
01450
. D’après la doc, il convient de passer le code commune sous le format:code_insee_commune_actualise:{code_commune}
. Pour éviter tout risque de mauvais formatage, on va utiliser%3A
pour signifier:
,%2A
pour signifier*
et%22
pour signifier"
. - D’autres paramètres annexes, suggérés par la documentation
Cela nous donne ainsi un URL dont la structure est la suivante :
Si vous introduisez cet URL dans votre navigateur, vous devriez aboutir
sur un JSON
non formaté2. En Python
,
on peut utiliser requests
pour récupérer les données3 :
import requests
import pandas as pd
= requests.get(url_api)
req = req.json() wb
Prenons par exemple les 1000 premiers caractères du résultat, pour se donner une idée du résultat et se convaincre que notre filtre au niveau communal est bien passé :
print(req.content[:1000])
b’{“total”: 121,“next”: “https://koumoul.com/data-fair/api/v1/datasets/dpe-france/lines?after=102721&format=json&q_mode=simple&qs=code_insee_commune_actualise%3A%2201450%22&size=100&select=*&sampling=neighbors”,“results”: [\n {“classe_consommation_energie”: “D”,“tr001_modele_dpe_type_libelle”: “Vente”,“annee_construction”: 1947,“_geopoint”: “45.925922,5.229964”,“latitude”: 45.925922,“surface_thermique_lot”: 117.16,“_i”: 487,“tr002_type_batiment_description”: “Maison Individuelle”,“geo_adresse”: “Rue de la Brugni8re 01800 Villieu-Loyes-Mollon”,“_rand”: 23215,“code_insee_commune_actualise”: “01450”,“estimation_ges”: 53,“geo_score”: 0.4,“classe_estimation_ges”: “E”,“nom_methode_dpe”: “M9thode Facture”,“tv016_departement_code”: “01”,“consommation_energie”: 178,“date_etablissement_dpe”: “2013-06-13”,“longitude”: 5.229964,“_score”: null,’
Ici, il n’est même pas nécessaire en première approche
d’utiliser le package json
, l’information
étant déjà tabulée dans l’écho renvoyé (on a la même information pour tous les pays):
On peut donc se contenter de Pandas
pour transformer nos données en
DataFrame
et Geopandas
pour convertir en données
géographiques :
import pandas as pandas
import geopandas as gpd
def get_dpe_from_url(url):
= requests.get(url)
req = req.json()
wb = pd.json_normalize(wb["results"])
df
= gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df.longitude, df.latitude), crs = 4326)
dpe = dpe.dropna(subset = ['longitude', 'latitude'])
dpe
return dpe
= get_dpe_from_url(url_api)
dpe 2) dpe.head(
/opt/mamba/lib/python3.9/site-packages/geopandas/_compat.py:123: UserWarning:
The Shapely GEOS version (3.12.0-CAPI-1.18.0) is incompatible with the GEOS version PyGEOS was compiled with (3.10.4-CAPI-1.16.2). Conversions between both will be slow.
/tmp/ipykernel_3715/2008334648.py:2: UserWarning:
Shapely 2.0 is installed, but because PyGEOS is also installed, GeoPandas will still use PyGEOS by default for now. To force to use and test Shapely 2.0, you have to set the environment variable USE_PYGEOS=0. You can do this before starting the Python process, or in your code before importing geopandas:
import os
os.environ['USE_PYGEOS'] = '0'
import geopandas
In a future release, GeoPandas will switch to using Shapely by default. If you are using PyGEOS directly (calling PyGEOS functions on geometries from GeoPandas), this will then stop working and you are encouraged to migrate from PyGEOS to Shapely 2.0 (https://shapely.readthedocs.io/en/latest/migration_pygeos.html).
classe_consommation_energie | tr001_modele_dpe_type_libelle | annee_construction | _geopoint | latitude | surface_thermique_lot | _i | tr002_type_batiment_description | geo_adresse | _rand | ... | classe_estimation_ges | nom_methode_dpe | tv016_departement_code | consommation_energie | date_etablissement_dpe | longitude | _score | _id | version_methode_dpe | geometry | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | D | Vente | 1947 | 45.925922,5.229964 | 45.925922 | 117.16 | 487 | Maison Individuelle | Rue de la Brugnière 01800 Villieu-Loyes-Mollon | 23215 | ... | E | Méthode Facture | 01 | 178.00 | 2013-06-13 | 5.229964 | None | 04JZNel3WCJYcfsHpCcHv | NaN | POINT (5.22996 45.92592) |
2 | D | Neuf | 2006 | 45.923421,5.223777 | 45.923421 | 90.53 | 689 | Maison Individuelle | Chemin du Pont-vieux 01800 Villieu-Loyes-Mollon | 401672 | ... | C | FACTURE - DPE | 01 | 227.99 | 2013-06-11 | 5.223777 | None | rkdV2lJn2wxaidVBaHBFY | V2012 | POINT (5.22378 45.92342) |
2 rows × 23 columns
Essayons de représenter sur une carte ces DPE avec les
années de construction des logements.
Avec Folium
, on obtient la carte interactive suivante :
import seaborn as sns
import folium
= sns.color_palette("coolwarm", 8)
palette
def interactive_map_dpe(dpe):
# convert in number
'color'] = [ord(dpe.iloc[i]['classe_consommation_energie'].lower()) - 96 for i in range(len(dpe))]
dpe[= dpe.loc[dpe['color']<=7]
dpe 'color'] = [palette.as_hex()[x] for x in dpe['color']]
dpe[
= dpe[['latitude', 'longitude']].mean().values.tolist()
center = dpe[['latitude', 'longitude']].min().values.tolist()
sw = dpe[['latitude', 'longitude']].max().values.tolist()
ne
= folium.Map(location = center, tiles='OpenStreetMap')
m
# I can add marker one by one on the map
for i in range(0,len(dpe)):
'latitude'], dpe.iloc[i]['longitude']],
folium.Marker([dpe.iloc[i][=f"Année de construction : {dpe.iloc[i]['annee_construction']}, <br>DPE : {dpe.iloc[i]['classe_consommation_energie']}",
popup=folium.Icon(color="black", icon="home", icon_color = dpe.iloc[i]['color'])).add_to(m)
icon
m.fit_bounds([sw, ne])
return m
= interactive_map_dpe(dpe) m
/opt/mamba/lib/python3.9/site-packages/geopandas/geodataframe.py:1443: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
# Afficher la carte
m
Un catalogue incomplet d’API existantes
De plus en plus de sites mettent des API à disposition des développeurs et autres curieux.
Pour en citer quelques-unes très connues :
Twitter
: https://dev.twitter.com/rest/publicFacebook
: https://developers.facebook.com/Instagram
: https://www.instagram.com/developer/Spotify
: https://developer.spotify.com/web-api/
Cependant, il est intéressant de ne pas se restreindre à celles-ci dont les données ne sont pas toujours les plus intéressantes. Beaucoup de producteurs de données, privés comme publics, mettent à disposition leurs données sous forme d’API.
- API gouv : beaucoup d’API officielles de l’Etat français et accès à de la documentation
- Insee : https://api.insee.fr/catalogue/ et
pynsee
- Pôle Emploi : https://www.emploi-store-dev.fr/portail-developpeur-cms/home.html
- SNCF : https://data.sncf.com/api
- Banque Mondiale : https://datahelpdesk.worldbank.org/knowledgebase/topics/125589
L’API DVF : accéder à des données de transactions immobilières simplement
⚠️ Cette partie nécessite une mise à jour pour privilégier l’API DVF du Cerema.
Le site DVF
(demandes de valeurs foncières) permet de visualiser toutes les données relatives aux mutations à titre onéreux (ventes de maisons, appartements, garages…) réalisées durant les 5 dernières années.
Un site de visualisation est disponible sur https://app.dvf.etalab.gouv.fr/.
Ce site est très complet quand il s’agit de connaître le prix moyen au mètre carré d’un quartier ou de comparer des régions entre elles. L’API DVF permet d’aller plus loin afin de récupérer les résultats dans un logiciel de traitement de données. Elle a été réalisée par Christian Quest et le code source est disponible sur Github .
Les critères de recherche sont les suivants :
- code_commune
= code INSEE de la commune (ex: 94068)
- section
= section cadastrale (ex: 94068000CQ)
- numero_plan
= identifiant de la parcelle, (ex: 94068000CQ0110)
- lat
+ lon
+ dist
(optionnel): pour une recherche géographique, dist est par défaut un rayon de 500m
- code_postal
Les filtres de sélection complémentaires :
- nature_mutation
(Vente, etc)
- type_local
(Maison, Appartement, Local, Dépendance)
Les requêtes sont de la forme : http://api.cquest.org/dvf?code_commune=29168
.
Exercice 1 : Exploiter l'API DVF
- Rechercher toutes les transactions existantes dans DVF à Plogoff (code commune
29168
, en Bretagne). Afficher les clés du JSON et en déduire le nombre de transactions répertoriées. - N’afficher que les transactions portant sur des maisons.
- Utiliser l’API geo pour récupérer le découpage communal de la ville de Plogoff.
- Représenter l’histogramme des prix de vente.
N’hésitez pas à aller plus loin en jouant sur des variables de groupes par exemple.
Le résultat de la question 2 devrait
ressembler au DataFrame
suivant :
L’histogramme des prix de vente (question 4) aura l’aspect suivant :
On va faire une carte des ventes en affichant le prix de l’achat. La cartographie réactive sera présentée dans les chapitres consacrés à la visualisation de données.
Supposons que le DataFrame des ventes s’appelle ventes
. Il faut d’abord le
convertir
en objet geopandas
.
Avant de faire une carte, on va convertir
les limites de la commune de Plogoff en geoJSON pour faciliter
sa représentation avec folium
(voir la doc geopandas
à ce propos):
Pour représenter graphiquement, on peut utiliser le code suivant (essayez de le comprendre et pas uniquement de l’exécuter).
# Afficher la carte
m
Géocoder des données grâce aux API officielles
Pour pouvoir faire cet exercice
!pip install xlrd
Jusqu’à présent, nous avons travaillé sur des données où la dimension géographique était déjà présente ou relativement facile à intégrer.
Ce cas idéal ne se rencontre pas nécessairement dans la pratique. On dispose parfois de localisations plus ou moins précises et plus ou moins bien formattées pour déterminer la localisation de certains lieux.
Depuis quelques années, un service officiel de géocodage a été mis en place. Celui-ci est gratuit et permet de manière efficace de coder des adresses à partir d’une API. Cette API, connue sous le nom de la Base d’Adresses Nationale (BAN) a bénéficié de la mise en commun de données de plusieurs acteurs (collectivités locales, Poste) et de compétences d’acteurs comme Etalab. La documentation de celle-ci est disponible à l’adresse https://api.gouv.fr/les-api/base-adresse-nationale.
Pour illustrer la manière de géocoder des données avec Python
, nous
allons partir de la base
des résultats des auto-écoles à l’examen du permis sur l’année 2018.
Ces données nécessitent un petit peu de travail pour être propres à une analyse statistique. Après avoir renommé les colonnes, nous n’allons conserver que les informations relatives au permis B (permis voiture classique) et les auto-écoles ayant présenté au moins 20 personnes à l’examen.
import pandas as pd
import xlrd
import geopandas as gpd
= pd.read_excel("https://www.data.gouv.fr/fr/datasets/r/d4b6b072-8a7d-4e04-a029-8cdbdbaf36a5", header = [0,1])
df
# Le Excel a des noms de colonne emboitées,
# on nettoie
= ["" if df.columns[i][0].startswith("Unnamed") else df.columns[i][0] for i in range(len(df.columns))]
index_0 = [df.columns[i][1] for i in range(len(df.columns))]
index_1 = [True if el in ('', "B") else False for el in index_0]
keep_index = [index_0[i] + " " + index_1[i].replace("+", "_") for i in range(len(df.columns))]
cols = cols
df.columns = df.loc[:, keep_index]
df = df.columns.str.replace("(^ |°)", "", regex = True).str.replace(" ", "_")
df.columns
# On garde le sous-échantillon d'intérêt
= df.dropna(subset = ['B_NB'])
df = df.loc[~df["B_NB"].astype(str).str.contains("(\%|\.)"),:]
df 'B_NB'] = df['B_NB'].astype(int)
df['B_TR'] = df['B_TR'].str.replace(",", ".").str.replace("%","").astype(float)
df[= df.loc[df["B_NB"]>20] df
/tmp/ipykernel_3715/3216706257.py:19: UserWarning:
This pattern is interpreted as a regular expression, and has match groups. To actually get the groups, use str.extract.
Sur cet échantillon, le taux de réussite moyen était, en 2018, de 58.02%
Nos informations géographiques prennent la forme suivante :
'Adresse','CP','Ville']].head(5) df.loc[:,[
Adresse | CP | Ville | |
---|---|---|---|
0 | 56 RUE CHARLES ROBIN | 01000 | BOURG EN BRESSE |
2 | 7, avenue Revermont | 01250 | Ceyzeriat |
3 | 72 PLACE DE LA MAIRIE | 01000 | SAINT-DENIS LES BOURG |
4 | 6 RUE DU LYCEE | 01000 | BOURG EN BRESSE |
5 | 9 place Edgard Quinet | 01000 | BOURG EN BRESSE |
Autrement dit, nous disposons d’une adresse, d’un code postal et d’un nom de ville. Ces informations peuvent servir à faire une recherche sur la localisation d’une auto-école puis, éventuellement, de se restreindre à un sous-échantillon.
Utiliser l’API BAN
La documentation officielle de l’API propose un certain nombre d’exemples de manière de géolocaliser des données. Dans notre situation, deux points d’entrée paraissent intéressants:
- L’API
/search/
qui représente un point d’entrée avec des URL de la formehttps://api-adresse.data.gouv.fr/search/?q=\<adresse\>&postcode=\<codepostal\>&limit=1
- L’API
/search/csv
qui prend un CSV en entrée et retourne ce même CSV avec les observations géocodées. La requête prend la forme suivante, en apparence moins simple à mettre en oeuvre :curl -X POST -F data=@search.csv -F columns=adresse -F columns=postcode https://api-adresse.data.gouv.fr/search/csv/
La tentation serait forte d’utiliser la première méthode avec une boucle sur les
lignes de notre DataFrame
pour géocoder l’ensemble de notre jeu de données.
Cela serait néanmoins une mauvaise idée car les communications entre notre
session Python
et les serveurs de l’API seraient beaucoup trop nombreuses
pour offrir des performances satisfaisantes.
Pour vous en convaincre, vous pouvez exécuter le code suivant sur un petit échantillon de données (par exemple 100 comme ici) et remarquer que le temps d’exécution est assez important
import time
= df.loc[:, ['Adresse','CP','Ville']].apply(lambda s: s.str.lower().str.replace(","," "))
dfgeoloc 'url'] = (dfgeoloc['Adresse'] + "+" + dfgeoloc['Ville'].str.replace("-",'+')).str.replace(" ","+")
dfgeoloc['url'] = 'https://api-adresse.data.gouv.fr/search/?q=' + dfgeoloc['url'] + "&postcode=" + df['CP'] + "&limit=1"
dfgeoloc[= dfgeoloc.dropna()
dfgeoloc
= time.time()
start_time
def get_geoloc(i):
print(i)
return gpd.GeoDataFrame.from_features(requests.get(dfgeoloc['url'].iloc[i]).json()['features'])
= [get_geoloc(i) for i in range(len(dfgeoloc.head(10)))]
local print("--- %s seconds ---" % (time.time() - start_time))
Comme l’indique la documentation, si on désire industrialiser notre processus de géocodage, on va privilégier l’API CSV.
Pour obtenir une requête CURL
cohérente avec le format désiré par l’API
on va à nouveau utiliser Requests
mais cette fois avec des paramètres
supplémentaires:
data
va nous permettre de passer des paramètres àCURL
(équivalents aux-F
de la requêteCURL
) :columns
: Les colonnes utilisées pour localiser une donnée. En l’occurrence, on utilise l’adresse et la ville (car les codes postaux n’étant pas uniques, un même nom de voirie peut se trouver dans plusieurs villes partageant le même code postal) ;postcode
: Le code postal de la ville. Idéalement nous aurions utilisé le code Insee mais nous ne l’avons pas dans nos données ;result_columns
: on restreint les données échangées avec l’API aux colonnes qui nous intéressent. Cela permet d’accélérer les processus (on échange moins de données) et de réduire l’impact carbone de notre activité (moins de transferts = moins d’énergie dépensée). En l’occurrence, on ne ressort que les données géolocalisées et un score de confiance en la géolocalisation ;
files
: permet d’envoyer un fichier viaCURL
.
Les données sont récupérées avec request.post
. Comme il s’agit d’une
chaîne de caractère, nous pouvons directement la lire avec Pandas
en
utilisant io.StringIO
pour éviter d’écrire des données intermédiaires.
Le nombre d’échos semblant être limité, il est proposé de procéder par morceaux (ici, le jeu de données est découpé en 5 morceaux).
import requests
import io
import numpy as np
import time
= {
params 'columns': ['Adresse', 'Ville'],
'postcode': 'CP',
'result_columns': ['result_score', 'latitude', 'longitude'],
}
'Adresse','CP','Ville']] = df.loc[:, ['Adresse','CP','Ville']].apply(lambda s: s.str.lower().str.replace(","," "))
df[[
def geoloc_chunk(x):
= x.loc[:, ['Adresse','CP','Ville']]
dfgeoloc "datageocodage.csv", index=False)
dfgeoloc.to_csv(= requests.post('https://api-adresse.data.gouv.fr/search/csv/', data=params, files={'data': ('datageocodage.csv', open('datageocodage.csv', 'rb'))})
response = pd.read_csv(io.StringIO(response.text), dtype = {'CP': 'str'})
geoloc return geoloc
= time.time()
start_time = [geoloc_chunk(dd) for dd in np.array_split(df, 10)]
geodata print("--- %s seconds ---" % (time.time() - start_time))
Cette méthode est beaucoup plus rapide et permet ainsi, une fois retourné à nos données initiales, d’avoir un jeu de données géolocalisé.
# Retour aux données initiales
= pd.concat(geodata, ignore_index = True)
geodata = df.merge(geodata, on = ['Adresse','CP','Ville'])
df_xy = df_xy.dropna(subset = ['latitude','longitude'])
df_xy
# Mise en forme pour le tooltip
'text'] = (
df_xy['Raison_Sociale'] + '<br>' +
df_xy['Adresse'] + '<br>' +
df_xy['Ville'] + '<br>Nombre de candidats:' + df_xy['B_NB'].astype(str)
df_xy[
)filter(
df_xy.'Raison_Sociale','Adresse','CP','Ville','latitude','longitude'],
[= "columns"
axis 10) ).sample(
Raison_Sociale | Adresse | CP | Ville | latitude | longitude | |
---|---|---|---|---|---|---|
1866 | BE MARULAZ | 5 rue marulaz | 25000 | besancon | 47.239450 | 6.017970 |
2641 | RALLYE | 11 allée du vivarais | 31300 | toulouse | 43.618635 | 1.398048 |
10240 | ECOLE DE CONDUITE DE LIVRY | 38 av du general leclerc | 93190 | livry gargan | 48.920615 | 2.540015 |
6261 | BIALLAIS | 20 rue du huitième de ligne | 62500 | saint omer | 50.751024 | 2.250891 |
11015 | Auto-Ecole MARY P. Chronopermis | section pombiray - chemin du centre | 97118 | saint-françois | 16.272601 | -61.300826 |
3033 | TOP DEPART | 10 rue roger sabatier | 30170 | saint hyppolite du fort | 43.965113 | 3.855350 |
9749 | MC BRETIGNY | 18 rue d estienne d orves | 91220 | bretigny sur orge | 48.607321 | 2.303790 |
2601 | ZZ IDEE FIXE GRATENTOUR | 4 rue du barry | 31150 | gratentour | 43.714516 | 1.431349 |
6438 | Ecole de conduite ECO 3 | 3 place grassion | 63670 | le cendre | 45.721374 | 3.187518 |
9099 | E2CR Puget | rue général de gaulle | 83480 | puget sur argens | 43.454074 | 6.688542 |
Il ne reste plus qu’à utiliser Geopandas
et nous serons en mesure de faire une carte des localisations des auto-écoles :
# Transforme en geopandas pour les cartes
import geopandas as gpd
= gpd.GeoDataFrame(
dfgeo
df_xy,= gpd.points_from_xy(df_xy.longitude, df_xy.latitude)
geometry )
Nous allons représenter les stations dans l’Essonne avec un zoom initialement sur les villes de Massy et Palaiseau. Le code est le suivant :
import folium
# Représenter toutes les autoécoles de l'Essonne
= df_xy.loc[df_xy["Dept"] == "091"]
df_91
# Centrer la vue initiale sur Massy-Palaiseau
= df_xy.loc[df_xy['Ville'].isin(["massy", "palaiseau"])]
df_pal = df_pal[['latitude', 'longitude']].mean().values.tolist()
center = df_pal[['latitude', 'longitude']].min().values.tolist()
sw = df_pal[['latitude', 'longitude']].max().values.tolist()
ne
= folium.Map(location = center, tiles='OpenStreetMap')
m
# I can add marker one by one on the map
for i in range(0,len(df_91)):
'latitude'], df_91.iloc[i]['longitude']],
folium.Marker([df_91.iloc[i][=df_91.iloc[i]['text'],
popup=folium.Icon(icon='car', prefix='fa')).add_to(m)
icon
m.fit_bounds([sw, ne])
Ce qui permet d’obtenir la carte:
# Afficher la carte
m
Vous pouvez aller plus loin avec l’exercice suivant.
Exercice 2 : Quelles sont les auto-écoles les plus proches de chez moi ?
On va supposer que vous cherchez, dans un rayon donné autour d’un centre ville, les auto-écoles disponibles.
Fonction nécessaire pour cet exercice
Cet exercice nécessite une fonction pour créer un cercle autour d’un point (source ici). La voici :
from functools import partial
import pyproj
from shapely.ops import transform
from shapely.geometry import Point
= pyproj.Proj('+proj=longlat +datum=WGS84')
proj_wgs84
def geodesic_point_buffer(lat, lon, km):
# Azimuthal equidistant projection
= '+proj=aeqd +lat_0={lat} +lon_0={lon} +x_0=0 +y_0=0'
aeqd_proj = partial(
project
pyproj.transform,format(lat=lat, lon=lon)),
pyproj.Proj(aeqd_proj.
proj_wgs84)= Point(0, 0).buffer(km * 1000) # distance in metres
buf return transform(project, buf).exterior.coords[:]
- Pour commencer, utiliser l’API Geo pour la ville de Palaiseau.
- Appliquer la fonction
geodesic_point_buffer
au centre ville de Palaiseau - Ne conserver que les auto-écoles dans ce cercle et les ordonner
Si vous avez la réponse à la question 3, n’hésitez pas à la soumettre sur Github
afin que je complète la correction 😉 !
ERROR 1: PROJ: proj_create_from_database: Open of /opt/mamba/share/proj failed
/opt/mamba/lib/python3.9/site-packages/shapely/ops.py:276: FutureWarning:
This function is deprecated. See: https://pyproj4.github.io/pyproj/stable/gotchas.html#upgrading-to-pyproj-2-from-pyproj-1
Pour se convaincre, de notre cercle constitué lors de la question 2, on peut représenter une carte. On a bien un cercle centré autour de Palaiseau :
Exercices supplémentaires
Découvrir l’API d’OpenFoodFacts
Pour vous aidez, vous pouvez regarder une exemple de structure du JSON ici :
https://world.openfoodfacts.org/api/v0/product/3274080005003.json en particulier la catégorie nutriments
.
Exercice 3 : Retrouver des produits dans l'openfood facts 🍕
Voici une liste de code-barres:
3274080005003, 5449000000996, 8002270014901, 3228857000906, 3017620421006, 8712100325953
Utiliser l’API d’openfoodfacts (l’API, pas depuis le CSV !) pour retrouver les produits correspondants et leurs caractéristiques nutritionnelles.
Le panier paraît-il équilibré ? 🍫
Récupérer l’URL d’une des images et l’afficher dans votre navigateur.
Voici par exemple la photo du produit ayant le code-barre 5449000000996
. Vous le reconnaissez ?
Footnotes
Le JSON est un format très apprécié dans le domaine du big data car il permet d’empiler des données qui ne sont pas complètes. Il s’agit d’un des formats privilégiés du paradigme No-SQL pour lequel cet excellent cours propose plus de détails.↩︎
Suivant les API, nous avons soit besoin de rien de plus si nous parvenons directement à obtenir un json, soit devoir utiliser un parser comme
BeautifulSoup
dans le cas contraire. Ici, le JSON peut être formaté relativement aisément.↩︎
Citation
@book{galiana2023,
author = {Galiana, Lino},
title = {Python Pour La Data Science},
date = {2023},
url = {https://pythonds.linogaliana.fr/},
doi = {10.5281/zenodo.8229676},
langid = {en}
}