Récupérer des données avec des API depuis Python

Les API (Application Programming Interface) sont un mode d’accès aux données en expansion. Grâce aux API, l’automatisation de scripts est facilitée puisqu’il n’est plus nécessaire de stocker un fichier, et gérer ses différentes versions, mais uniquement de requêter une base et laisser au producteur de données le soin de gérer les mises à jour de la base.

Exercice
Manipulation
Auteur·rice

Lino Galiana

Date de publication

2025-01-15

Pour essayer les exemples présents dans ce tutoriel :
View on GitHub Onyxia Onyxia Open In Colab

1 Introduction : Qu’est-ce qu’une API ?

Nous avons vu dans les chapitres précédents comment consommer des données depuis un fichier (le mode d’accès le plus simple) ou comment récupérer des données par le biais du webscraping, une méthode qui permet à Python de singer le comportement d’un navigateur web et de récupérer de l’information en moissonnant le HTML auquel accède un site web.

Le webscraping est un pis-aller pour accéder à de la donnée. Heureusement, il existe d’autres manières d’accéder à des données : les API de données. En informatique, une API est un ensemble de protocoles permettant à deux logiciels de communiquer entre eux. Par exemple, on parle parfois d’API Pandas ce qui désigne le fait que Pandas est une interface entre votre code Python et un langage compilé plus efficace (C) qui fait les calculs que vous demandez au niveau de Python. L’objectif d’une API est ainsi de fournir un point d’accès à une fonctionnalité qui soit facile à utiliser et qui masque les détails de la mise en oeuvre.

Dans ce chapitre, nous nous intéressons principalement aux API de données. Ces dernières sont simplement 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.

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 françaises en matière d’ouverture, de circulation et de valorisation des données publiques.

Note

Depuis quelques années, un service officiel de géocodage a été mis en place pour le territoire français. 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, IGN) 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.

On prend souvent comme exemple pour illustrer les API l’exemple du restaurant. La documentation est votre menu : elle liste les plats (les bases de données) que vous pouvez commander et les éventuels ingrédients de celle-ci que vous pouvez choisir (les paramètres de votre requête) : poulet, boeuf ou option végé ? Lorsque vous faites ceci, vous ne connaissez pas la recette utilisée en arrière cuisine pour faire votre plat: vous recevez seulement celui-ci. De manière logique, plus le plat que vous avez demandé est raffiné (calculs complexes côté serveur), plus votre plat mettra du temps à vous arriver.

Illustration avec l’API BAN

Pour illustrer ceci, imaginons ce qu’il se passe lorsque, dans la suite du chapitre, nous ferons des requêtes à l’API BAN.

Via Python, on envoie notre commande à celle-ci: des adresses plus ou moins complètes avec des instructions annexes comme le code commune. Ces instructions annexes peuvent s’apparenter à des informations fournies au serveur du restaurant comme des interdits alimentaires qui vont personnaliser la recette.

A partir de ces instructions, le plat est lancé. En l’occurrence, il s’agit de faire tourner sur les serveurs d’Etalab une routine qui va chercher dans un référentiel d’adresse celle qui est la plus similaire à celle qu’on a demandé en adaptant éventuellement en fonction des instructions annexes qu’on a fourni. Une fois que côté cuisine on a fini cette préparation, on renvoie le plat au client. En l’occurrence, le plat sera des coordonnées géographiques qui correspondent à l’adresse la plus similaire.

Le client n’a donc qu’à se préoccuper de faire une bonne requête et d’apprécier le plat qui lui est fourni. L’intelligence dans la mise en oeuvre est laissée aux spécialistes qui ont conçu l’API. Peut-être que d’autres spécialistes, par exemple Google Maps, mettent en oeuvre une recette différente pour ce même plat (des coordonnées géographiques) mais ils vous proposeront probablement un menu très similaire. Ceci vous simplifie beaucoup la vie: il vous suffit de changer quelques lignes de code d’appel à une API plutôt que de modifier un ensemble long et complexe de méthodes d’identification d’adresses.

Approche pédagogique

Après une première présentation du principe général des API, ce chapitre illustre l’usage de celles-ci via Python par le biais d’un use case assez standard: on dispose d’un jeu de données qu’on désire d’abord géolocaliser. Pour cela, on va demander à une API de nous renvoyer des coordonnées géographiques à partir d’adresses. Ensuite on ira chercher des informations un peu plus complexes par le biais d’autres API.

2 Première utilisation d’API

Une API a donc vocation à servir d’intermédiaire entre un client et un serveur. Ce client peut être de deux types: une interface web ou un logiciel de programmation. L’API ne fait pas d’a priori sur l’outil qui sert lui passe une commande, elle lui demande seulement de respecter un standard (en général une requête http), une structure de requête (les arguments) et d’attendre le résultat.

2.1 Comprendre le principe avec un exemple interactif

Le premier mode (accès par un navigateur) est principalement utilisé lorsqu’une interface web permet à un utilisateur de faire des choix afin de lui renvoyer des résultats correspondant à ceux-ci. Prenons à nouveau l’exemple de l’API de géolocalisation que nous utiliserons dans ce chapitre. Imaginons une interface web permettant à l’utilisateur deux choix: un code postal et une adresse. Cela sera injecté dans la requête et le serveur répondra avec la géolocalisation adaptée.

Voici donc nos deux widgets pour permettre au client (l’utilisateur de la page web) de choisir son adresse.

Une petite mise en forme des valeurs renseignées par ce widget permet d’obtenir la requête voulue:

Ce qui nous donne un output au format JSON, le format de sortie d’API le plus commun.

Si on veut un beau rendu, comme la carte ci-dessus, il faudra que le navigateur retravaille cet output, ce qui se fait normalement avec Javascript, le langage de programmation embarqué par les navigateurs.

2.2 Comment faire avec Python ?

Le principe est le même sauf que nous perdons l’aspect interactif. Il s’agira donc, avec Python, de construire l’URL voulu et d’aller chercher via une requête HTTP le résultat.

Nous avons déjà vu dans le chapitre de webscraping la manière dont Python communique avec internet: via le package requests. Ce package suit le protocole HTTP où on retrouve principalement deux types de requêtes: GET et POST:

  • La requête GET est utilisée pour récupérer des données depuis un serveur web. C’est la méthode la plus simple et courante pour accéder aux ressources d’une page web. Nous allons commencer par décrire celle-ci.
  • La requête POST est utilisée pour envoyer des données au serveur, souvent dans le but de créer ou de mettre à jour une ressource. Sur les pages web, elle sert souvent à la soumission de formulaires qui nécessitent de mettre à jour des informations sur une base (mot de passe, informations clients, etc.). Nous verrons son utilité plus tard, lorsque nous commencerons à rentrer dans les requêtes authentifiées où il faudra soumettre des informations supplémentaires à notre requête.

