On peut choisir d'indexer son tableau par un index chronologique. Dans ce cas certaines opérations liées au temps deviennent possibles.

Note: regardez ce qu'est une date/heure dans Python si vous ne l'avez pas encore fait : [Introduction à DateTime](../lesson2 Deeper in Python/21 datetime.ipynb)

Création et utilisation d'un tableau chronologique¶

Les dates sont ordonnées et peuvent être données dans une liste de dates, sous forme d’index, ou avec la méthode

pd.date_range (début, fin, période, fréquence)

où vous devez choisir entre fin et période, la période étant le nombre d'itérations

La fréquence est définie par ces codes (attention, ils sont différents de ceux de Numpy datetime) :

B        jours ouvrables
C        jours ouvrables personnalisée (à titre expérimental)
J        jour
W        hebdomadaire
M        mensuel
BM       mois des affaires
CBM      mois d'activité personnalisée
MS       début de mois
BMS      début de mois d'activité
CBMS     début de mois d'activité personnalisée
Q        fin de trimestre
BQ       trimestre d’affaires
QS       début de trimestre
BQS      début du trimestre d'activité
A        fin d'année
BA       fin d'année d'exercice
AS       début d'année 
BAS      année de début d'exercice
BH       heure professionel
H        heure
T, min   minute
S        seconde
L, ms    millisecondes
U, us    microsecondes
N        nanosecondes
In [1]:
import numpy as np
import pandas as pd
np.random.seed(1)
In [2]:
dates = pd.date_range('2016-08-28', '2016-09-06', freq='B') # begin, end, only business days
dates
Out[2]:
DatetimeIndex(['2016-08-29', '2016-08-30', '2016-08-31', '2016-09-01',
               '2016-09-02', '2016-09-05', '2016-09-06'],
              dtype='datetime64[ns]', freq='B')

Avec cet index on peut créer un tableau chronologique :

In [3]:
tdf1 = pd.DataFrame({'temperature': 20 + np.random.randint(0,5,7),
                     'pression'   : 1 + np.random.random(7)/10 },
                    index=dates)
tdf1
Out[3]:
temperature pression
2016-08-29 23 1.039658
2016-08-30 24 1.038791
2016-08-31 20 1.066975
2016-09-01 21 1.093554
2016-09-02 23 1.084631
2016-09-05 20 1.031327
2016-09-06 20 1.052455

Comme pour les tableaux usuels on peut selectionner les parties qui nous intéressent avec loc et les filtres. Il est également possible de contraindre les dates :

In [4]:
tdf1.loc['2016-08']  # just August
Out[4]:
temperature pression
2016-08-29 23 1.039658
2016-08-30 24 1.038791
2016-08-31 20 1.066975
In [5]:
tdf1.loc['2016-09-03':]  # after that date even if the date is not in the index
Out[5]:
temperature pression
2016-09-05 20 1.031327
2016-09-06 20 1.052455

Manipulation¶

Boucher les trous¶

Soit deux sources d'information incomplètes, utilisons les méthodes que l'on a déjà vu pour boucher les trous.

In [6]:
tdf2 = tdf1.copy()
tdf1.drop(tdf1.index[[0,1,3]], inplace=True)   # we remove some data
tdf2.drop(tdf2.index[[5,6]], inplace=True)     # more data removed
tdf2.drop(columns='pression', inplace=True)
display(tdf1, tdf2)
temperature pression
2016-08-31 20 1.066975
2016-09-02 23 1.084631
2016-09-05 20 1.031327
2016-09-06 20 1.052455
temperature
2016-08-29 23
2016-08-30 24
2016-08-31 20
2016-09-01 21
2016-09-02 23

On utilse merge pour aggréger les données des deux tableaux. Comme on veut se baser sur la température ainsi que sur l'index, il faut passer l'index en une colonne (c'est fusion sur l'index ou sur des colonnes).

In [7]:
res = pd.merge(tdf1.reset_index(), tdf2.reset_index(), on=['temperature', 'index'], how='outer')
display(res)
res.set_index('index').sort_index()
index temperature pression
0 2016-08-31 20 1.066975
1 2016-09-05 20 1.031327
2 2016-09-06 20 1.052455
3 2016-09-01 21 NaN
4 2016-08-29 23 NaN
5 2016-09-02 23 1.084631
6 2016-08-30 24 NaN
Out[7]:
temperature pression
index
2016-08-29 23 NaN
2016-08-30 24 NaN
2016-08-31 20 1.066975
2016-09-01 21 NaN
2016-09-02 23 1.084631
2016-09-05 20 1.031327
2016-09-06 20 1.052455

Si les deux sources de données ne sont pas d'accord sur une valeur, que se passe-t-il ?

In [8]:
tdf1.loc['2016-08-31','temperature'] = 19
res = pd.merge(tdf1.reset_index(), tdf2.reset_index(), on=['temperature', 'index'], how='outer')
res = res.set_index('index').sort_index()
res
Out[8]:
temperature pression
index
2016-08-29 23 NaN
2016-08-30 24 NaN
2016-08-31 19 1.066975
2016-08-31 20 NaN
2016-09-01 21 NaN
2016-09-02 23 1.084631
2016-09-05 20 1.031327
2016-09-06 20 1.052455

