2 votes

Index Datetime des Pandas: Nombre d'événements actuels au fil du temps

Je analyse un ensemble d'événements, chacun ayant un type, un début et une heure de fin. Je cherche à résumer le nombre simultané de chaque événement au fil du temps dans la plage de temps.

Considérez l'ensemble de données ci-dessous, répertoriant les événements N1-N4, chacun avec des plages qui se chevauchent :

>>> data = {
...    'name' : [ 'N1', 'N2', 'N3', 'N4', 'N1',  'N2', 'N7'],
...    'start_dt_str' : ['01-01-2020', '01-03-2020', '01-01-2020', '01-01-2020', '01-03-2020', '01-04-2020','01-10-2020'],
...    'end_dt_str' : ['01-03-2020', '01-05-2020', '01-05-2020', '01-02-2020', '01-04-2020', '01-05-2020', '01-11-2020']
... }
>>> df = pd.DataFrame(data)
>>> df['start_dt'] = pd.to_datetime(df['start_dt_str'])
>>> df['end_dt'] = pd.to_datetime(df['end_dt_str'])
>>> del df['start_dt_str']
>>> del df['end_dt_str']
>>> df 
  name   start_dt     end_dt
0   N1 2020-01-01 2020-01-03
1   N2 2020-01-03 2020-01-05
2   N3 2020-01-01 2020-01-05
3   N4 2020-01-01 2020-01-02
4   N1 2020-01-03 2020-01-04
5   N2 2020-01-04 2020-01-05
6   N7 2020-01-10 2020-01-11

Mon objectif est de produire ce résumé, le nombre d'événements simultanés, par type, pour chaque date dans la plage. Voici la réponse correcte :

               N1 N2 N3 N4 N7
2020-01-01     1  0  1  1  0
2020-01-02     1  0  1  1  0 
2020-01-03     2  1  1  0  0
2020-01-04     1  2  1  0  0
2020-01-05     1  2  0  0  0
2020-01-06     0  0  0  0  0
2020-01-07     0  0  0  0  0
2020-01-08     0  0  0  0  0
2020-01-09     0  0  0  0  0
2020-01-10     0  0  0  0  1
2020-01-11     0  0  0  0  1

Notez qu'il y a des dates en double dans les colonnes start_dt et end_dt.

Remarquez également que la solution doit permettre de reconstituer les données pour remplir les dates manquantes avec des lignes contenant uniquement des zéros. Dans cet exemple, la date 01-09 n'apparaît pas en tant que date de début ou de fin, mais doit être présente dans la sortie. Dans le cas général, je souhaite pouvoir effectuer une reconstitution pour sélectionner des intervalles arbitraires.

Pour simplifier l'explication du problème, à la fois la période de déclaration et les données sont précises au jour près dans l'ensemble de données ci-dessus. Dans l'ensemble de données réel, start_dt et end_dt sont précis au milliseconde (mais contiennent toujours des doublons), et la période de déclaration pourrait être en heures, jours, semaines, etc.

Remarquez également qu'il existe des lacunes dans les données, donc une reconstitution est nécessaire pour produire la série de dates. (c'est-à-dire, même si les données sont précises au milliseconde, il manque des journéees entières).

J'ai essayé plusieurs approches qui NE fonctionnent PAS. Au début, cela semblait simple, j'ai essayé :

df.set_index(['name','start_dt']).groupby('name').resample('D',level='start_dt').ffill()

ValueError: L'augmentation de niveau= ou de sélection on= n'est pas prise en charge, utilisez .set_index(...) pour définir explicitement l'index comme chronologie

Cela conduit à ce problème de pandas concernant l'augmentation qui est ouvert, et fournit quelques contournements. Malheureusement, Nous ne pouvons pas utiliser seulement start_dt (ou end_dt) comme index car il n'est pas unique :

