Analyse de statistique tradionnelle

Dans cet exercice, nous allons étudier la base de données que nous réutiliserons dans les exercices suivants. Pour cela, nous pratiquerons une analyse statistique traditionelle, ne faisant pas appel à des outils de machine learning. Le but de l’exercice sera donc avant tout d’acquérir des connaissances sur les données.

Notre jeu de données

Pour cet exercice, nous allons utiliser un jeu de données structuré. En effet, ces données peuvent être représentées sous la forme d’un tableau de données. Dans les chapitres précédents, nous avons vu utilisé la librairie pandas et la classe DataFrame pour stocker ce type de données.

Les données sont stockées dans fichier CSV et nous pouvons utiliser spécifiquement la fonction read_csv pour charger les données.

import pandas as pd

location_velo = pd.read_csv("../datasets/bike_sharing/hour.csv")

Une bonne pratique avant d’effectuer une analyse statistique est de comprendre quelles sont les variables stockées. Pour cela, nous allons utiliser la method .info() de la classe DataFrame.

location_velo.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 17379 entries, 0 to 17378
Data columns (total 17 columns):
 #   Column                               Non-Null Count  Dtype  
---  ------                               --------------  -----  
 0   ID                                   17379 non-null  int64  
 1   date                                 17379 non-null  object 
 2   saison                               17379 non-null  int64  
 3   annee                                17379 non-null  int64  
 4   mois                                 17379 non-null  int64  
 5   heure                                17379 non-null  int64  
 6   vacances                             17379 non-null  int64  
 7   jour de la semaine                   17379 non-null  int64  
 8   jour travaille                       17379 non-null  int64  
 9   meteo                                17379 non-null  int64  
 10  temperature                          17379 non-null  float64
 11  temperature ressentie                17379 non-null  float64
 12  humidite                             17379 non-null  float64
 13  vitesse du vent                      17379 non-null  float64
 14  nombre de locations sans abonnement  17379 non-null  int64  
 15  nombre de locations avec abonnement  17379 non-null  int64  
 16  nombre de locations                  17379 non-null  int64  
dtypes: float64(4), int64(12), object(1)
memory usage: 2.3+ MB

Cette méthode est particulièrement utile pour comprendre :

  • le noms des variables stockées dans le fichier CSV

  • le type de données de chaque variable inféré par pandas

  • la présence ou non de valeurs manquantes pour chaque variable

Comme précédemment présenté, pandas généralement stocke les chaînes de caractères en utilisant le type obj. Pour le jeu de données que nous utilisons, la colonne date est stockée de cette manière. En revanche, il serait mieux de donner à pandas cette information au moment de la lecture pour qu’il puisse caster les chaînes de caractères en dates.

Nous allons donc utiliser l’option parse_date de la fonction read_csv pour convertir la colonne date.

location_velo = pd.read_csv("../datasets/bike_sharing/hour.csv", parse_dates=["date"])
location_velo.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 17379 entries, 0 to 17378
Data columns (total 17 columns):
 #   Column                               Non-Null Count  Dtype         
---  ------                               --------------  -----         
 0   ID                                   17379 non-null  int64         
 1   date                                 17379 non-null  datetime64[ns]
 2   saison                               17379 non-null  int64         
 3   annee                                17379 non-null  int64         
 4   mois                                 17379 non-null  int64         
 5   heure                                17379 non-null  int64         
 6   vacances                             17379 non-null  int64         
 7   jour de la semaine                   17379 non-null  int64         
 8   jour travaille                       17379 non-null  int64         
 9   meteo                                17379 non-null  int64         
 10  temperature                          17379 non-null  float64       
 11  temperature ressentie                17379 non-null  float64       
 12  humidite                             17379 non-null  float64       
 13  vitesse du vent                      17379 non-null  float64       
 14  nombre de locations sans abonnement  17379 non-null  int64         
 15  nombre de locations avec abonnement  17379 non-null  int64         
 16  nombre de locations                  17379 non-null  int64         
dtypes: datetime64[ns](1), float64(4), int64(12)
memory usage: 2.3 MB

Nous pouvons donc observer que la conversion en date a bien été effectuée. Il semblerait également que la colonne ID ne soit pas pertinente. Nous pourrions nous attendre à ce quelle contienne des entiers uniques. Nous allons le vérifier en utilisant la méthode .nunique().

location_velo["ID"].nunique() == len(location_velo)
True

Il est donc possible d’utiliser pour le moment cette colonne comme l’index de notre dataframe.

location_velo = location_velo.set_index("ID")

Maintenant, il est temps de regarder les données. Nous allons afficher les cinq premières lignes de notre jeu de données.

