# Pandas et les séries temporelles
Dans le domaine de la Data Science, et plus particulièrement du *Forecasting* (prévision), les données temporelles sont omniprésentes. Qu'il s'agisse de cours boursiers, de ventes de produits, de données météorologiques ou de consommation électrique, la dimension temporelle est souvent la clé pour comprendre les phénomènes et prédire leur évolution future.
Vous avez déjà exploré l'univers du *Data Mining avec Python* et manipulé des `DataFrame` Pandas pour structurer et analyser des données tabulaires. Ce chapitre approfondit l'utilisation de Pandas spécifiquement pour les **séries temporelles**, ces séquences de données indexées par le temps. Pandas offre des outils puissants et optimisés pour la manipulation, l'analyse et la transformation de ce type de données, rendant les tâches complexes étonnamment simples.
Nous allons découvrir comment :
* Représenter des dates et des durées avec le module `datetime` de Python.
* Construire des indices temporels robustes avec `DatetimeIndex` de Pandas.
* Effectuer des opérations spécifiques aux séries temporelles : sélection par période, rééchantillonnage, calcul de moyennes mobiles, gestion des données manquantes.
Maîtriser ces concepts est une étape fondamentale pour tout projet de prévision ou d'analyse temporelle.
# Les objets `datetime` de Python
Avant de plonger dans Pandas, il est essentiel de comprendre les objets `datetime` natifs de Python, car Pandas s'appuie sur eux pour ses fonctionnalités temporelles. Le module standard `datetime` fournit des classes pour manipuler des dates et des heures.
## Le module `datetime`
Le module `datetime` contient plusieurs classes clés :
* `date`: Représente une date (année, mois, jour).
* `time`: Représente une heure (heure, minute, seconde, microseconde).
* `datetime`: Représente une date et une heure combinées. C'est la classe la plus couramment utilisée.
* `timedelta`: Représente une durée, la différence entre deux objets `date`, `time`, ou `datetime`.
* `timezone`: Représente un fuseau horaire.
## Création d'objets `datetime` et `timedelta`
```python
import datetime
# Création d'une date
d = datetime.date(2023, 10, 26)
print(f"Date: {d}")
# Création d'une heure
t = datetime.time(14, 30, 15)
print(f"Heure: {t}")
# Création d d'un datetime (date et heure)
dt = datetime.datetime(2023, 10, 26, 14, 30, 15)
print(f"Datetime: {dt}")
# Datetime actuel
now = datetime.datetime.now()
print(f"Maintenant: {now}")
# Création d'un timedelta (durée)
delta = datetime.timedelta(days=7, hours=3)
print(f"Timedelta: {delta}")
# Opérations avec timedelta
future_dt = now + delta
past_dt = now - delta
print(f"Dans une semaine et 3h: {future_dt}")
print(f"Il y a une semaine et 3h: {past_dt}")
# Différence entre deux datetimes
diff = future_dt - now
print(f"Différence: {diff}")
print(f"Type de la différence: {type(diff)}")
```
> [!example] Création et manipulation de `datetime`
> ```python
> import datetime
>
> # Créer un datetime pour le 1er janvier 2024 à 9h00
> date_debut = datetime.datetime(2024, 1, 1, 9, 0, 0)
> print(f"Date de début : {date_debut}")
>
> # Ajouter 10 jours et 2 heures
> duree = datetime.timedelta(days=10, hours=2)
> date_fin = date_debut + duree
> print(f"Date de fin (après ajout) : {date_fin}")
>
> # Calculer la différence entre deux dates
> autre_date = datetime.datetime(2024, 1, 5, 10, 0, 0)
> difference = date_fin - autre_date
> print(f"Différence entre date_fin et autre_date : {difference}")
> print(f"Nombre de jours dans la différence : {difference.days}")
> ```
## Formatage et parsing de dates
Il est souvent nécessaire de convertir des objets `datetime` en chaînes de caractères (pour l'affichage ou l'enregistrement) et vice-versa (pour charger des données).
* `strftime()` (string format time): Convertit un `datetime` en chaîne selon un format spécifié.
* `strptime()` (string parse time): Convertit une chaîne en `datetime` selon un format spécifié.
```python
# Formatage (datetime -> string)
formatted_dt = dt.strftime("%Y-%m-%d %H:%M:%S")
print(f"Datetime formatée: {formatted_dt}")
# Parsing (string -> datetime)
date_str = "2023-12-25 10:30:00"
parsed_dt = datetime.datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
print(f"Chaîne parsée en datetime: {parsed_dt}")
```
> [!note] Codes de formatage courants
> Voici quelques codes de formatage couramment utilisés avec `strftime` et `strptime` :
> - `%Y`: Année sur 4 chiffres (ex: 2023)
> - `%m`: Mois sur 2 chiffres (01-12)
> - `%d`: Jour du mois sur 2 chiffres (01-31)
> - `%H`: Heure (horloge de 24 heures) sur 2 chiffres (00-23)
> - `%M`: Minute sur 2 chiffres (00-59)
> - `%S`: Seconde sur 2 chiffres (00-59)
> - `%f`: Microseconde sur 6 chiffres (000000-999999)
> - `%a`: Nom abrégé du jour de la semaine (ex: Mon)
> - `%A`: Nom complet du jour de la semaine (ex: Monday)
> - `%b`: Nom abrégé du mois (ex: Oct)
> - `%B`: Nom complet du mois (ex: October)
# Pandas et les indices temporels (`DatetimeIndex`)
Pandas étend les capacités du module `datetime` de Python en introduisant des structures de données optimisées pour les séries temporelles, dont la plus fondamentale est le `DatetimeIndex`.
## Qu'est-ce qu'un `DatetimeIndex` ?
Un `DatetimeIndex` est un index spécialisé pour les `Series` ou `DataFrame` Pandas, où chaque élément de l'index est un objet `datetime`. Il offre plusieurs avantages majeurs :
1. **Performances optimisées** : Les opérations sur les dates sont beaucoup plus rapides.
2. **Fonctionnalités spécifiques** : Permet des opérations d'indexation par période, de rééchantillonnage, de décalage temporel, etc.
3. **Alignement automatique** : Lors de l'association de séries temporelles différentes, Pandas utilise l'index temporel pour aligner correctement les données.
4. **Gestion des fréquences** : Il peut stocker des informations sur la fréquence des données (journalière, mensuelle, horaire, etc.), ce qui est crucial pour de nombreuses opérations.
## Création d'un `DatetimeIndex`
Il existe plusieurs façons de créer un `DatetimeIndex`.
### À partir d'une liste de dates/chaînes
Vous pouvez construire un `DatetimeIndex` à partir d'une liste d'objets `datetime` ou même de chaînes de caractères que Pandas peut interpréter comme des dates.
```python
import pandas as pd
import datetime
# À partir d'objets datetime
dates_dt = [datetime.datetime(2023, 1, 1),
datetime.datetime(2023, 1, 2),
datetime.datetime(2023, 1, 3)]
index_dt = pd.DatetimeIndex(dates_dt)
print(f"Index à partir de datetime:\n{index_dt}\n")
# À partir de chaînes de caractères (Pandas les parse automatiquement)
dates_str = ['2023-01-04', '2023-01-05', '2023-01-06']
index_str = pd.DatetimeIndex(dates_str)
print(f"Index à partir de chaînes:\n{index_str}\n")
```
### Utilisation de `pd.to_datetime()`
C'est la méthode la plus flexible pour convertir une colonne ou une série de dates en un format `datetime`. Elle peut gérer une grande variété de formats de chaînes et est très robuste.
```python
# Conversion d'une Series de chaînes en DatetimeIndex
date_series = pd.Series(['2023-07-01', '2023/07/02', '03-07-2023'])
datetime_series = pd.to_datetime(date_series)
print(f"Series convertie en datetime:\n{datetime_series}\n")
print(f"Type de la Series: {type(datetime_series.iloc[0])}\n")
# Conversion d'une liste de chaînes avec un format spécifique
dates_custom_format = ['01/01/2023', '02/01/2023']
datetime_custom = pd.to_datetime(dates_custom_format, format='%d/%m/%Y')
print(f"Dates avec format spécifique:\n{datetime_custom}\n")
# Gestion des erreurs (errors='coerce')
# Les dates non parsables sont converties en NaT (Not a Time)
invalid_dates = ['2023-01-01', 'date_invalide', '2023-01-03']
parsed_invalid = pd.to_datetime(invalid_dates, errors='coerce')
print(f"Dates avec erreurs gérées:\n{parsed_invalid}\n")
```
> [!tip] Utilisation de `pd.to_datetime()`
> Pour des performances optimales lors de la conversion d'une colonne de `DataFrame` en `datetime`, utilisez `pd.to_datetime()` sur la colonne entière plutôt que d'itérer sur les lignes. Si votre colonne contient des chaînes de caractères non uniformes, `pd.to_datetime()` est souvent capable de les interpréter correctement. Utilisez `errors='coerce'` pour transformer les valeurs non parsables en `NaT` (Not a Time) au lieu de lever une erreur.
### Utilisation de `pd.date_range()`
`pd.date_range()` est extrêmement utile pour générer des séquences de dates régulières. C'est idéal pour créer des indices temporels pour des données simulées ou pour s'assurer d'avoir un index complet sur une période donnée.
```python
# Générer un index journalier pour 10 jours à partir du 1er janvier 2023
daily_index = pd.date_range(start='2023-01-01', periods=10, freq='D')
print(f"Index journalier:\n{daily_index}\n")
# Générer un index mensuel pour 6 mois
monthly_index = pd.date_range(start='2023-01-01', periods=6, freq='M')
print(f"Index mensuel:\n{monthly_index}\n")
# Générer un index horaire pour 24 heures
hourly_index = pd.date_range(start='2023-10-26 00:00', periods=24, freq='H')
print(f"Index horaire:\n{hourly_index}\n")
```
> [!example] Création de `DatetimeIndex`
> ```python
> import pandas as pd
>
> # Créer un DatetimeIndex pour les 5 premiers jours de mars 2023
> index_jours = pd.date_range(start='2023-03-01', periods=5, freq='D')
> print(f"Index journalier :\n{index_jours}\n")
>
> # Créer un DatetimeIndex pour chaque début de trimestre de l'année 2022
> index_trimestres = pd.date_range(start='2022-01-01', end='2022-12-31', freq='QS') # QS = Quarter Start
> print(f"Index trimestriel :\n{index_trimestres}\n")
> ```
> Vous pouvez trouver une liste complète des alias de fréquence dans la documentation de Pandas (voir section 4).
# Manipulation des séries temporelles avec Pandas
Une fois que vos données sont indexées par un `DatetimeIndex`, Pandas débloque un ensemble puissant d'outils pour leur manipulation.
## Création de `Series` et `DataFrame` temporels
```python
import numpy as np
# Création d'une Series temporelle
dates = pd.date_range(start='2023-01-01', periods=10, freq='D')
data = np.random.randn(10).cumsum() # Données cumulatives pour simuler une série
ts = pd.Series(data, index=dates)
print(f"Série temporelle:\n{ts}\n")
# Création d'un DataFrame temporel
df_data = {
'Valeur_A': np.random.randint(10, 100, 10),
'Valeur_B': np.random.rand(10) * 10
}
df_ts = pd.DataFrame(df_data, index=dates)
print(f"DataFrame temporel:\n{df_ts}\n")
```
## Indexation et sélection par date
L'un des avantages majeurs du `DatetimeIndex` est la facilité d'indexation et de sélection de données par période.
```python
# Sélectionner une date spécifique
print(f"Valeur du 2023-01-05:\n{ts['2023-01-05']}\n")
# Sélectionner une plage de dates
print(f"Valeurs du 2023-01-03 au 2023-01-07:\n{ts['2023-01-03':'2023-01-07']}\n")
# Sélectionner un mois entier (même si l'index est journalier)
# Pandas est intelligent et comprend que '2023-01' signifie tout le mois de janvier 2023
print(f"Valeurs pour janvier 2023:\n{ts['2023-01']}\n")
# Sélectionner une année entière
# Si l'index couvrait plusieurs années, ts['2023'] sélectionnerait toutes les données de 2023.
```
## Accès aux attributs temporels
Chaque élément du `DatetimeIndex` possède des attributs qui permettent d'accéder facilement à ses composants (année, mois, jour, heure, etc.).
```python
# Accéder aux attributs de l'index
print(f"Années de l'index: {ts.index.year}\n")
print(f"Mois de l'index: {ts.index.month}\n")
print(f"Jours de la semaine (0=Lundi, 6=Dimanche): {ts.index.dayofweek}\n")
print(f"Est-ce le début du mois? {ts.index.is_month_start}\n")
# Ajouter des colonnes dérivées d'un index temporel à un DataFrame
df_ts['Annee'] = df_ts.index.year
df_ts['Mois'] = df_ts.index.month
df_ts['Jour_Semaine'] = df_ts.index.dayofweek
print(f"DataFrame avec attributs temporels:\n{df_ts}\n")
```
> [!example] Accès aux attributs temporels
> ```python
> import pandas as pd
>
> # Créer une série temporelle avec des données horaires sur plusieurs jours
> index_hourly = pd.date_range(start='2023-10-26 00:00', periods=48, freq='H')
> data_hourly = np.random.rand(48)
> ts_hourly = pd.Series(data_hourly, index=index_hourly)
>
> # Afficher les heures
> print(f"Heures : {ts_hourly.index.hour.unique()}\n")
>
> # Afficher les jours de la semaine
> print(f"Jours de la semaine : {ts_hourly.index.day_name().unique()}\n")
>
> # Filtrer les données pour les week-ends (Samedi=5, Dimanche=6)
> weekend_data = ts_hourly[ts_hourly.index.dayofweek >= 5]
> print(f"Données du week-end :\n{weekend_data.head()}\n")
> ```
## Rééchantillonnage (`Resampling`)
Le rééchantillonnage consiste à modifier la fréquence de votre série temporelle.
* **Downsampling** : Réduire la fréquence (ex: de journalier à mensuel). Cela implique une agrégation des données.
* **Upsampling** : Augmenter la fréquence (ex: de mensuel à journalier). Cela implique une interpolation ou un remplissage des valeurs.
La méthode `resample()` est au cœur de cette opération. Elle est suivie d'une fonction d'agrégation (pour le downsampling) ou d'une méthode de remplissage/interpolation (pour l'upsampling).
### Downsampling
```python
# Créer une série temporelle journalière sur 3 mois
daily_data = pd.Series(np.random.rand(90).cumsum(),
index=pd.date_range(start='2023-01-01', periods=90, freq='D'))
# Rééchantillonner en mensuel, en prenant la moyenne des valeurs
monthly_mean = daily_data.resample('M').mean()
print(f"Moyenne mensuelle:\n{monthly_mean}\n")
# Rééchantillonner en hebdomadaire, en prenant la somme des valeurs
weekly_sum = daily_data.resample('W').sum()
print(f"Somme hebdomadaire:\n{weekly_sum}\n")
# Rééchantillonner pour obtenir les valeurs d'ouverture, maximum, minimum, clôture (OHLC)
ohlc_data = pd.DataFrame(np.random.rand(90, 4), index=daily_data.index, columns=['Open', 'High', 'Low', 'Close'])
monthly_ohlc = ohlc_data.resample('M').ohlc()
print(f"OHLC mensuel:\n{monthly_ohlc}\n")
```
### Upsampling
Lors de l'upsampling, de nouvelles dates/heures sont créées dans l'index, et les valeurs correspondantes sont `NaN` par défaut. Vous devez spécifier comment remplir ces valeurs manquantes.
```python
# Créer une série temporelle mensuelle
monthly_data = pd.Series(np.random.rand(5),
index=pd.date_range(start='2023-01-01', periods=5, freq='M'))
print(f"Données mensuelles originales:\n{monthly_data}\n")
# Upsampling en journalier, avec remplissage des valeurs manquantes par la dernière valeur valide (forward-fill)
daily_upsampled_ffill = monthly_data.resample('D').ffill()
print(f"Upsampling journalier avec ffill:\n{daily_upsampled_ffill.head()}\n")
# Upsampling en journalier, avec remplissage par la prochaine valeur valide (backward-fill)
daily_upsampled_bfill = monthly_data.resample('D').bfill()
print(f"Upsampling journalier avec bfill:\n{daily_upsampled_bfill.head()}\n")
# Upsampling avec interpolation linéaire
daily_upsampled_interp = monthly_data.resample('D').interpolate(method='linear')
print(f"Upsampling journalier avec interpolation linéaire:\n{daily_upsampled_interp.head()}\n")
```
> [!example] Rééchantillonnage (Downsampling et Upsampling)
> ```python
> import pandas as pd
> import numpy as np
>
> # Données horaires pour une semaine
> index_hourly = pd.date_range(start='2023-01-01 00:00', periods=7*24, freq='H')
> values_hourly = np.random.rand(7*24) * 100
> ts_hourly = pd.Series(values_hourly, index=index_hourly)
>
> print(f"Série horaire originale (5 premières valeurs) :\n{ts_hourly.head()}\n")
>
> # Downsampling : moyenne journalière
> daily_avg = ts_hourly.resample('D').mean()
> print(f"Moyenne journalière :\n{daily_avg}\n")
>
> # Upsampling : de journalier à horaire, avec remplissage par la dernière valeur connue
> # Créons d'abord une série journalière à partir de la moyenne journalière
> daily_series = pd.Series(np.random.rand(7), index=pd.date_range(start='2023-01-01', periods=7, freq='D'))
> upsampled_hourly_ffill = daily_series.resample('H').ffill()
> print(f"Upsampling de journalier à horaire (ffill) (5 premières valeurs) :\n{upsampled_hourly_ffill.head()}\n")
> ```
> [!note] Règle pour `resample()`
> La méthode `resample()` doit être suivie d'une fonction d'agrégation (`mean()`, `sum()`, `min()`, `max()`, `first()`, `last()`, `ohlc()`, `count()`, `median()`, `std()`, `var()`, etc.) pour le downsampling, ou d'une méthode de remplissage/interpolation (`ffill()`, `bfill()`, `interpolate()`) pour l'upsampling.
## Décalage temporel (`Shifting`)
Le décalage (`shift()`) est une opération fondamentale pour les séries temporelles, notamment pour le calcul de retards (lags) ou d'avances.
* `df.shift(periods=n)` : Décale les données de `n` périodes. Si `n` est positif, les données sont décalées vers le bas (valeurs plus anciennes), créant des `NaN` au début. Si `n` est négatif, les données sont décalées vers le haut (valeurs plus récentes), créant des `NaN` à la fin.
* `df.shift(periods=n, freq='D')` : Décale les données *et* l'index selon une fréquence donnée. Utile pour aligner des séries avec des fréquences différentes.
```python
# Créer une série temporelle simple
data_shift = pd.Series(range(10), index=pd.date_range(start='2023-01-01', periods=10, freq='D'))
print(f"Série originale:\n{data_shift}\n")
# Décaler d'une période vers l'arrière (lag de 1)
lag_1 = data_shift.shift(1)
print(f"Décalage de 1 (lag):\n{lag_1}\n")
# Décaler de deux périodes vers l'avant (lead de 2)
lead_2 = data_shift.shift(-2)
print(f"Décalage de -2 (lead):\n{lead_2}\n")
# Décaler les valeurs et l'index de 3 jours
shifted_with_freq = data_shift.shift(3, freq='D')
print(f"Décalage avec fréquence (3 jours):\n{shifted_with_freq}\n")
```
> [!example] Décalage de données
> ```python
> import pandas as pd
> import numpy as np
>
> # Série de ventes journalières
> sales = pd.Series(np.random.randint(50, 200, 10),
> index=pd.date_range(start='2023-11-01', periods=10, freq='D'))
>
> print(f"Ventes originales :\n{sales}\n")
>
> # Calculer les ventes de la veille (lag de 1)
> sales_yesterday = sales.shift(1)
> print(f"Ventes de la veille :\n{sales_yesterday}\n")
>
> # Calculer la variation par rapport à la veille
> daily_change = sales - sales_yesterday
> print(f"Variation journalière :\n{daily_change}\n")
> ```
## Fenêtres glissantes (`Rolling Windows`)
Les fenêtres glissantes (ou *rolling windows*) permettent d'appliquer une fonction d'agrégation (moyenne, somme, écart-type, etc.) sur une "fenêtre" de données de taille fixe qui se déplace le long de la série temporelle. C'est essentiel pour le lissage des séries, la détection de tendances et la réduction du bruit.
```python
# Créer une série temporelle avec du bruit
np.random.seed(42)
series_rolling = pd.Series(np.random.randn(50).cumsum() + np.random.randn(50) * 5,
index=pd.date_range(start='2023-01-01', periods=50, freq='D'))
print(f"Série originale (5 premières valeurs):\n{series_rolling.head()}\n")
# Calculer une moyenne mobile sur 7 jours
rolling_mean_7_days = series_rolling.rolling(window=7).mean()
print(f"Moyenne mobile sur 7 jours (5 premières valeurs):\n{rolling_mean_7_days.head(10)}\n") # Les premières valeurs sont NaN
# Calculer l'écart-type mobile sur 3 jours
rolling_std_3_days = series_rolling.rolling(window=3).std()
print(f"Écart-type mobile sur 3 jours (5 premières valeurs):\n{rolling_std_3_days.head()}\n")
# Spécifier un nombre minimum d'observations pour commencer le calcul
rolling_mean_min_obs = series_rolling.rolling(window=7, min_periods=1).mean()
print(f"Moyenne mobile sur 7 jours (min_periods=1) (5 premières valeurs):\n{rolling_mean_min_obs.head()}\n")
```
> [!definition] Fenêtre glissante
> Une **fenêtre glissante** est un sous-ensemble de données consécutives d'une série temporelle. Lorsque la fenêtre "glisse" le long de la série, elle englobe de nouvelles données et en rejette d'anciennes, permettant de calculer une statistique (moyenne, somme, etc.) localement, sur un intervalle de temps donné.
>
> La syntaxe générale est `series.rolling(window=W, min_periods=M, center=C).aggregation_function()`, où :
> - `W` est la taille de la fenêtre.
> - `M` est le nombre minimum d'observations requises dans la fenêtre pour produire une valeur (par défaut, `W`).
> - `C` (booléen) indique si la fenêtre doit être centrée sur la date actuelle ou alignée à droite (par défaut, `False`).
> [!example] Moyennes mobiles
> ```python
> import pandas as pd
> import numpy as np
>
> # Créer une série de données avec une tendance et du bruit
> dates = pd.date_range(start='2023-01-01', periods=30, freq='D')
> data = np.linspace(0, 10, 30) + np.random.randn(30) * 2
> ts = pd.Series(data, index=dates)
>
> print(f"Série originale (10 premières valeurs) :\n{ts.head(10)}\n")
>
> # Calculer une moyenne mobile sur 5 jours
> ma_5_days = ts.rolling(window=5).mean()
> print(f"Moyenne mobile sur 5 jours (10 premières valeurs) :\n{ma_5_days.head(10)}\n")
>
> # Calculer une moyenne mobile sur 5 jours, centrée
> ma_5_days_centered = ts.rolling(window=5, center=True).mean()
> print(f"Moyenne mobile sur 5 jours (centrée) (10 premières valeurs) :\n{ma_5_days_centered.head(10)}\n")
> ```
> Les moyennes mobiles sont très utiles pour lisser les fluctuations à court terme et révéler les tendances sous-jacentes.
## Gestion des données manquantes dans les séries temporelles
Les données manquantes (`NaN`) sont courantes dans les séries temporelles. Pandas offre des méthodes robustes pour les gérer.
* `dropna()` : Supprime les lignes (ou colonnes) contenant des `NaN`.
* `fillna()` : Remplace les `NaN` par une valeur spécifiée (ex: 0, la moyenne, la dernière valeur connue, etc.).
* `interpolate()` : Estime les valeurs manquantes en se basant sur les valeurs existantes, selon diverses méthodes.
```python
# Créer une série avec des NaN
ts_nan = pd.Series([1, 2, np.nan, 4, 5, np.nan, 7],
index=pd.date_range(start='2023-01-01', periods=7, freq='D'))
print(f"Série avec NaN:\n{ts_nan}\n")
# Supprimer les NaN
ts_dropna = ts_nan.dropna()
print(f"Série après dropna:\n{ts_dropna}\n")
# Remplacer les NaN par 0
ts_fillna_0 = ts_nan.fillna(0)
print(f"Série après fillna(0):\n{ts_fillna_0}\n")
# Remplacer les NaN par la dernière valeur valide (forward fill)
ts_fillna_ffill = ts_nan.fillna(method='ffill')
print(f"Série après fillna(ffill):\n{ts_fillna_ffill}\n")
# Remplacer les NaN par la prochaine valeur valide (backward fill)
ts_fillna_bfill = ts_nan.fillna(method='bfill')
print(f"Série après fillna(bfill):\n{ts_fillna_bfill}\n")
# Interpolation linéaire
ts_interpolate_linear = ts_nan.interpolate(method='linear')
print(f"Série après interpolate(linear):\n{ts_interpolate_linear}\n")
# Interpolation basée sur le temps (nécessite un DatetimeIndex)
# Si les données sont irrégulières, l'interpolation 'time' est plus appropriée que 'linear'
ts_nan_irregular = pd.Series([1, np.nan, 3, np.nan, np.nan, 6],
index=[pd.Timestamp('2023-01-01'), pd.Timestamp('2023-01-03'),
pd.Timestamp('2023-01-04'), pd.Timestamp('2023-01-07'),
pd.Timestamp('2023-01-08'), pd.Timestamp('2023-01-10')])
print(f"Série irrégulière avec NaN:\n{ts_nan_irregular}\n")
ts_interpolate_time = ts_nan_irregular.interpolate(method='time')
print(f"Série après interpolate(time):\n{ts_interpolate_time}\n")
```
> [!example] Interpolation de données manquantes
> ```python
> import pandas as pd
> import numpy as np
>
> # Créer une série avec des valeurs manquantes à des intervalles irréguliers
> dates = pd.to_datetime(['2023-01-01', '2023-01-03', '2023-01-04', '2023-01-07', '2023-01-09'])
> values = [10, 15, np.nan, 25, np.nan]
> ts_missing = pd.Series(values, index=dates)
>
> print(f"Série avec valeurs manquantes :\n{ts_missing}\n")
>
> # Interpolation linéaire
> ts_interpolated = ts_missing.interpolate(method='linear')
> print(f"Série après interpolation linéaire :\n{ts_interpolated}\n")
>
> # Interpolation polynomiale (d'ordre 2)
> ts_interpolated_poly = ts_missing.interpolate(method='polynomial', order=2)
> print(f"Série après interpolation polynomiale (ordre 2) :\n{ts_interpolated_poly}\n")
> ```
> Le choix de la méthode d'interpolation dépend de la nature de vos données et des hypothèses que vous faites sur le comportement des valeurs manquantes.
# Fréquences et décalages temporels avancés (`Offset Aliases`)
Pandas utilise des chaînes de caractères appelées "offset aliases" pour spécifier des fréquences temporelles. Vous les avez déjà rencontrées avec `pd.date_range()` et `resample()`. Elles sont très flexibles et permettent de définir des fréquences allant de la nanoseconde à l'année, avec des variantes pour les jours ouvrés, les fins de mois, etc.
> [!tip] Liste des alias de fréquence courants
> | Alias | Description | Exemple d'utilisation |
> | :---- | :----------------------- | :-------------------------------------------------- |
> | `D` | Jour calendaire | `pd.date_range(..., freq='D')` |
> | `B` | Jour ouvré | `pd.date_range(..., freq='B')` |
> | `W` | Semaine | `resample('W')` |
> | `W-MON` | Semaine se terminant le lundi | `resample('W-MON')` |
> | `M` | Fin de mois | `resample('M')` |
> | `MS` | Début de mois | `resample('MS')` |
> | `Q` | Fin de trimestre | `resample('Q')` |
> | `QS` | Début de trimestre | `resample('QS')` |
> | `A` | Fin d'année | `resample('A')` |
> | `AS` | Début d'année | `resample('AS')` |
> | `H` | Heure | `pd.date_range(..., freq='H')` |
> | `T` (ou `min`) | Minute | `resample('T')` |
> | `S` | Seconde | `resample('S')` |
> | `L` (ou `ms`) | Milliseconde | `resample('L')` |
> | `U` (ou `us`) | Microseconde | `resample('U')` |
> | `N` (ou `ns`) | Nanoseconde | `resample('N')` |
>
> Vous pouvez également préfixer ces alias avec un nombre pour spécifier des fréquences multiples (ex: `'2H'` pour toutes les 2 heures, `'3D'` pour tous les 3 jours).
```python
# Exemple de fréquences avancées
# Index bi-horaire
bi_hourly_index = pd.date_range(start='2023-01-01 00:00', periods=5, freq='2H')
print(f"Index bi-horaire:\n{bi_hourly_index}\n")
# Index journalier ouvré
business_daily_index = pd.date_range(start='2023-11-01', periods=7, freq='B')
print(f"Index journalier ouvré:\n{business_daily_index}\n")
# Décalage temporel avec DateOffset
from pandas.tseries.offsets import DateOffset
dt_base = pd.Timestamp('2023-10-26')
print(f"Date de base: {dt_base}\n")
# Ajouter 2 mois
dt_plus_2_months = dt_base + DateOffset(months=2)
print(f"Date + 2 mois: {dt_plus_2_months}\n")
# Soustraire 3 jours ouvrés
dt_minus_3_biz_days = dt_base - pd.tseries.offsets.BusinessDay(3)
print(f"Date - 3 jours ouvrés: {dt_minus_3_biz_days}\n")
```
> [!warning] Fuseaux horaires
> Ce chapitre se concentre sur les séries temporelles "naïves" (sans information de fuseau horaire). La gestion des fuseaux horaires (`tz_localize()`, `tz_convert()`) est un sujet plus avancé qui peut introduire des complexités, notamment lors des changements d'heure d'été/hiver. Assurez-vous d'être conscient de cette dimension si vos données proviennent de différentes régions géographiques.
# ➡️ C'est la fin !
Ce chapitre vous a introduit aux fondations de la manipulation des séries temporelles avec Pandas. Vous avez appris à :
* Utiliser les objets `datetime` et `timedelta` de Python.
* Créer et gérer des `DatetimeIndex` pour des `Series` et `DataFrame` temporels.
* Effectuer des opérations clés comme l'indexation par date, le rééchantillonnage, le décalage et l'utilisation de fenêtres glissantes.
* Gérer les données manquantes de manière appropriée.
La maîtrise de ces techniques est indispensable pour la préparation et l'exploration de données temporelles, qui sont les premières étapes cruciales avant toute tentative de modélisation ou de prévision. Dans les prochains chapitres, nous nous appuierons sur ces compétences pour explorer des modèles de prévision spécifiques et des techniques d'analyse plus avancées.
N'hésitez pas à expérimenter avec les exemples fournis et à manipuler vos propres ensembles de données temporelles. C'est en pratiquant que vous consoliderez votre compréhension de ces outils puissants.
---
- Cours précèdent: `cours-de-départ`
- Prochain cours: [[Cours 2 - Forecasting]]
- Page d'accueil de la compétence: [[Forecasting]]
# 🗓️ Historique
- Dernière MAJ: `20-Octobre-2025`
- Rédigé par: [[Hamilton DE ARAUJO]]