Faisons un premier test avec Python en faisant comme si nous connaissions bien cette API.

import requests

url_ban_example = (
    "https://api-adresse.data.gouv.fr/search/?q=88+avenue+verdier&postcode=92120"
)
requests.get(url_ban_example)
<Response [200]>

Qu’est-ce qu’on obtient ? Un code HTTP. Le code 200 correspond aux requêtes réussies, c’est-à-dire pour lesquelles le serveur est en mesure de répondre. Si ce n’est pas le cas, pour une raison x ou y, vous aurez un code différent.

Les codes HTTP

Les codes de statut HTTP sont des réponses standard envoyées par les serveurs web pour indiquer le résultat d’une requête effectuée par un client (comme un navigateur ou un script Python). Ils sont classés en différentes catégories selon le premier chiffre du code :

  • 1xx : Informations
  • 2xx : Succès
  • 3xx : Redirections
  • 4xx : Erreurs côté client
  • 5xx : Erreurs côté serveur

Ceux à retenir sont : 200 (succès), 400 (requête mal structurée), 401 (authentification non réussie), 403 (accès interdit), 404 (ressource demandée n’existe pas), 503 (le serveur n’est pas en capacité de répondre)

Pour récupérer le contenu renvoyé par requests, il existe plusieurs méthodes. Quand on un JSON bien formatté, le plus simple est d’utiliser la méthode json qui transforme cela en dictionnaire :

req = requests.get(url_ban_example)
localisation_insee = req.json()
localisation_insee
{'type': 'FeatureCollection',
 'version': 'draft',
 'features': [{'type': 'Feature',
   'geometry': {'type': 'Point', 'coordinates': [2.309144, 48.81622]},
   'properties': {'label': '88 Avenue Verdier 92120 Montrouge',
    'score': 0.9735636363636364,
    'housenumber': '88',
    'id': '92049_9625_00088',
    'banId': '92dd3c4a-6703-423d-bf09-fc0412fb4f89',
    'name': '88 Avenue Verdier',
    'postcode': '92120',
    'citycode': '92049',
    'x': 649270.67,
    'y': 6857572.24,
    'city': 'Montrouge',
    'context': '92, Hauts-de-Seine, Île-de-France',
    'type': 'housenumber',
    'importance': 0.7092,
    'street': 'Avenue Verdier'}}],
 'attribution': 'BAN',
 'licence': 'ETALAB-2.0',
 'query': '88 avenue verdier',
 'filters': {'postcode': '92120'},
 'limit': 5}

En l’occurrence, on voit que les données sont dans un JSON imbriqué. Il faut donc développer un peu de code pour récupérer les informations voulues dans celui-ci:

localisation_insee.get("features")[0].get("properties")
{'label': '88 Avenue Verdier 92120 Montrouge',
 'score': 0.9735636363636364,
 'housenumber': '88',
 'id': '92049_9625_00088',
 'banId': '92dd3c4a-6703-423d-bf09-fc0412fb4f89',
 'name': '88 Avenue Verdier',
 'postcode': '92120',
 'citycode': '92049',
 'x': 649270.67,
 'y': 6857572.24,
 'city': 'Montrouge',
 'context': '92, Hauts-de-Seine, Île-de-France',
 'type': 'housenumber',
 'importance': 0.7092,
 'street': 'Avenue Verdier'}

C’est là l’inconvénient principal de l’usage des API: le travail ex post sur les données renvoyées. Le code nécessaire est propre à chaque API puisque l’architecture du JSON dépend de chaque API.

2.3 Comment connaître les inputs et outputs des API ?

Ici on a pris l’API BAN comme un outil magique dont on connaissait les principaux inputs (l’endpoint, les paramètres et leur formattage…). Mais comment faire, en pratique, pour en arriver là ? Tout simplement en lisant la documentation lorsqu’elle existe et en testant celle-ci via des exemples.

Les bonnes API proposent un outil interactif qui s’appelle le swagger. C’est un site web interactif où sont décrites les principales fonctionnalités de l’API et où l’utilisateur peut tester des exemples interactivement. Ces documentations sont souvent créées automatiquement lors de la construction d’une API et mises à disposition par le biais d’un point d’entrée /docs. Elles permettent souvent d’éditer certains paramètres dans le navigateur, voir le JSON obtenu (ou l’erreur générée) et récupérer la requête formattée qui permet d’obtenir celui-ci. Ces consoles interactives dans le navigateur permettent de répliquer le tâtonnement qu’on peut faire par ailleurs dans des outils spécialisés comme postman.

Concernant l’API BAN, la documentation se trouve sur https://adresse.data.gouv.fr/api-doc/adresse. Elle n’est pas interactive, malheureusement. Mais elle présente de nombreux exemples qui peuvent être testés directement depuis le navigateur. Il suffit d’utiliser les URL proposés comme exemple. Ceux-ci sont présentées par le biais de curl (un équivalent de request en ligne de commande Linux):

curl "https://api-adresse.data.gouv.fr/search/?q=8+bd+du+port&limit=15"