location_velo.head()
date saison annee mois heure vacances jour de la semaine jour travaille meteo temperature temperature ressentie humidite vitesse du vent nombre de locations sans abonnement nombre de locations avec abonnement nombre de locations
ID
1 2011-01-01 1 0 1 0 0 6 0 1 0.24 0.2879 0.81 0.0 3 13 16
2 2011-01-01 1 0 1 1 0 6 0 1 0.22 0.2727 0.80 0.0 8 32 40
3 2011-01-01 1 0 1 2 0 6 0 1 0.22 0.2727 0.80 0.0 5 27 32
4 2011-01-01 1 0 1 3 0 6 0 1 0.24 0.2879 0.75 0.0 3 10 13
5 2011-01-01 1 0 1 4 0 6 0 1 0.24 0.2879 0.75 0.0 0 1 1

Il apparaît que les données associées à la notion de date sont présentes dans plusieurs colonnes : date, annee, mois. De plus, des informations concernant l’heure (colonne heure), la saison (colonne saison), le jour de la semaine (colonne jour de la semaine) et le type de jour (colonnes jour travaille et vacances) sont également présentes.

Il sera donc important de décider quelles variables à inclure lorsque nous définirons le type de problème à résoudre. Nous devons également noter que la majorité de ces données sont encodées de manière ordinale. Ceci est particulièrement important et doit être pris en compte lorsque nous choisirons notre modèle prédictif de machine learning.

Ensuite, nous pouvons observer des variables liées au condition météorologique : temperature, temperature ressentie, humidite et vitesse du vent. Nous pouvons donc présentir que certaines de ces variables pourraient être corrélées.

Finalement, nous observons trois variables liées aux nombres de locations de vélos : nombre de locations, nombre de locations sans abonnement et nombre de locations avec abonnement.

Il advient que le problème que nous allons résoudre soit de prédire le nombre de locations de vélos pour une heure donnée. Nous pouvons d’ores et déjà exclure l’information liée aux abonnements et se focaliser seulement à la prédiction du nombre de locations globale.

Il est donc temps de sélectionner les variables à inclure dans notre analyse statistique. Pour le moment nous pouvons exclure les variables suivantes : nombre de location sans abonnement et nombre de locations avec abonnement.

variable_a_exclure = [
    "nombre de locations sans abonnement",
    "nombre de locations avec abonnement",
]
location_velo = location_velo.drop(columns=variable_a_exclure)

Analyse des données d’intérêt

Analyse des variables numériques

Nous allons maintenant analyser les données que nous venons de sélectionner en utilisant des statistiques de base.

Une première analyse assez simple consiste à regarder des statistiques tels que la moyenne, la variance, la quantile, la distribution des valeurs, etc. Cependant, ce type d’analyse n’est utile que pour obtenir des informations sur les variables de type numérique.

Il est important de différencier les variables encodées de manière ordinale et donc te type numérique des variables numériques “naturelles”. Concrètement, les informations liées à la date dans notre jeu de données appartiennent à la première catégorie. Calculer par exemple la moyenne ou la variance sur ce type de variable n’est pas pertinent. En revanche, calculer la moyenne ou la variance sur des variables naturellement numériques tels que la vitesse du vent ou la température ressentie seront pertinent.

Nous pouvons donc définir les variables de type numérique que nous voulons analyser dans un premier temps.

variable_numerique = [
    "temperature",
    "temperature ressentie",
    "humidite",
    "vitesse du vent",
    "nombre de locations",
]

Nous pouvons maintenant utiliser la méthode .describe() sur notre dataframe afin de calculer les statistiques de base.

location_velo[variable_numerique].describe()
temperature temperature ressentie humidite vitesse du vent nombre de locations
count 17379.000000 17379.000000 17379.000000 17379.000000 17379.000000
mean 0.496987 0.475775 0.627229 0.190098 189.463088
std 0.192556 0.171850 0.192930 0.122340 181.387599
min 0.020000 0.000000 0.000000 0.000000 1.000000
25% 0.340000 0.333300 0.480000 0.104500 40.000000
50% 0.500000 0.484800 0.630000 0.194000 142.000000
75% 0.660000 0.621200 0.780000 0.253700 281.000000
max 1.000000 1.000000 1.000000 0.850700 977.000000

Nous pouvons observer que toutes ces variables sont comprises entre 0 et 1. L’ordre de grandeur des moyennes et des écart-types est équivalent pour toutes ces variables, mis à part la variable nombre de locations ce qui n’est pas un problème puisque cette variable devra être prédite. Elles ne sont donc pas exprimées dans leurs unités naturelles. Cela nous indique également que nous n’aurons pas forcément besoin de normaliser (même si celà ne serait pas une mauvaise chose si nous utilisons un modèle lineaire ou qui utilise des distances Euclidienne) les données en amont dans la partie de prétraitement de notre modèle prédictif.

En plus d’extraire ces informations, nous pouvons observer visuellement la distribution empirique des différentes variables. Pour cela, nous pouvons utiliser la méthode .plot.hist() de pandas.

# Afin de changer le style de nos graphiques, nous pouvons utiliser seaborn
# context.
import seaborn as sns

sns.set_context("poster")
import matplotlib.pyplot as plt

for variable in variable_numerique:
    _, ax = plt.subplots()
    location_velo[variable].plot.hist(ax=ax)
    ax.set_xlabel(f"{variable}")
    _ = ax.set_title(f"Distribution empirique de \n'{variable}'")
../_images/file_00_20_0.png ../_images/file_00_20_1.png ../_images/file_00_20_2.png ../_images/file_00_20_3.png ../_images/file_00_20_4.png

En observant les graphiques, nous pouvons apprécier le type de distribution empirique que nous obtenons. D’ailleurs, il est intéressant de noter que ces distributions ne sont pas de type Gaussienne ce qui pourrait également avoir un impact sur le design de notre modèle prédictif.

Nous pouvons aller un peu plus loin dans notre analyse statistique en observant le lien marginal entre chacune de ces variables.

La librairie seaborn nous permet de visualiser les liens marginales entre deux variables. Pour cela, nous pouvons utiliser la méthode .pairplot()

import numpy as np

# Nous pouvons utiliser un sous échantillonnage pour accelerer la visualisation
rng = np.random.RandomState(42)
index_sous_echantillons = rng.choice(
    location_velo.index,
    size=1_000,
    replace=False,
)

_ = sns.pairplot(
    location_velo.loc[index_sous_echantillons, variable_numerique],
    kind="kde",
    diag_kind="kde",
    height=4,
)
../_images/file_00_22_01.png

Dans ce graphique, la diagonale correspond à la distribution empirique et est donc équivalente aux histogrammes que nous avons vus précédemment.

Nous sommes particulièrement intéressés par la dernière colonne de notre graphique qui correspond au nombre de locations. Une corrélation entre cet variable et tout autre variable pourrait nous permettre de savoir qu’un modèle prédictif fonctionnera. En revanche, cela nous indiquerait également que de simple statisque suffirait pour nous prédire cette variable.

Nous pouvons observer que la distribution de la variable nombre de locations n’est pas Gaussienne et à une longue queue. Cela nous indique qu’il serait intéressant de prétraiter cette variable ou d’utiliser un modèle prédictif qui ne suppose pas que la variable à prédire soit Gaussienne.

En ce qui concerne les liens marginales avec les autres variables, nous noter un effet de seuil : les valeurs sont observées au dessus ou en dessous de la diagonale suivant si les variables sont postivement ou négativement corrélées.

En observant les autres graphiques, nous pouvons constater que les variables temperature et temperature ressentie sont corrélées.

Au lieu de visualiser les relations en utilisant la méthode KDE (kernel density estimation), nous pouvons représenter graphiquement tous les points.

_ = sns.pairplot(
    location_velo[variable_numerique],
    kind="scatter",
    diag_kind="kde",
    height=4,
)
../_images/file_00_24_0.png

À partir de cette représentation, nous pouvons observer des nuages de points assez curieux. Dans le graphique liant temperature et temperature ressentie, nous pouvons observer une ligne horizontale (ou verticale suivant la relation considérée). Il semblerait que la température ressentie reste constante alors que la température augmente.

Nous pouvons observer des phénomènes identiques pour d’autres variables : lorsque la vitesse du vent ou l’humidité sont nul. Ces échantillons pourraient peut-être correspondre à des données bruitées.

Finalement, il pourrait être intéressant d’observer si il existe une relation entre le nombre de locations et un groupe à l’intérieur d’une des variables. Nous pouvons “discretizer” la variable nombre de locations en cinq groupes définis par des quantiles. La function qcut() de pandas nous permet de discretiser une variable.

location_velo_discrete = location_velo.copy()
location_velo_discrete["nombre de locations"] = pd.qcut(
    location_velo_discrete["nombre de locations"],
    q=5,
)

Nous pouvons maintenant encoder la valeur de la variable nombre de locations avec une couleur.

_ = sns.pairplot(
    location_velo_discrete[variable_numerique],
    hue="nombre de locations",
    kind="scatter",
    diag_kind="kde",
    height=4,
)
../_images/file_00_28_0.png

Sur ce graphique, il est difficile d’observer une tendance quand nous étudions trois variables à la fois (l’axe x et y de chaque graphique représentent deux variables alors que la couleur représente une variable additionelle). En revanche, nous pouvons nous attarder sur les graphiques de la diagonale.

Pour les graphiques de température, nous pouvons observer que les catégories des deux plus faibles nombres de locations sont dissociées des deux catégories des deux plus grand nombres de locations.

Nous pouvons noter une tendance similaire pour les deux catégories représentant les deux plus petits nombres de locations pour la variable humidite.

En revanche, il semble que nous ne pouvons pas observer un impact de la vitesse du vent sur le nombre de locations.

Analyse des variables liées à la date et l’heure

Maintenant, nous allons nous intéresser aux variables liées au temps. Rappelons dans un premier temps les variables que nous avons dans notre jeu de données.

location_velo.head()
date saison annee mois heure vacances jour de la semaine jour travaille meteo temperature temperature ressentie humidite vitesse du vent nombre de locations
ID
1 2011-01-01 1 0 1 0 0 6 0 1 0.24 0.2879 0.81 0.0 16
2 2011-01-01 1 0 1 1 0 6 0 1 0.22 0.2727 0.80 0.0 40
3 2011-01-01 1 0 1 2 0 6 0 1 0.22 0.2727 0.80 0.0 32
4 2011-01-01 1 0 1 3 0 6 0 1 0.24 0.2879 0.75 0.0 13
5 2011-01-01 1 0 1 4 0 6 0 1 0.24 0.2879 0.75 0.0 1

Une première analyse qui sera intéressante sera de combiner la variable date et heure pour avoir une variable de type datetime ou nous pourrons analyser un eventuelle relation entre ces périodes et le nombre de locations.

location_velo["date"] = pd.to_datetime(
    {
        "year": location_velo["date"].dt.year,
        "month": location_velo["date"].dt.month,
        "day": location_velo["date"].dt.day,
        "hour": location_velo["heure"],
    }
)
_, ax = plt.subplots(figsize=(6, 4))
location_velo.set_index("date").resample("D")["nombre de locations"].mean().plot(ax=ax)
_ = ax.set_ylabel("Nombre de locations")
../_images/file_00_33_0.png

La première observation à noter sur ce graphique est que le nombre de locations sur l’année 2012 est le plus élevé que pour l’année 2011. Ceci est un élement à prendre en compte pour notre modèle prédictif. Entraîner un modèle prédictif sur l’année 2011 pour prédire pour l’année 2012 résultera à une erreur due à cette tendance.

Maintenant, nous pouvons nous attarder sur les variables catégorielles encodées à partir de la date. Une représentation graphique assez simple de ces variables peut se faire en utilisant des boîtes à moustaches. seaborn fournit une méthode boxplot() à cet égard.

Nous allons donc représenter chacune des variables catégorielles encodée. En revanche, il est nécessaire de déclarer que ces variables sont des catégories en utilisant la méthode astype("category").

variable_date = [
    "saison",
    "mois",
    "heure",
    "jour de la semaine",
    "jour travaille",
]
location_velo[variable_date] = location_velo[variable_date].astype("category")
for variable in variable_date:
    height_axis = location_velo[variable].nunique() * 0.5
    _, ax = plt.subplots(figsize=(6, height_axis))
    sns.boxplot(
        y=variable,
        x="nombre de locations",
        data=location_velo,
        color="white",
        ax=ax,
    )
    sns.stripplot(
        y=variable,
        x="nombre de locations",
        data=location_velo,
        alpha=0.01,
        color="black",
        ax=ax,
    )
../_images/file_00_37_0.png ../_images/file_00_37_1.png ../_images/file_00_37_2.png ../_images/file_00_37_3.png ../_images/file_00_37_4.png

Nous pouvons observer une tendance similaire pour les variables mois et saison. Ceci est logique vu que les saisons sont une aggrégation des mois de l’année.

La variable permettant de discerner une différence dans le nombre de locations est la variable heure. Nous pouvons observer une augmentation du nombre de locations à 8 heures et entre 17 et 18 heures. Il semblerait que des personnes utilisent le vélo comme moyen de déplacement pour le travail.

Conclusion

Nous avons effectué une analyse statistique descriptive de notre jeu de données. En revanche, il est extrêmement important de noter que nous les intuitions que nous avons construites sont seulement des relations marginales entre deux variables. En d’autres termes, nous avons sélectionné deux variables et ignoré les autres pour construire ses intuitions. Ce type d’analyse est différent des interprétations que nous pourrons obtenir en inspectant nos modèles prédictifs. En effet, un les interprétations seront de type conditionelle puisque l’interprétation de l’effet d’une variable donnée sera faite en fonction de la valeur de toutes les autres variables.

Dans les examples suivants, nous utiliserons des méthodes d’apprentissage automatique pour traiter ce même jeu de données.