merge en mode outer garde toutes les valeurs, aussi a 2 températures différentes pour le 31/08.

Interpolation¶

Comme avec Numpy, l'interpolation peut être faite en prenant en compte les dates et donc l'écart entre 2 dates successives.

In [9]:
res.interpolate(method='time')
Out[9]:
temperature pression
index
2016-08-29 23 NaN
2016-08-30 24 NaN
2016-08-31 19 1.066975
2016-08-31 20 1.066975
2016-09-01 21 1.075803
2016-09-02 23 1.084631
2016-09-05 20 1.031327
2016-09-06 20 1.052455

Il est également possible de changer l'index et de demander de recalculer les valeurs sur le nouvel index. Bizarrement cela ne marche que sur une Serie (une colonne) :

In [10]:
tdf1['temperature'].resample('30h').interpolate('time')
Out[10]:
2016-08-31 00:00:00    19.00
2016-09-01 06:00:00    19.25
2016-09-02 12:00:00    19.50
2016-09-03 18:00:00    19.75
2016-09-05 00:00:00    20.00
Freq: 30h, Name: temperature, dtype: float64

Pour le faire sur un DataFrame complet on peut faire les colonnes une par une ou cela :

In [11]:
interpol = tdf1.asfreq('30h')
display(interpol)
tmp = pd.concat([tdf1, interpol]).sort_index().interpolate(method='time').drop_duplicates()
tmp.loc[interpol.index]
temperature pression
2016-08-31 00:00:00 19.0 1.066975
2016-09-01 06:00:00 NaN NaN
2016-09-02 12:00:00 NaN NaN
2016-09-03 18:00:00 NaN NaN
2016-09-05 00:00:00 20.0 1.031327
Out[11]:
temperature pression
2016-08-31 00:00:00 19.00 1.066975
2016-09-01 06:00:00 21.50 1.078010
2016-09-02 12:00:00 22.50 1.075747
2016-09-03 18:00:00 21.25 1.053537
2016-09-05 00:00:00 20.00 1.031327

Grouper les données¶

Lorsqu'une colonne a des date de type datetime on sent bien qu'il ne va pas être possble de faire un groupby directement puisque toutes les dates sont en général différente (à la milliseconde au moins). Aussi il faut grouper les donner suivant un intervale ce qui se fait avec dt.to_period:

In [12]:
df = pd.DataFrame({'date': pd.date_range(start="2020-01-01", periods=15, freq='5D'),
                   'day sales': np.random.randint(50,size=15)}).sort_values('date')

df
Out[12]:
date day sales
0 2020-01-01 42
1 2020-01-06 28
2 2020-01-11 29
3 2020-01-16 14
4 2020-01-21 4
5 2020-01-26 23
6 2020-01-31 23
7 2020-02-05 41
8 2020-02-10 49
9 2020-02-15 30
10 2020-02-20 32
11 2020-02-25 22
12 2020-03-01 13
13 2020-03-06 41
14 2020-03-11 9
In [13]:
df.groupby(df['date'].dt.to_period('W'))['day sales'].mean()
Out[13]:
date
2019-12-30/2020-01-05    42.0
2020-01-06/2020-01-12    28.5
2020-01-13/2020-01-19    14.0
2020-01-20/2020-01-26    13.5
2020-01-27/2020-02-02    23.0
2020-02-03/2020-02-09    41.0
2020-02-10/2020-02-16    39.5
2020-02-17/2020-02-23    32.0
2020-02-24/2020-03-01    17.5
2020-03-02/2020-03-08    41.0
2020-03-09/2020-03-15     9.0
Freq: W-SUN, Name: day sales, dtype: float64

On voit qu'on a dans date le début et la fin de la semaine, c'est bien une période. On note que Freq indique W-Sun pour souligner que les semaines finissent le dimanche.

Ré-échantillonner avec resample¶

Il est possible de grouper des données sur une date, le dernier jour de la semaine par exemple, dans ce cas date reste une date et il faut utiliser resample avec comme argument

  • 'W-Sun' pour grouper toutes les données de la semaine sur le dimanche et avoir le même comportement que ci-dessus. On peut choisir un autre jour de la semaine.
  • 'ME' pour regrouper les données à la fin du mois
  • 'YE' à la fin de l'année mais on peut aussi choisir le mois : 'Y-Mar' pour avoir une fenêtre glissante de mars en mars par exemple
In [14]:
df.resample('W-Sun', on='date').mean()  # without 'on', dates shoud be in the index
Out[14]:
day sales
date
2020-01-05 42.0
2020-01-12 28.5
2020-01-19 14.0
2020-01-26 13.5
2020-02-02 23.0
2020-02-09 41.0
2020-02-16 39.5
2020-02-23 32.0
2020-03-01 17.5
2020-03-08 41.0
2020-03-15 9.0

Plus¶

Pour plus d'information sur les tableaux chronologiques on regardera la page sur les séries chronologiques : http://pandas.pydata.org/pandas-docs/stable/timeseries.html

In [ ]: