Prédire la qualité de l’air est un sujet délicat et important. Il est possible avec des modèles de Machine Learning de prédire avec précision cette qualité. Notons que produire une prévision de bonne qualité c’est difficile. Cela demande beaucoup d’expérience et certaines compétences très spécifiques. Certes, il existe des outils de prévision mais qui sont assez rigides, notamment pour intégrer certaines hypothèses utiles.

Pour ces raisons, Facebook a créé Prophet, un outil de prévision disponible en Python et R. Cet outil permet aux experts et aux non-experts de produire de bonnes prévisions avec un minimum d’efforts.

Dans cet article, nous allons utiliser Prophet pour nous aider à prédire la qualité de l’air!

Vous trouverez le notebook complet en cliquant ici et le dataset est à télécharger ici.

Commençons à faire quelques prédictions !

 

Importer les bibliothèques

Comme d’habitude, nous commençons par importer les bibliothèques utiles:

import warnings
warnings.filterwarnings('ignore')

import numpy as np
import pandas as pd
from scipy import stats
import statsmodels.api as sm
import matplotlib.pyplot as plt

%matplotlib inline

Importer les données

Nous allons importer le dataset et le visualiser:

DATAPATH = 'data/AirQualityUCI.csv'

data = pd.read_csv(DATAPATH, sep=';')
data.head()

 

On obtient ce résultat:

 DateTimeCO(GT)PT08.S1(CO)NMHC(GT)C6H6(GT)PT08.S2(NMHC)NOx(GT)PT08.S3(NOx)NO2(GT)PT08.S4(NO2)PT08.S5(O3)TRHAHUnnamed: 15Unnamed: 16
010/03/200418.00.002,61360.0150.011,91046.0166.01056.0113.01692.01268.013,648,90,7578NaNNaN
110/03/200419.00.0021292.0112.09,4955.0103.01174.092.01559.0972.013,347,70,7255NaNNaN
210/03/200420.00.002,21402.088.09,0939.0131.01140.0114.01555.01074.011,954,00,7502NaNNaN
310/03/200421.00.002,21376.080.09,2948.0172.01092.0122.01584.01203.011,060,00,7867NaNNaN
410/03/200422.00.001,61272.051.06,5836.0131.01205.0116.01490.01110.011,259,60,7888NaNNaN
5 premières entrées du dataset

 

Comme vous pouvez le constater, le dataset contient des informations sur les concentrations de différents gaz. Ils ont été enregistrés à chaque heure pour chaque jour. Vous pouvez trouver une description de toutes les colonnes ici.

Si vous explorez un peu plus le dataset, vous remarquerez qu’il existe de nombreuses instances de la valeur -200. Bien entendu, il n’est pas logique d’avoir une concentration négative, nous aurons donc besoin de nettoyer les données avant de faire notre modélisation.

 

Nettoyer les données

Tout d’abord, nous nous débarrassons de toutes les instances où il existe une valeur vide:

data.dropna(axis=1, how='all', inplace=True)
data.dropna(axis=0, how='all', inplace=True)

 

Après, nous devons formater la colonne Date en tant que date et transformer toutes les mesures en float:

# Formater les dates
data['Date'] = pd.to_datetime(data['Date'])

# Convertir les mesures en float
for col in data.iloc[:,2:].columns:
    if data[col].dtypes == object:
        data[col] = data[col].str.replace(',', '.').astype('float')

 

Ensuite, nous agrégeons les données par jour en prenant la moyenne de chaque mesure:

def positive_average(num):
    return num[num > -200].mean()
    
daily_data = data.drop('Time', axis=1).groupby('Date').apply(positive_average)

daily_data.head()

 

À ce stade, les données devraient ressembler à ceci:

 CO(GT)PT08.S1(CO)NMHC(GT)C6H6(GT)PT08.S2(NMHC)NOx(GT)PT08.S3(NOx)NO2(GT)PT08.S4(NO2)PT08.S5(O3)TRHAH
Date
2004-01-042.5500001244.380952310.12511.9714291010.428571161.304348880.66666796.7391301644.3809521155.85714315.51904851.1333330.854881
2004-01-052.0565221097.500000275.0008.662500913.708333104.739130918.08333384.9565221640.416667904.62500020.15000052.1833331.167312
2004-01-062.1000001135.583333NaN12.3750001021.875000152.043478896.79166775.8695651881.5000001066.95833320.32500066.1541671.533350
2004-01-072.1625001130.583333NaN12.2250001038.541667139.695652740.916667113.4347831854.2500001059.62500030.45000039.6916671.624108
2004-01-080.983333974.166667NaN5.808333792.58333351.739130880.08333358.5217391559.000000670.58333330.65416742.1208331.673521

 

Nous avons encore quelques NaN dont nous devons nous débarrasser. Nous pouvons voir combien de NaN sont présents dans chaque colonne avec cette commande:

daily_data.isna().sum()

 

Supprimons les colonnes contenant plus de 8 NaN:

daily_data = daily_data.iloc[:,(daily_data.isna().sum() <= 8).values]
daily_data = daily_data.dropna()

 

Parfait! Maintenant, nous devrions regrouper les données par semaine, car cela donnera une tendance plus lisse à analyser.

weekly_data = daily_data.resample('W').mean()
weekly_data = weekly_data.dropna()

Top! Nous sommes maintenant prêts à explorer ces données.

 

Analyse des données

Traçons chaque colonne du dataset:

def plot_data(col):
    plt.figure(figsize=(17, 8))
    plt.plot(weekly_data[col])
    plt.xlabel('Time')
    plt.ylabel(col)
    plt.grid(False)
    plt.show()
    
for col in weekly_data.columns:
    plot_data(col)

 

Prenez le temps de regarder chaque graphique et d’identifier les tendances intéressantes. Par souci de longueur, nous ne prendrons que la concentration de NOx.

Les oxydes d’azote sont très nocifs, car ils réagissent en formant de la brume et des pluies acides. Ils sont également responsables de la formation de particules fines et de l’ozone troposphérique. Celles-ci ont des effets néfastes sur la santé, la concentration de NOx est donc un élément clé de la qualité de l’air.

Par conséquent, supprimons toutes les colonnes non pertinentes avant de passer à la modélisation:

cols_to_drop = ['PT08.S1(CO)', 'C6H6(GT)', 'PT08.S2(NMHC)', 'PT08.S4(NO2)', 'PT08.S5(O3)', 'T', 'RH', 'AH']

weekly_data = weekly_data.drop(cols_to_drop, axis=1)

weekly_data.head()
 PT08.S3(NOx)
Date
2004-01-04880.666667
2004-01-11760.484990
2004-01-181490.333333
2004-02-08869.108333
2004-02-15706.395833

 

Modélisation

On commence par installer Prophet si ce n’est pas déjà fait.

Vous pouvez suivre ce lien: installation Prophet. Ou si vous êtes sur anaconda: conda install gcc puis conda install -c conda-forge fbprophet

On continue en important la bibliothèque Prophet:

from fbprophet import Prophet
import logging

logging.getLogger().setLevel(logging.ERROR)

 

Ensuite, Prophet exige que la colonne Date soit nommée ds et que la colonne des résultats soit nommée y:

df = weekly_data.reset_index()
df.columns = ['ds', 'y']
df.head()

 

Maintenant, nos données devraient ressembler à ça:

 dsy
02004-01-04880.666667
12004-01-11760.484990
22004-01-181490.333333
32004-02-08869.108333
42004-02-15706.395833

 

Ensuite, nous définissons un dataset de training. Pour cela, nous utiliserons les 30 dernières entrées à des fins de prédiction et de validation.

prediction_size = 30
train_df = df[:-prediction_size]

 

Ensuite, nous initialisons simplement Prophet, ajustons le modèle aux données et commençons à faire des prédictions!

m = Prophet()
m.fit(train_df)

