import requests
import pandas
import seaborn as sns
import matplotlib.pyplot as plt
import solutions
Projet 2 - Prédictions météorologiques
Contexte du projet
Il y a certains jours où l’on serait bien resté en télétravail.. Parmi ceux-là, ces jours à la fois humides et venteux où il est impossible de maintenir une coiffure décente, malgré tous ses efforts. Pourrait-on utiliser Python
pour prédire ce que les anglo-saxons nomment des bad hair day (“mauvais jour de cheveux”) ?
L’objectif du projet est de construire un bad hair index (“indice de mauvais jour de cheveux”) à partir des données météorologiques et de représenter graphiquement l’évolution de cette indice afin de déterminer à l’avance les jours où l’on ferait mieux de rester bien au chaud. Afin d’obtenir les données adéquates, nous allons requêter des APIs.
Une API (Interface de Programmation d’Application) est un ensemble de règles et de spécifications que les applications suivent pour communiquer entre elles. Elle permet à votre code d’accéder à des fonctionnalités externes ou à des données, comme celles de bases de données météorologiques ou de services de localisation. Lorsqu’on parle de requêtage d’une API, cela se fait généralement via le protocole HTTP, qui est le même protocole utilisé pour charger des pages web. Dans ce tutoriel, nous utiliserons le package requests, qui simplifie le processus de requêtage et de gestion de réponses HTTP.
Les APIs que nous allons utiliser sont :
- Nominatim : une API de géocodage proposée par OpenStreetMap qui nous permet de convertir un nom de lieu en coordonnées géographiques.
- Open-Meteo Weather Forecast : une API qui fournit des prévisions météorologiques détaillées.
Commençons par importer les packages dont nous aurons besoin au cours de ce projet.
Partie 1 : récupération des coordonnées géographiques pour une localisation donnée
L’API de prédiction d’open-meteo prend en entrée les coordonnées géographiques (latitude, longitude) du lieu où seront réalisées les prédictions. On pourrait récupérer manuellement les coordonnées du lieu qui nous intéresse, mais cela limiterait la reproductibilité de nos analyses avec d’autres lieux que celui choisi. On va donc utiliser une seconde API, Nominatim
, pour obtenir ces coordonnées pour un lieu donné.
Lorsque l’on travaille à partir d’une API, la première étape est toujours de lire sa documentation. C’est elle qui indique à quelle adresse nous devons envoyer nos requêtes, sous quel format, et ce que va nous répondre l’API. Dans notre cas, la docuemntation de Nominatim
se trouve à cette adresse. N’hésitez pas à la parcourir rapidement pour évaluer les possibilités de l’API.
Question 1
La première caractéristique essentielle d’une API est le endpoint, c’est à dire l’URL à laquelle on va envoyer des requêtes. Dans notre cas, on va utiliser le endpoint /search
dans la mesure où l’on veut trouver un objet géographique (des coordonnées) à partir d’un nom de localisation. La page de documentation associée à ce endpoint nous donne toutes les informations dont nous avons besoin :
- le format d’une requête est
https://nominatim.openstreetmap.org/search?<params>
où<params>
doit être remplacé par les paramètres de la requête, séparés par le symbole&
- dans la section Structured Query, on voit que l’API admet comme paramètres
country
(pays) etcity
(ville), que l’on va utiliser pour paramétrer notre requête.
Définissez une fonction build_request_nominatim
qui construit le lien de la requête pour un pays et une ville donnée.
Résultat attendu
= solutions.build_request_nominatim("France", "Montrouge")
url_request_nominatim url_request_nominatim
À vous de jouer !
def build_request_nominatim(country, city):
# Votre code ici
return url_request
# Vérification du résultat
= build_request_nominatim("France", "Montrouge")
url_request_nominatim url_request_nominatim
Question 2
La prochaine étape est d’envoyer notre requête paramétrisée à l’API. Pour la tester au préalable, on peut simplement mettre l’adresse dans un navigateur et voir ce que nous renvoie l’API. Si les résultats ont l’air cohérent, on peut continuer. Si l’API nous renvoie un code d’erreur, il y a sûrement une erreur à trouver dans la requête.
Pour effectuer cette requête à partir de Python
afin d’en récupérer les résultats, on utilise la fonction requests.get()
à laquelle on fournit comme seul paramètre l’URL de la requête. On obtient en retour un objet “réponse”, dont on peut extraire le contenu JSON
sous forme d’un dictionnaire Python
en lui appliquant la méthode .json()
. Il faut alors parcourir le dictionnaire pour en extraire les informations pertinentes ; dans notre cas : la latitude et la longitude.
Définissez une fonction get_lat_long
qui récupère la latitude et la longitude (centrale) pour un pays et une ville donnée.
Résultat attendu
long = solutions.get_lat_long(query=url_request_nominatim)
lat, print(lat, long)
print(type(lat))
print(type(long))
À vous de jouer !
def get_lat_long(query):
# Votre code ici
return latitude, longitude
# Vérification du résultat
long = get_lat_long(query=url_request_nominatim)
lat, print(lat, long)
print(type(lat))
print(type(long))
Partie 2 : récupération des prévisions météorologiques
Maintenant que nous pouvons récupérer les coordonnées associées à une localisation donnée, nous pouvons requêter l’API open-meteo.com
pour obtenir les données de prédiction météo associées à ces coordonnées. Là encore, la première étape est de s’intéresser à la documentation (page d’accueil, doc), qui nous fournit plusieurs informations :
- le endpoint pour l’API de prédiction est
https://api.open-meteo.com/v1/forecast
- l’API attend en entrée une
latitude
et unelatitude
, ainsi que les variables météorologiques souhaitées. Pour notre problématique, nous allons récupérer des informations sur le taux d’humidité (relativehumidity_2m
) et la vitesse du vent (windspeed_10m
) - par défaut, l’API renvoie des prédictions à 7 jours
Question 3
Sachant toutes ces informations et en vous aidant de la documentation, définissez une fonction build_request_open_meteo
qui construit le lien de la requête pour une latitude et une longitude donnée. Là encore, il est possible de tester la validité de la requête en exécutant le lien dans un navigateur et en vérifiant que les résultats retournés paraissent cohérents.
Résultat attendu
= solutions.build_request_open_meteo(latitude=lat, longitude=long)
url_request_open_meteo url_request_open_meteo
À vous de jouer !
def build_request_open_meteo(latitude, longitude):
# Votre code ici
return url_request
# Vérification du résultat
= build_request_open_meteo(latitude=lat, longitude=long)
url_request_open_meteo url_request_open_meteo
Question 4
A nouveau, on utilise la fonction requests.get()
pour soumettre la requête à l’API. On obtient en retour un objet “réponse”, dont on peut extraire le contenu JSON
sous forme d’un dictionnaire Python
en lui appliquant la méthode .json()
.
Mais que se passe-t-il dans le cas où la requête soumise est invalide (faute de frappe, paramètres inexistants, etc.) ? Dans ce cas, l’API nous renvoie une erreur. L’objet réponse de la requête contient un attribut .status_code
qui donne le code de réponse d’une requête. Le code 200
indique la réussite d’une requête ; tout autre code indique une erreur.
Définissez une fonction get_meteo_data
qui récupère le dictionnaire complet de données retourné par l’API suite à notre requête. Le comportement de la fonction doit cependant dépendre du code de réponse de la requête :
- si le code vaut
200
, la fonction renvoie le dictionnaire des prédictions ; - si le code est différent de
200
, la fonction affiche le code d’erreur et renvoieNone
.
Résultat attendu
= solutions.get_meteo_data(url_request_open_meteo)
predictions type(predictions)
= solutions.build_request_open_meteo(latitude=lat, longitude="dix-sept-virgule-quatre")
wrong_request = solutions.get_meteo_data(wrong_request)
output print(output)
À vous de jouer !
def get_meteo_data(query):
# Votre code ici
return response.json()
# Vérification du résultat
= get_meteo_data(url_request_open_meteo)
predictions type(predictions)
# Vérification du résultat
= build_request_open_meteo(latitude=lat, longitude="dix-sept-virgule-quatre")
wrong_request = get_meteo_data(wrong_request)
output print(output)
Question 5
Afin de bien comprendre la structure des données que nous avons récupérées, explorez le dictionnaire des prédictions retourné par l’API (clefs, différents niveaux, format des prédictions, format de la variable indiquant les dates/heures des prédictions, etc.)
Afficher le code
# Exploration des données
print(type(predictions))
print(predictions.keys())
print(type(predictions["hourly"]))
print(predictions["hourly"].keys())
print(type(predictions["hourly"]["time"]))
print()
# Afficher les données
print(predictions['hourly']["time"][:5])
print(predictions['hourly']["time"][-5:])
print()
print(predictions['hourly']["relativehumidity_2m"][:5])
print(predictions['hourly']["windspeed_10m"][:5])
Partie 3 : construction et visualisation d’un bad hair index
L’objectif de cette dernière partie est de calculer et représenter graphiquement le bad hair index. Rappelons que l’on définit cet indice comme le produit de l’humidité relative et de la vitesse du vent. Il s’agit d’une mesure ludique de la probabilité d’avoir une “mauvaise coiffure” en raison des conditions météorologiques.
Question 6
Définissez une fonction preprocess_predictions
qui met en forme les prédictions issues de l’API sous forme d’un DataFrame Pandas
en vue d’une analyse statistique. Les étapes à implémenter sont les suivantes :
- convertir les données prédites en un
DataFrame Pandas
à 3 colonnes (date et heure de l’observation, humidité, vitesse du vent) ; - convertir la colonne de temps au format
datetime
(documentation) - ajouter deux nouvelles variables indiquant le jour de l’observation et l’heure de l’observation
- ajouter une variable qui calcule le bad hair index
Résultat attendu
= solutions.preprocess_predictions(predictions)
df_preds df_preds.head()
À vous de jouer !
def preprocess_predictions(predictions):
# Votre code ici
return df
# Vérification du résultat
= preprocess_predictions(predictions)
df_preds df_preds.head()
Question 7
A des fins de représentation graphique, nous allons représenter le bad hair index agrégé à deux niveaux :
- moyenne heure par heure. Cela permettra de répondre à la question : “à quel heure sera-t-il généralement préférable de rester à la maison la semaine prochaine ?”
- moyenne jour par jour. Cela permettra de répondre à la question : “quel jour sera-t-il généralement préférable de rester à la maison la semaine prochaine ?”
Définissez une fonction plot_agg_avg_bhi
qui calcule l’indice agrégé dans chaque cas, et représente le résultat sous la forme d’un lineplot
.
Résultat attendu
="day") solutions.plot_agg_avg_bhi(df_preds, agg_var
="hour") solutions.plot_agg_avg_bhi(df_preds, agg_var
À vous de jouer !
def plot_agg_avg_bhi(df_preds, agg_var="day"):
# Votre code ici
return None
# Vérification du résultat
="day") plot_agg_avg_bhi(df_preds, agg_var
# Vérification du résultat
="hour") plot_agg_avg_bhi(df_preds, agg_var
Qu’en concluez-vous pour la semaine à venir ?
Question 8
Notre outil de prévision des bad hair days fonctionne à merveille. Mais c’est bientôt les vacances, et un voyage à Berlin est prévu. Idéalement, on voudrait pouvoir utiliser notre outil pour n’importe quelle localité. Heureusement, on a défini à chaque étape des fonctions, ce qui va nous permettre de passer facilement à une fonction “chef d’orchestre” qui appelle toutes les autres pour une localité donnée.
Définissez une fonction main
qui représente le bad hair index pour un pays, une ville et un niveau d’agrégation donnés.
Résultat attendu
="Germany", city="Berlin", agg_var="day") solutions.main(country
="Germany", city="Berlin", agg_var="hour") solutions.main(country
À vous de jouer !
def main(country, city, agg_var="day"):
# Votre code ici
return None
# Vérification du résultat
="Germany", city="Berlin", agg_var="day") main(country
# Vérification du résultat
="Germany", city="Berlin", agg_var="hour") main(country