Traceback (most recent call last):
  File "", line 1, in 
  File "/home/dcowden/envs/analysis-env/lib/python3.6/site-packages/pandas/core/resample.py", line 453, in pad
    return self._upsample("pad", limit=limit)
  File "/home/dcowden/envs/analysis-env/lib/python3.6/site-packages/pandas/core/resample.py", line 1095, in _upsample
    res_index, method=method, limit=limit, fill_value=fill_value
  File "/home/dcowden/envs/analysis-env/lib/python3.6/site-packages/pandas/util/_decorators.py", line 227, in wrapper
    return func(*args, **kwargs)
  File "/home/dcowden/envs/analysis-env/lib/python3.6/site-packages/pandas/core/frame.py", line 3856, in reindex
    return super().reindex(**kwargs)
  File "/home/dcowden/envs/analysis-env/lib/python3.6/site-packages/pandas/core/generic.py", line 4544, in reindex
    axes, level, limit, tolerance, method, fill_value, copy
  File "/home/dcowden/envs/analysis-env/lib/python3.6/site-packages/pandas/core/frame.py", line 3744, in _reindex_axes
    index, method, copy, level, fill_value, limit, tolerance
  File "/home/dcowden/envs/analysis-env/lib/python3.6/site-packages/pandas/core/frame.py", line 3760, in _reindex_index
    new_index, method=method, level=level, limit=limit, tolerance=tolerance
  File "/home/dcowden/envs/analysis-env/lib/python3.6/site-packages/pandas/core/indexes/base.py", line 3149, in reindex
    "cannot reindex a non-unique index "
ValueError: impossible de réindexer un index non unique avec une méthode ou une limite

Cette question qui semble similaire à mon problème, mais ne remplit pas toutes les dates dans la plage pour chaque type d'événement :

>>> df.set_index('start_dt').groupby('name').resample('D').asfreq()
                name     end_dt
name start_dt                  
N1   2020-01-01   N1 2020-01-03
     2020-01-02  NaN        NaT
     2020-01-03   N1 2020-01-04
N2   2020-01-03   N2 2020-01-05
     2020-01-04   N2 2020-01-05
N3   2020-01-01   N3 2020-01-05
N4   2020-01-01   N4 2020-01-02

Cette solution semblait prometteuse, mais n'est pas exactement ce dont j'ai besoin non plus. Elle cherche essentiellement un seul événement dans une plage, mais ne comptabilise pas le nombre total en cours. Cependant, utiliser un IntervalIndex semble être un bon départ.

Je pense que cela devrait être assez simple, mais il est clair que mon expertise en pandas est lamentablement insuffisante.

Toute aide est grandement appréciée !

EDIT :

1voto

jezrael Points 290608

L'idée est de répéter les valeurs par date_range pour aider DataFrame et ensuite utiliser SeriesGroupBy.value_counts avec Series.unstack:

L = [pd.Series(r.name, pd.date_range(r.start_dt, r.end_dt)) for r in df.itertuples()]
s = pd.concat(L)

df1 = s.groupby(level=0).value_counts().unstack(fill_value=0)
print (df1)
            N1  N2  N3  N4
2020-01-01   1   0   1   1
2020-01-02   1   0   1   1
2020-01-03   2   1   1   0
2020-01-04   1   2   1   0
2020-01-05   0   2   1   0

Une autre solution avec un remodelage par DataFrame.melt, mais il est d'abord nécessaire de distinguer les valeurs consécutives par Series.shift avec l'astuce Series.cumsum, puis utiliser DataFrameGroupBy.resample et enfin crosstab:

df['g'] = df['name'].ne(df['name'].shift()).cumsum()
df1 = (df.melt(['name','g'])
         .set_index('value')
         .groupby(['g','name'])['variable']
         .resample('d')
         .first()
         .reset_index())

df1 = pd.crosstab(df1['value'], df1['name'])
print (df1)
name        N1  N2  N3  N4
value                     
2020-01-01   1   0   1   1
2020-01-02   1   0   1   1
2020-01-03   2   1   1   0
2020-01-04   1   2   1   0
2020-01-05   0   2   1   0

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X