Il suffit de copier l’URL en question (https://api-adresse.data.gouv.fr/search/?q=8+bd+du+port&limit=15), d’ouvrir un nouvel onglet et vérifier que cela produit bien un résultat. Puis de changer un paramètre et vérifier à nouveau, jusqu’à trouver la structure qui convient. Et après, on peut passer à Python comme le propose l’exercice suivant.

2.4 Application

Pour commencer cet exercice, vous aurez besoin de cette variable:

adresse = "88 Avenue Verdier"
Exercice 1: Structurer un appel à une API depuis Python
  1. Tester sans aucun autre paramètre, le retour de notre API. Transformer en DataFrame le résultat.
  2. Se restreindre à Montrouge avec le paramètre ad hoc et la recherche du code insee ou code postal adéquat sur google.
  3. (Optionnel): Représenter l’adresse trouvée sur une carte

Les deux premières lignes du dataframe obtenu à la question 1 devraient être

label score housenumber id banId name postcode citycode x y city context type importance street
0 88 Avenue Verdier 92120 Montrouge 0.973564 88 92049_9625_00088 92dd3c4a-6703-423d-bf09-fc0412fb4f89 88 Avenue Verdier 92120 92049 649270.67 6857572.24 Montrouge 92, Hauts-de-Seine, Île-de-France housenumber 0.7092 Avenue Verdier
1 Avenue Verdier 44500 La Baule-Escoublac 0.719373 NaN 44055_3690 NaN Avenue Verdier 44500 44055 291884.83 6701220.48 La Baule-Escoublac 44, Loire-Atlantique, Pays de la Loire street 0.6006 Avenue Verdier

A la question 2, on ressort cette fois qu’une seule observation, qu’on pourrait retravailler avec GeoPandas pour vérifier qu’on a bien placé ce point sur une carte

label score housenumber id banId name postcode citycode x y city context type importance street geometry
0 88 Avenue Verdier 92120 Montrouge 0.973564 88 92049_9625_00088 92dd3c4a-6703-423d-bf09-fc0412fb4f89 88 Avenue Verdier 92120 92049 649270.67 6857572.24 Montrouge 92, Hauts-de-Seine, Île-de-France housenumber 0.7092 Avenue Verdier POINT (2.30914 48.81622)

Enfin, à la question 3, on obtient cette carte (plus ou moins la même que précédemment):

Make this Notebook Trusted to load map: File -> Trust Notebook
Quelques API à connaître

Les principaux fournisseurs de données officielles proposent des API. C’est le cas notamment de l’Insee, d’Eurostat, de la BCE, de la FED, de la Banque Mondiale

Néanmoins, la production de données par les institutions étatiques est loin d’être restreinte aux producteurs de statistiques publiques. Le portail API gouv est le point de référencement principal pour les API produites par l’administration centrale française ou des administrations territoriales. De nombreuses villes publient également des données sur leurs infrastructures par le biais d’API, par exemple la ville de Paris.

Les producteurs de données privées proposent également des API. Par exemple, la SNCF ou la RATP proposent des API pour certains usages. Les grands acteurs du numérique, par exemple Spotify proposent généralement des API pour intégrer certains de leurs services à des applications externes.

Cependant, il faut être conscient des limites de certaines API. En premier lieu, les données partagées ne sont pas forcément très riches pour ne pas compromettre la confidentialité des informations partagées par les utilisateurs du service ou la part de marché du producteur qui n’a pas intérêt à vous partager ses données à forte valeur. Il faut également être conscient du fait qu’une API peut disparaître ou changer de structure du jour au lendemain. Les codes de restructuration de données étant assez adhérants à une structure d’API, on peut se retrouver à devoir changer un volume conséquent de code si une API critique change substantiellement.

3 Plus d’exemples de requêtes GET

3.1 Source principale

Nous allons utiliser comme base principale pour ce tutoriel la base permanente des équipements, un répertoire d’équipements publics accueillant du public.

On va commencer par récupérer les données qui nous intéressent. On ne récupère pas toutes les variables du fichier mais seulement celles qu’ils nous intéressent: quelques variables sur l’équipement, son adresse et sa commune d’appartement.

Nous allons nous restreindre aux établissements d’enseignement primaire, secondaire et supérieur du département de la Haute-Garonne (le département 31). Ces établissements sont identifiés par un code particulier, entre C1 et C5.

query = """
FROM read_parquet('https://minio.lab.sspcloud.fr/lgaliana/diffusion/BPE23.parquet')
SELECT NOMRS, NUMVOIE, INDREP, TYPVOIE, LIBVOIE,
       CADR, CODPOS, DEPCOM, DEP, TYPEQU,
       concat_ws(' ', NUMVOIE, INDREP, TYPVOIE, LIBVOIE) AS adresse, SIRET
WHERE DEP = '31'
      AND starts_with(TYPEQU, 'C')
      AND NOT (starts_with(TYPEQU, 'C6') OR starts_with(TYPEQU, 'C7'))
"""

import duckdb

bpe = duckdb.sql(query)
bpe = bpe.to_df()

3.2 Récupérer des données à façon grâce aux API

Nous avons vu précédemment le principe général d’une requête d’API. Pour illustrer, de manière plus massive, la récupération de données par le biais d’une API, essayons de récupérer des données complémentaires à notre source principale. Nous allons utiliser l’annuaire de l’éducation qui fournit de nombreuses informations sur les établissements scolaires. Nous utiliserons le SIRET pour croiser les deux sources de données.

L’exercice suivant viendra illustrer l’intérêt d’utiliser une API pour avoir des données à façon et la simplicité à récupéreer celles-ci via Python. Néanmoins, cet exercice illustrera également une des limites de certaines API, à savoir la volumétrie des données à récupérer.

Exercice 2
  1. Visiter le swagger de l’API de l’Annuaire de l’Education nationale sur api.gouv.fr/documentation et tester une première récupération de données en utilisant le endpoint records sans aucun paramètre.
  2. Puisqu’on n’a conservé que les données de la Haute Garonne dans notre base principale, on désire ne récupérer que les établissements de ce département par le biais de notre API. Faire une requête avec le paramètre ad hoc, sans en ajouter d’autres.
  3. Augmenter la limite du nombre de paramètres, voyez-vous le problème ?
  4. On va tenter de récupérer ces données par le biais de l’API tabular de data.gouv. Sa documentation est ici et l’identifiant de la ressource est b22f04bf-64a8-495d-b8bb-d84dbc4c7983 (source). Avec l’aide de la documentation, essayer de récupérer des données par le biais de cette API en utilisant le paramètre Code_departement__exact=031 pour ne garder que le département d’intérêt.
  5. Voyez-vous le problème et comment nous pourrions automatiser la récupération de données ?

La première question nous permet de récupérer un premier jeu de données

identifiant_de_l_etablissement nom_etablissement type_etablissement statut_public_prive adresse_1 adresse_2 adresse_3 code_postal code_commune nom_commune ... libelle_nature code_type_contrat_prive pial etablissement_mere type_rattachement_etablissement_mere code_circonscription code_zone_animation_pedagogique libelle_zone_animation_pedagogique code_bassin_formation libelle_bassin_formation
0 0470395Z Ecole primaire Ecole Public 8 impasse du chateau AU BOURG 47150 GAVAUDUN 47150 47109 Gavaudun ... ECOLE DE NIVEAU ELEMENTAIRE 99 0470026Y None None 0470786Z 047010 ZAP 047010 VILLENEUVE S/LOT FUMEL None None
1 0470423E Ecole élémentaire Ecole Public 138 route de Massels NOUVEAU BOURG 47140 AURADOU 47140 47017 Auradou ... ECOLE DE NIVEAU ELEMENTAIRE 99 0470031D None None 0470904C 047010 ZAP 047010 VILLENEUVE S/LOT FUMEL None None

2 rows × 72 columns

Néanmoins, on a deux problèmes: le nombre de lignes et le département d’intérêt. Essayons déjà avec la question 2 de changer ce dernier.

identifiant_de_l_etablissement nom_etablissement type_etablissement statut_public_prive adresse_1 adresse_2 adresse_3 code_postal code_commune nom_commune ... libelle_nature code_type_contrat_prive pial etablissement_mere type_rattachement_etablissement_mere code_circonscription code_zone_animation_pedagogique libelle_zone_animation_pedagogique code_bassin_formation libelle_bassin_formation
0 0310166M Ecole primaire publique Aérogare Ecole Public 4 place de Verdun None 31700 BLAGNAC 31700 31069 Blagnac ... ECOLE DE NIVEAU ELEMENTAIRE 99 0311581A None None 0312602K None None 16127 TOULOUSE NORD-OUEST
1 0310174W Ecole maternelle publique Ecole Public Rue des Ecoles None 31230 L ISLE EN DODON 31230 31239 L'Isle-en-Dodon ... ECOLE MATERNELLE 99 0310003K None None 0311108L None None 16106 COMMINGES
2 0310175X Ecole maternelle publique André Massat Ecole Public Rue Saint-Victor None 31310 MONTESQUIEU VOLVESTRE 31310 31375 Montesquieu-Volvestre ... ECOLE MATERNELLE 99 0310012V None None 0311109M None None 16107 MURET
3 0310182E Ecole maternelle publique la résidence Ecole Public Rue de la Résidence None 31800 ST GAUDENS 31800 31483 Saint-Gaudens ... ECOLE MATERNELLE 99 0310083X None None 0311108L None None 16106 COMMINGES
4 0310184G Ecole maternelle publique les caussades Ecole Public 15 rue des Caussades None 31800 ST GAUDENS 31800 31483 Saint-Gaudens ... ECOLE MATERNELLE 99 0310083X None None 0311108L None None 16106 COMMINGES

5 rows × 72 columns

C’est mieux mais nous avons toujours seulement 10 observations. Si on essayer d’ajuster le nombre de lignes (question 3), on obtient le retour suivant de l’API:

b'{\n  "error_code": "InvalidRESTParameterError",\n  "message": "Invalid value for limit API parameter: 200 was found but -1 <= limit <= 100 is expected."\n}'

Essayons avec des données plus exhaustives: le fichier brut sur data.gouv. Comme on peut le voir dans les métadonnées, on sait qu’on a plus de 1000 écoles dont on peut récupérer des données mais qu’on en a ici extrait seulement 20. Le champ next nous donne directement l’URL à utiliser pour récupérer les 20 pages suivantes: c’est grâce à lui qu’on a une chance de récupérer toutes nos données d’intérêt.

La partie intéressante pour automatiser la récupération de nos données est la clé links du JSON:

{'profile': 'https://tabular-api.data.gouv.fr/api/resources/b22f04bf-64a8-495d-b8bb-d84dbc4c7983/profile/',
 'swagger': 'https://tabular-api.data.gouv.fr/api/resources/b22f04bf-64a8-495d-b8bb-d84dbc4c7983/swagger/',
 'next': 'https://tabular-api.data.gouv.fr/api/resources/b22f04bf-64a8-495d-b8bb-d84dbc4c7983/data/?Code_departement__exact=031&page=2&page_size=20',
 'prev': None}

En bouclant sur celui-ci pour parcourir la liste des URL accessibles, on peut récupérer des données. Comme le code d’automatisation est assez fastidieux à écrire, le voici :

import requests
import pandas as pd

# Initialize the initial API URL
url_api_datagouv = "https://tabular-api.data.gouv.fr/api/resources/b22f04bf-64a8-495d-b8bb-d84dbc4c7983/data/?Code_departement__exact=031&page_size=50"

# Initialize an empty list to store all data entries
all_data = []

# Initialize the URL for pagination
current_url = url_api_datagouv

# Loop until there is no next page
while current_url:
    try:
        # Make a GET request to the current URL
        response = requests.get(current_url)
        response.raise_for_status()  # Raise an exception for HTTP errors

        # Parse the JSON response
        json_response = response.json()

        # Extract data and append to the all_data list
        page_data = json_response.get("data", [])
        all_data.extend(page_data)
        print(f"Fetched {len(page_data)} records from {current_url}")

        # Get the next page URL
        links = json_response.get("links", {})
        current_url = links.get("next")  # This will be None if there's no next page

    except requests.exceptions.RequestException as e:
        print(f"An error occurred: {e}")
        break

Le DataFrame obtenu est le suivant:

schools_dep31 = pd.DataFrame(all_data)
schools_dep31.head()
__id Identifiant_de_l_etablissement Nom_etablissement Type_etablissement Statut_public_prive Adresse_1 Adresse_2 Adresse_3 Code_postal Code_commune ... libelle_nature Code_type_contrat_prive PIAL etablissement_mere type_rattachement_etablissement_mere code_circonscription code_zone_animation_pedagogique libelle_zone_animation_pedagogique code_bassin_formation libelle_bassin_formation
0 3346 0310160F Ecole élémentaire d'application Ricardie 3 Ecole Public 54 avenue de l'URSS None 31400 TOULOUSE 31400 31555 ... ECOLE ELEMENTAIRE D APPLICATION 99 0311338L None None 0312825C None None 16109 TOULOUSE CENTRE
1 3347 0310161G Ecole primaire publique Calas - Dupont Ecole Public 101 grande rue Saint-Michel None 31400 TOULOUSE 31400 31555 ... ECOLE DE NIVEAU ELEMENTAIRE 99 0311338L None None 0312825C None None 16109 TOULOUSE CENTRE
2 3348 0310179B Ecole maternelle publique Jean Jaurès Ecole Public Allées des Tilleuls None 31120 PORTET SUR GARONNE 31120 31433 ... ECOLE MATERNELLE 99 0311093V None None 0311105H None None 16108 TOULOUSE SUD-OUEST
3 3349 0310181D Ecole maternelle publique Roger Sudre Ecole Public Rue Montpezat None 31250 REVEL 31250 31451 ... ECOLE MATERNELLE 99 0311690U None None 0311102E None None 16128 TOULOUSE EST
4 3350 0310184G Ecole maternelle publique les caussades Ecole Public 15 rue des Caussades None 31800 ST GAUDENS 31800 31483 ... ECOLE MATERNELLE 99 0310083X None None 0311108L None None 16106 COMMINGES

5 rows × 73 columns

On peut fusionner ces nouvelles données avec nos données précédentes pour enrichir celles-ci. Pour faire une production fiable, il faudrait faire attention aux écoles qui ne s’apparient pas mais ce n’est pas grave pour cette série d’exercices.

bpe_enriched = bpe.merge(schools_dep31, left_on="SIRET", right_on="SIREN_SIRET")

Cela nous donne des données enrichies de nouvelles caractéristiques sur les établissements. Il y a des coordonnées géographiques dans celles-ci mais nous allons faire comme s’il n’y en avait pas pour ré-utiliser notre API de géolocalisation.

4 Découverte des requêtes POST

4.1 Logique

Nous avons jusqu’à présent évoqué les requêtes GET. Nous allons maintenant présenter les requêtes POST qui permettent d’interagir de manière plus complexe avec des serveurs de l’API.

Pour découvrir celles-ci, nous allons reprendre l’API de géolocalisation précédente mais utiliser un autre point d’entrée qui nécessite une requête POST.

Ces dernières sont généralement utilisées quand il est nécessaire d’envoyer des données particulières pour déclencher une action. Par exemple, dans le monde du web, si vous avez une authentification à mettre en oeuvre, une requête POST permettra d’envoyer un token au serveur qui répondra en acceptant votre authentification.

Dans notre cas, nous allons envoyer des données au serveur, ce dernier va les recevoir, les utiliser pour la géolocalisation puis nous envoyer une réponse. Pour continuer sur la métaphore culinaire, c’est comme si vous donniez vous-mêmes à la cuisine un tupperware pour récupérer votre plat à emporter.

4.2 Principe

Prenons cette requête proposée sur le site de documentation de l’API de géolocalisation:

curl -X POST -F data=@path/to/file.csv -F columns=voie -F columns=ville -F citycode=ma_colonne_code_insee https://api-adresse.data.gouv.fr/search/csv/

Comme nous avons pu l’évoquer précédemment, curl est un outil en ligne de commande qui permet de faire des requêtes API. L’option -X POST indique, de manière assez transparente, qu’on désire faire une requête POST.

Les autres arguments sont passés par le biais des options -F. En l’occurrence, on envoie un fichier et on ajoute des paramètres pour aider le serveur à aller chercher la donnée dedans. L’@ indique que file.csv doit être lu sur le disque et envoyé dans le corps de la requête comme une donnée de formulaire.

4.3 Application avec Python

Nous avions requests.get, il est donc logique que nous ayons requests.post. Cette fois, il faudra passer des paramètres à notre requête sous la forme d’un dictionnaire dont les clés sont le nom de l’argument et les valeurs sont des objets Python.

Le principal défi, illustré dans le prochain exercice, est le passage de l’argument data: il faudra renvoyer le fichier comme un objet Python par le biais de la fonction open.

Exercice 3: une requête POST pour géolocaliser en masse nos données
  1. Enregistrer au format CSV les colonnes adresse, DEPCOM et Nom_commune de la base d’équipements fusionnée avec notre répertoire précédent (objet bpe_enriched). Il peut être utile, avant l’écriture au format CSV, de remplacer les virgules dans la colonne adresse par des espaces.
  2. Créer l’objet response avec requests.post et les bons arguments pour géocoder votre CSV.
  3. Transformer votre output en objet geopandas avec la commande suivante:
bpe_loc = pd.read_csv(io.StringIO(response.text))

Les géolocalisations obtenues prennent cette forme

index adresse DEPCOM Nom_commune result_score latitude longitude
0 0 LD LA BOURDETTE 31001 Agassac 0.367587 43.379868 0.874645
1 1 21 CHE DE L AUTAN 31003 Aigrefeuille 0.730293 43.567530 1.585745

En enrichissant les données précédentes, cela donne:

NOMRS NUMVOIE INDREP TYPVOIE LIBVOIE CADR CODPOS DEPCOM DEP TYPEQU ... etablissement_mere type_rattachement_etablissement_mere code_circonscription code_zone_animation_pedagogique libelle_zone_animation_pedagogique code_bassin_formation libelle_bassin_formation result_score latitude_ban longitude_ban
0 ECOLE PRIMAIRE PUBLIQUE DENIS LATAPIE LD LA BOURDETTE 31230 31001 31 C108 ... None None 0311108L None None 16106 COMMINGES 0.367587 43.379868 0.874645
1 ECOLE MATERNELLE PUBLIQUE 21 CHE DE L AUTAN 31280 31003 31 C107 ... None None 0311102E None None 16128 TOULOUSE EST 0.730293 43.567530 1.585745

2 rows × 88 columns

On peut vérifier que la géolocalisation ne soit pas trop délirante en comparant avec les longitudes et latitudes de l’annuaire de l’éducation ajouté précédemment:

NOMRS Nom_commune longitude_annuaire longitude_ban latitude_annuaire latitude_ban
786 ECOLE MATERNELLE PUBLIQUE SAUZELONG Toulouse 1.461930 1.462216 43.579014 43.579132
498 ECOLE ELEMENTAIRE PUBLIQUE JEAN MERMOZ Muret 1.333780 1.334034 43.467869 43.468277
62 ECOLE MATERNELLE PUBLIQUE LES MESANGES Beauzelle 1.377058 1.377226 43.662119 43.662234
624 ECOLE PRIMAIRE PUBLIQUE ANNE FRANK Sainte-Foy-d'Aigrefeuille 1.610310 1.603325 43.536311 43.554022
232 ECOLE MATERNELLE PUBLIQUE LEON BLUM Cugnaux 1.347270 1.346527 43.538188 43.537320

Sans rentrer dans le détail, les positions semblent très similaires à quelques imprécisions près.

Pour profiter de nos données enrichies, on peut faire une carte. Pour ajouter un peu de contexte à celle-ci, on peut mettre un fond de carte des communes en arrière plan. Celui-ci peut être récupéré avec cartiflette:

from cartiflette import carti_download

shp_communes = carti_download(
    crs=4326,
    values=["31"],
    borders="COMMUNE",
    vectorfile_format="topojson",
    filter_by="DEPARTEMENT",
    source="EXPRESS-COG-CARTO-TERRITOIRE",
    year=2022,
)
shp_communes.crs = 4326
This is an experimental version of cartiflette published on PyPi.
To use the latest stable version, you can install it directly from GitHub with the following command:
pip install git+https://github.com/inseeFrLab/cartiflette.git
ERROR 1: PROJ: proj_create_from_database: Open of /opt/conda/share/proj failed

Représentées sur une carte, cela donne la carte suivante:

Make this Notebook Trusted to load map: File -> Trust Notebook

5 Gestion des secrets et des exceptions

Nous avons déjà utilisé plusieurs API. Néanmoins ces dernières étaient toutes sans authentification et présentent peu de restrictions, hormis le nombre d’échos. Ce n’est pas le cas de toutes les API. Il est fréquent que les API qui permettent d’aspirer plus de données ou d’accéder à des données confidentielles nécessitent une authentification pour tracer les utilisateurs de données.

Cela se fait généralement par le biais d’un token. Ce dernier est une sorte de mot de passe, souvent utilisé dans les systèmes modernes d’authentification pour certifier de l’identité d’un utilisateur.trice (cf. chapitre Git).

Pour illustrer l’usage des tokens, nous allons utiliser une API de l’Insee. Les API développées par l’institut statistique français nécessitent en effet une authentification. Nous allons chercher des informations agrégées de l’Insee sur la population dans chaque commune. Cela nous donnera une estimation, très grossière, du bassin de population autour d’un établissement.

Avant d’en arriver là, nous allons faire un aparté sur la confidentialité des tokens et la manière d’éviter de révéler ceux-ci dans votre code.

5.1 Utiliser un token dans un code sans le révéler

Les tokens sont des informations personnelles qui ne doivent pas être partagées. Ils n’ont pas vocation à être présent dans le code. Comme ceci est évoqué à plusieurs reprises dans le cours de mise en production que Romain Avouac et moi donnons en 3e année, il est important de séparer le code des éléments de configuration

L’idée est de trouver une recette pour apporter les éléments de configuration avec le code mais sans mettre ceux-ci en clair dans le code. L’idée générale sera de stocker la valeur du token dans une variable mais ne jamais révéler celle-ci dans le code. Comment faire dès lors pour déclarer la valeur du jeton sans que celui-ci soit apparent dans le code ?

  • Pour un code amené à fonctionner de manière interactive (par exemple par le biais d’un notebook), il est possible de créer une boite de dialogue qui injectera la valeur renseignée dans une variable. Cela se fait par le biais du package getpass.
  • Pour le code qui tourne en non interactif, par exemple par le biais de la ligne de commande, l’approche par variable d’environnement est la plus fiable, à condition de faire attention à ne pas mettre le fichier de mot de passe dans Git.

L’exercice suivant permettra de mettre en oeuvre ces deux méthodes. Ces méthodes nous serviront à ajouter de manière confidentielle un payload à des requêtes d’authentification, c’est-à-dire des informations confidentielles identifiantes en complément d’une requête.

5.2 Application

Pour cette application, à partir de la question 4, nous allons avoir besoin de créer une classe spéciale permettant à requests de surcharger notre requête d’un jeton d’authentification. Comme elle n’est pas triviale à créer sans connaissance préalable, la voici:

class BearerAuth(requests.auth.AuthBase):
    def __init__(self, token):
        self.token = token

    def __call__(self, r):
        r.headers["authorization"] = "Bearer " + self.token
        return r

Nous allons aussi avoir besoin de cette variable qui correspond au Siren de Decathlon

siren = "500569405"
Exercice 4: ajouter un payload à une requête
  1. Se créer un compte pour l’API de l’INPI (Institut national de la protection intellectuelle) qui nous servira à récupérer des bilans des comptes sociaux d’entreprises au format PDF.
  2. Créer les variables username et password avec getpass en faisant en sorte de ne pas rentrer les valeurs dans le code.
  3. En utilisant la documentation de l’API, l’argument json de requests.post, récupérer un jeton d’authentification et le stocker dans une variable token.
  4. Récupérer les données en utilisant la f-string f'https://registre-national-entreprises.inpi.fr/api/companies/{siren}/attachments' et en donnant à requests l’argument auth=BearerAuth(token)
  5. Créer identifier = documents.get('bilans')[0]['id'] et utiliser requests avec l’URL f'https://registre-national-entreprises.inpi.fr/api/bilans/{identifier}/download', sans argument, pour récupérer un PDF. Cela a-t-il fonctionné ? Vérifier le status code. A quoi correspond-il ? Comment éviter cela ?
  6. En supposant que l’objet requests.get créé s’appelle r, écrire l’output de notre API dans un PDF de la manière suivante:
binary_file_path = "decathlon.pdf"
with open(binary_file_path, "wb") as f:
    f.write(r.content)
  1. Remplacer l’utilisation de getpass par l’approche variable d’environnement grâce à dotenv

A la question 5, sans identifiant, on récupère le code erreur 401, qui correspond à “Unauthorized”, c’est-à-dire à une requête refusée. Néanmoins, si on ajoute le token comme précédemment, tout se passe bien, on récupère le bilan de Decathlon.

Le PDF récupéré

Download PDF file.

Important

L’approche par variable d’environnement est la plus générale et malléable. Il faut néanmoins bien faire attention à ne pas oublier d’ajouter le .env stockant les identifiants dans Git. Autrement, vous risquez de révéler des informations identifiantes ce qui annule tout effet positif des bonnes pratiques mises en oeuvre avec dotenv.

Pour cela, la solution est simple : ajouter la ligne .env au .gitignore et par sécurité *.env au cas où le fichier ne soit pas à la racine du dépôt. Pour en savoir plus sur ce fichier .gitignore, se rendre sur les chapitres Git.

6 Ouverture aux API de modèles

Nous avons vu jusqu’à présent des API de données. Celles-ci permettent de récupérer du code. Ce n’est néanmoins pas la seule utilisation des API intéressantes pour les utilisateurs de Python.

Il existe de nombreux autres types d’API. Parmi celles-ci, les API de modèles sont intéressantes. Elles permettent de récupérer des modèles pré-entraînés voire effectuer une phase d’inférence sur des serveurs spécialisés ayant plus de ressources que son ordinateur local (plus d’éléments dans les parties machine learning et NLP). La librairie la plus connue dans ce domaine est la librairie transformers développée par HuggingFace.

L’un des objectifs du cours de 3A de mise en production est de montrer comment ce type d’architecture logicielle fonctionne et comment celle-ci peut être créée sur des modèles que vous auriez vous-mêmes créés.

7 Exercices supplémentaires

Exercice bonus

Dans notre exemple sur les écoles, se restreindre aux lycées et ajouter les informations sur la valeur ajoutée des lycées disponibles ici.

Informations additionnelles

environment files have been tested on.

Latest built version: 2025-01-15

Python version used:

'3.12.6 | packaged by conda-forge | (main, Sep 30 2024, 18:08:52) [GCC 13.3.0]'
Package Version
affine 2.4.0
aiobotocore 2.15.1
aiohappyeyeballs 2.4.3
aiohttp 3.10.8
aioitertools 0.12.0
aiosignal 1.3.1
alembic 1.13.3
altair 5.4.1
aniso8601 9.0.1
annotated-types 0.7.0
anyio 4.8.0
appdirs 1.4.4
archspec 0.2.3
asttokens 2.4.1
attrs 24.2.0
babel 2.16.0
bcrypt 4.2.0
beautifulsoup4 4.12.3
black 24.8.0
blinker 1.8.2
blis 0.7.11
bokeh 3.5.2
boltons 24.0.0
boto3 1.35.23
botocore 1.35.23
branca 0.7.2
Brotli 1.1.0
cachetools 5.5.0
cartiflette 0.0.2
Cartopy 0.24.1
catalogue 2.0.10
cattrs 24.1.2
certifi 2024.8.30
cffi 1.17.1
charset-normalizer 3.3.2
click 8.1.7
click-plugins 1.1.1
cligj 0.7.2
cloudpathlib 0.20.0
cloudpickle 3.0.0
colorama 0.4.6
comm 0.2.2
commonmark 0.9.1
conda 24.9.1
conda-libmamba-solver 24.7.0
conda-package-handling 2.3.0
conda_package_streaming 0.10.0
confection 0.1.5
contextily 1.6.2
contourpy 1.3.0
cryptography 43.0.1
cycler 0.12.1
cymem 2.0.10
cytoolz 1.0.0
dask 2024.9.1
dask-expr 1.1.15
databricks-sdk 0.33.0
dataclasses-json 0.6.7
debugpy 1.8.6
decorator 5.1.1
Deprecated 1.2.14
diskcache 5.6.3
distributed 2024.9.1
distro 1.9.0
docker 7.1.0
duckdb 0.10.1
en-core-web-sm 3.7.1
entrypoints 0.4
et_xmlfile 2.0.0
exceptiongroup 1.2.2
executing 2.1.0
fastexcel 0.11.6
fastjsonschema 2.21.1
fiona 1.10.1
Flask 3.0.3
folium 0.17.0
fontawesomefree 6.6.0
fonttools 4.54.1
fr-core-news-sm 3.7.0
frozendict 2.4.4
frozenlist 1.4.1
fsspec 2023.12.2
geographiclib 2.0
geopandas 1.0.1
geoplot 0.5.1
geopy 2.4.1
gitdb 4.0.11
GitPython 3.1.43
google-auth 2.35.0
graphene 3.3
graphql-core 3.2.4
graphql-relay 3.2.0
graphviz 0.20.3
great-tables 0.12.0
greenlet 3.1.1
gunicorn 22.0.0
h11 0.14.0
h2 4.1.0
hpack 4.0.0
htmltools 0.6.0
httpcore 1.0.7
httpx 0.28.1
httpx-sse 0.4.0
hyperframe 6.0.1
idna 3.10
imageio 2.36.1
importlib_metadata 8.5.0
importlib_resources 6.4.5
inflate64 1.0.1
ipykernel 6.29.5
ipython 8.28.0
itsdangerous 2.2.0
jedi 0.19.1
Jinja2 3.1.4
jmespath 1.0.1
joblib 1.4.2
jsonpatch 1.33
jsonpointer 3.0.0
jsonschema 4.23.0
jsonschema-specifications 2024.10.1
jupyter-cache 1.0.0
jupyter_client 8.6.3
jupyter_core 5.7.2
kaleido 0.2.1
kiwisolver 1.4.7
langchain 0.3.14
langchain-community 0.3.9
langchain-core 0.3.29
langchain-text-splitters 0.3.5
langcodes 3.5.0
langsmith 0.1.147
language_data 1.3.0
lazy_loader 0.4
libmambapy 1.5.9
locket 1.0.0
loguru 0.7.3
lxml 5.3.0
lz4 4.3.3
Mako 1.3.5
mamba 1.5.9
mapclassify 2.8.1
marisa-trie 1.2.1
Markdown 3.6
markdown-it-py 3.0.0
MarkupSafe 2.1.5
marshmallow 3.25.1
matplotlib 3.9.2
matplotlib-inline 0.1.7
mdurl 0.1.2
menuinst 2.1.2
mercantile 1.2.1
mizani 0.11.4
mlflow 2.16.2
mlflow-skinny 2.16.2
msgpack 1.1.0
multidict 6.1.0
multivolumefile 0.2.3
munkres 1.1.4
murmurhash 1.0.11
mypy-extensions 1.0.0
narwhals 1.22.0
nbclient 0.10.0
nbformat 5.10.4
nest_asyncio 1.6.0
networkx 3.3
nltk 3.9.1
numpy 1.26.4
opencv-python-headless 4.10.0.84
openpyxl 3.1.5
opentelemetry-api 1.16.0
opentelemetry-sdk 1.16.0
opentelemetry-semantic-conventions 0.37b0
orjson 3.10.14
OWSLib 0.28.1
packaging 24.1
pandas 2.2.3
paramiko 3.5.0
parso 0.8.4
partd 1.4.2
pathspec 0.12.1
patsy 0.5.6
Pebble 5.1.0
pexpect 4.9.0
pickleshare 0.7.5
pillow 10.4.0
pip 24.2
platformdirs 4.3.6
plotly 5.24.1
plotnine 0.13.6
pluggy 1.5.0
polars 1.8.2
preshed 3.0.9
prometheus_client 0.21.0
prometheus_flask_exporter 0.23.1
prompt_toolkit 3.0.48
protobuf 4.25.3
psutil 6.0.0
ptyprocess 0.7.0
pure_eval 0.2.3
py7zr 0.20.8
pyarrow 17.0.0
pyarrow-hotfix 0.6
pyasn1 0.6.1
pyasn1_modules 0.4.1
pybcj 1.0.3
pycosat 0.6.6
pycparser 2.22
pycryptodomex 3.21.0
pydantic 2.10.5
pydantic_core 2.27.2
pydantic-settings 2.7.1
Pygments 2.18.0
PyNaCl 1.5.0
pynsee 0.1.8
pyogrio 0.10.0
pyOpenSSL 24.2.1
pyparsing 3.1.4
pyppmd 1.1.1
pyproj 3.7.0
pyshp 2.3.1
PySocks 1.7.1
python-dateutil 2.9.0
python-dotenv 1.0.1
python-magic 0.4.27
pytz 2024.1
pyu2f 0.1.5
pywaffle 1.1.1
PyYAML 6.0.2
pyzmq 26.2.0
pyzstd 0.16.2
querystring_parser 1.2.4
rasterio 1.4.3
referencing 0.35.1
regex 2024.9.11
requests 2.32.3
requests-cache 1.2.1
requests-toolbelt 1.0.0
retrying 1.3.4
rich 13.9.4
rpds-py 0.22.3
rsa 4.9
Rtree 1.3.0
ruamel.yaml 0.18.6
ruamel.yaml.clib 0.2.8
s3fs 2023.12.2
s3transfer 0.10.2
scikit-image 0.24.0
scikit-learn 1.5.2
scipy 1.13.0
seaborn 0.13.2
setuptools 74.1.2
shapely 2.0.6
shellingham 1.5.4
six 1.16.0
smart-open 7.1.0
smmap 5.0.0
sniffio 1.3.1
sortedcontainers 2.4.0
soupsieve 2.5
spacy 3.7.5
spacy-legacy 3.0.12
spacy-loggers 1.0.5
SQLAlchemy 2.0.35
sqlparse 0.5.1
srsly 2.5.0
stack-data 0.6.2
statsmodels 0.14.4
tabulate 0.9.0
tblib 3.0.0
tenacity 9.0.0
texttable 1.7.0
thinc 8.2.5
threadpoolctl 3.5.0
tifffile 2025.1.10
toolz 1.0.0
topojson 1.9
tornado 6.4.1
tqdm 4.66.5
traitlets 5.14.3
truststore 0.9.2
typer 0.15.1
typing_extensions 4.12.2
typing-inspect 0.9.0
tzdata 2024.2
Unidecode 1.3.8
url-normalize 1.4.3
urllib3 1.26.20
wasabi 1.1.3
wcwidth 0.2.13
weasel 0.4.1
webdriver-manager 4.0.2
websocket-client 1.8.0
Werkzeug 3.0.4
wheel 0.44.0
wordcloud 1.9.3
wrapt 1.16.0
xgboost 2.1.1
xlrd 2.0.1
xyzservices 2024.9.0
yarl 1.13.1
yellowbrick 1.5
zict 3.0.0
zipp 3.20.2
zstandard 0.23.0

View file history

SHA Date Author Description
3c22d3a 2025-01-15 11:40:33 lgaliana SIREN Decathlon
e182c9a 2025-01-13 23:03:24 Lino Galiana Finalisation nouvelle version chapitre API (#586)
f992df0 2025-01-06 16:50:39 lgaliana Code pour import BPE
dc4a475 2025-01-03 17:17:34 Lino Galiana Révision de la partie API (#584)
6c6dfe5 2024-12-20 13:40:33 lgaliana eval false for API chapter
e56a219 2024-10-30 17:13:03 Lino Galiana Intro partie modélisation & typo geopandas (#571)
9d8e69c 2024-10-21 17:10:03 lgaliana update badges shortcode for all manipulation part
47a0770 2024-08-23 07:51:58 linogaliana fix API notebook
1953609 2024-08-12 16:18:19 linogaliana One button is enough
783a278 2024-08-12 11:07:18 Lino Galiana Traduction API (#538)
580cba7 2024-08-07 18:59:35 Lino Galiana Multilingual version as quarto profile (#533)
101465f 2024-08-07 13:56:35 Lino Galiana regex, webscraping and API chapters in 🇬🇧 (#532)
065b0ab 2024-07-08 11:19:43 Lino Galiana Nouveaux callout dans la partie manipulation (#513)
06d003a 2024-04-23 10:09:22 Lino Galiana Continue la restructuration des sous-parties (#492)
8c316d0 2024-04-05 19:00:59 Lino Galiana Fix cartiflette deprecated snippets (#487)
005d89b 2023-12-20 17:23:04 Lino Galiana Finalise l’affichage des statistiques Git (#478)
3fba612 2023-12-17 18:16:42 Lino Galiana Remove some badges from python (#476)
a06a268 2023-11-23 18:23:28 Antoine Palazzolo 2ème relectures chapitres ML (#457)
b68369d 2023-11-18 18:21:13 Lino Galiana Reprise du chapitre sur la classification (#455)
889a71b 2023-11-10 11:40:51 Antoine Palazzolo Modification TP 3 (#443)
04ce567 2023-10-23 19:04:01 Lino Galiana Mise en forme chapitre API (#442)
3eb0aeb 2023-10-23 11:59:24 Thomas Faria Relecture jusqu’aux API (#439)
a771183 2023-10-09 11:27:45 Antoine Palazzolo Relecture TD2 par Antoine (#418)
a63319a 2023-10-04 15:29:04 Lino Galiana Correction du TP numpy (#419)
154f09e 2023-09-26 14:59:11 Antoine Palazzolo Des typos corrigées par Antoine (#411)
3bdf3b0 2023-08-25 11:23:02 Lino Galiana Simplification de la structure 🤓 (#393)
130ed71 2023-07-18 19:37:11 Lino Galiana Restructure les titres (#374)
f0c583c 2023-07-07 14:12:22 Lino Galiana Images viz (#371)
ef28fef 2023-07-07 08:14:42 Lino Galiana Listing pour la première partie (#369)
f21a24d 2023-07-02 10:58:15 Lino Galiana Pipeline Quarto & Pages 🚀 (#365)
62aeec1 2023-06-10 17:40:39 Lino Galiana Avertissement sur la partie API (#358)
38693f6 2023-04-19 17:22:36 Lino Galiana Rebuild visualisation part (#357)
3248633 2023-02-18 13:11:52 Lino Galiana Shortcode rawhtml (#354)
3c880d5 2022-12-27 17:34:59 Lino Galiana Chapitre regex + Change les boites dans plusieurs chapitres (#339)
f5f0f9c 2022-11-02 19:19:07 Lino Galiana Relecture début partie modélisation KA (#318)
2dc82e7 2022-10-18 22:46:47 Lino Galiana Relec Kim (visualisation + API) (#302)
f10815b 2022-08-25 16:00:03 Lino Galiana Notebooks should now look more beautiful (#260)
494a85a 2022-08-05 14:49:56 Lino Galiana Images featured ✨ (#252)
d201e3c 2022-08-03 15:50:34 Lino Galiana Pimp la homepage ✨ (#249)
1239e3e 2022-06-21 14:05:15 Lino Galiana Enonces (#239)
bb38643 2022-06-08 16:59:40 Lino Galiana Répare bug leaflet (#234)
5698e30 2022-06-03 18:28:37 Lino Galiana Finalise widget (#232)
7b9f27b 2022-06-03 17:05:15 Lino Galiana Essaie régler les problèmes widgets JS (#231)
1ca1a8a 2022-05-31 11:44:23 Lino Galiana Retour du chapitre API (#228)
Retour au sommet

Citation

BibTeX
@book{galiana2023,
  author = {Galiana, Lino},
  title = {Python pour la data science},
  date = {2023},
  url = {https://pythonds.linogaliana.fr/},
  doi = {10.5281/zenodo.8229676},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
Galiana, Lino. 2023. Python pour la data science. https://doi.org/10.5281/zenodo.8229676.