future = m.make_future_dataframe(periods=prediction_size)

forecast = m.predict(future)

forecast.head()

 

Et vous devriez voir ce qui suit:

 dstrendyhat_loweryhat_uppertrend_lowertrend_upperadditive_termsadditive_terms_loweradditive_terms_uppermultiplicative_termsmultiplicative_terms_lowermultiplicative_terms_upperyhat
02004-01-04946.024230785.6490151106.908423946.024230946.0242300.00.00.00.00.00.0946.024230
12004-01-11943.202988781.8338711102.770070943.202988943.2029880.00.00.00.00.00.0943.202988
22004-01-18940.381746788.2163581105.017763940.381746940.3817460.00.00.00.00.00.0940.381746
32004-02-08931.918021773.9984501102.028775931.918021931.9180210.00.00.00.00.00.0931.918021
42004-02-15929.096780767.4108091096.729984929.096780929.0967800.00.00.00.00.00.0929.096780

Nickel! Ici, yhat représente la prédiction, tandis que yhat_lower et yhat_upper représentent respectivement la limite inférieure et supérieure de cette prédiction.

Prophet vous permet de tracer facilement les prévisions:

m.plot(forecast)

 

On obtient ceci:

Prévision de concentration de NOx

Prévision de concentration de NOx

 

Comme vous pouvez le constater, Prophet a simplement utilisé une ligne droite pour prédire la concentration future de NOx.

Vous pouvez également utiliser une commande pour voir si la série chronologique a des caractéristiques intéressantes, telles que la saisonnalité:

m.plot_components(forecast)

 

Et on obtient:

Graphique saisonnalité

Ici, Prophet a seulement identifié une tendance à la baisse sans aucune saisonnalité.

Maintenant, évaluons les performances du modèle en calculant son erreur moyenne en pourcentage absolu (Mean Absolute Percentage Error – MAPE) et son erreur moyenne absolue (Mean Absolute Error – MAE):

# Définit une fonction qui crée un DataFrame contenant les prédictions et valeurs actuelles
def make_comparison_dataframe(historical, forecast):
    return forecast.set_index('ds')[['yhat', 'yhat_lower', 'yhat_upper']].join(historical.set_index('ds'))

cmp_df = make_comparison_dataframe(df, forecast)

# Définit une fonction qui calcule MAPE et MAE
def calculate_forecast_errors(df, prediction_size):
    
    df = df.copy()
    
    df['e'] = df['y'] - df['yhat']
    df['p'] = 100 * df['e'] / df['y']
    
    predicted_part = df[-prediction_size:]
    
    error_mean = lambda error_name: np.mean(np.abs(predicted_part[error_name]))
    
    return {'MAPE': error_mean('p'), 'MAE': error_mean('e')}

# Affiche MAPE et MAE
for err_name, err_value in calculate_forecast_errors(cmp_df, prediction_size).items():
    print(err_name, err_value)

 

Et vous devriez voir que le MAPE est à 13,86% et le MAE à 109,32, ce qui n’est pas si mal! N’oubliez pas que nous n’avons pas du tout ajusté le modèle.

Enfin, décrivons la prévision avec ses limites supérieure et inférieure:

plt.figure(figsize=(17, 8))
plt.plot(cmp_df['yhat'])
plt.plot(cmp_df['yhat_lower'])
plt.plot(cmp_df['yhat_upper'])
plt.plot(cmp_df['y'])
plt.xlabel('Time')
plt.ylabel('Average Weekly NOx Concentration')
plt.grid(False)
plt.show()

 

Finalement, on obtient:

prévision de la concentration hebdomadaire moyenne de NOx

Prévision de la concentration hebdomadaire moyenne de NOx

 

Voilà, vous avez appris à utiliser Prophet pour la prévision de séries chronologiques. Notez que Prophet ne convient pas à toutes les situations. Veillez donc à lire cela avant d’utiliser Prophet.

N’hésitez pas si vous avez des questions !