Ajouter des dates manquantes à la base de données pandas


128

Mes données peuvent avoir plusieurs événements à une date donnée ou AUCUN événement à une date. Je prends ces événements, j'obtiens un décompte par date et je les trace. Cependant, lorsque je les trace, mes deux séries ne correspondent pas toujours.

idx = pd.date_range(df['simpleDate'].min(), df['simpleDate'].max())
s = df.groupby(['simpleDate']).size()

Dans le code ci-dessus, idx devient une plage de 30 dates. 09-01-2013 au 09-30-2013 Cependant S ne peut avoir que 25 ou 26 jours car aucun événement ne s'est produit à une date donnée. J'obtiens alors une AssertionError car les tailles ne correspondent pas lorsque j'essaye de tracer:

fig, ax = plt.subplots()    
ax.bar(idx.to_pydatetime(), s, color='green')

Quelle est la bonne façon d'aborder cela? Est-ce que je veux supprimer les dates sans valeurs d' IDX ou (ce que je préfère faire) ajouter à la série la date manquante avec un compte de 0. Je préfère avoir un graphique complet de 30 jours avec 0 valeurs. Si cette approche est correcte, des suggestions sur la façon de commencer? Ai-je besoin d'une sorte de reindexfonction dynamique ?

Voici un extrait de S ( df.groupby(['simpleDate']).size() ), ne notez aucune entrée pour 04 et 05.

09-02-2013     2
09-03-2013    10
09-06-2013     5
09-07-2013     1

Réponses:


257

Vous pouvez utiliser Series.reindex:

import pandas as pd

idx = pd.date_range('09-01-2013', '09-30-2013')

s = pd.Series({'09-02-2013': 2,
               '09-03-2013': 10,
               '09-06-2013': 5,
               '09-07-2013': 1})
s.index = pd.DatetimeIndex(s.index)

s = s.reindex(idx, fill_value=0)
print(s)

rendements

2013-09-01     0
2013-09-02     2
2013-09-03    10
2013-09-04     0
2013-09-05     0
2013-09-06     5
2013-09-07     1
2013-09-08     0
...

23
reindexest une fonction incroyable. Il peut (1) réorganiser les données existantes pour qu'elles correspondent à un nouvel ensemble d'étiquettes, (2) insérer de nouvelles lignes là où aucune étiquette n'existait auparavant, (3) remplir les données pour les étiquettes manquantes, (y compris par remplissage avant / arrière) (4) sélectionner des lignes par label!
unutbu

@unutbu Cela répond à une partie d'une question que j'avais aussi, merci! Mais vous vous demandiez si vous saviez comment créer dynamiquement une liste avec les dates qui ont des événements?
Nick Duddy

2
Il y a un problème (ou bogue) avec la réindexation: cela ne fonctionne pas avec les dates antérieures au 1/1/1970, donc dans ce cas, df.resample () fonctionne parfaitement.
Sergey Gulbin

2
vous pouvez l'utiliser à la place pour idx pour sauter la saisie manuelle des dates de début et de fin:idx = pd.date_range(df.index.min(), df.index.max())
Réveil le

En supprimant le lien vers la documentation ici, pour vous éviter la recherche: pandas.pydata.org/pandas-docs/stable/reference/api/…
Harm te Molder

41

Une solution de contournement plus rapide consiste à utiliser .asfreq(). Cela ne nécessite pas la création d'un nouvel index à appeler .reindex().

# "broken" (staggered) dates
dates = pd.Index([pd.Timestamp('2012-05-01'), 
                  pd.Timestamp('2012-05-04'), 
                  pd.Timestamp('2012-05-06')])
s = pd.Series([1, 2, 3], dates)

print(s.asfreq('D'))
2012-05-01    1.0
2012-05-02    NaN
2012-05-03    NaN
2012-05-04    2.0
2012-05-05    NaN
2012-05-06    3.0
Freq: D, dtype: float64

1
Je préfère vraiment cette méthode; vous évitez d'avoir à appeler date_rangecar il utilise implicitement le premier et le dernier index comme début et fin (ce que vous voudriez presque toujours).
Michael Hays

Méthode très propre et professionnelle. Fonctionne également bien avec l'utilisation de l'interpolation par la suite.
msarafzadeh

27

Un problème est que reindexcela échouera s'il y a des valeurs en double. Supposons que nous travaillions avec des données horodatées, que nous souhaitons indexer par date:

df = pd.DataFrame({
    'timestamps': pd.to_datetime(
        ['2016-11-15 1:00','2016-11-16 2:00','2016-11-16 3:00','2016-11-18 4:00']),
    'values':['a','b','c','d']})
df.index = pd.DatetimeIndex(df['timestamps']).floor('D')
df

rendements

            timestamps             values
2016-11-15  "2016-11-15 01:00:00"  a
2016-11-16  "2016-11-16 02:00:00"  b
2016-11-16  "2016-11-16 03:00:00"  c
2016-11-18  "2016-11-18 04:00:00"  d

En raison de la 2016-11-16date en double , une tentative de réindexation:

all_days = pd.date_range(df.index.min(), df.index.max(), freq='D')
df.reindex(all_days)

échoue avec:

...
ValueError: cannot reindex from a duplicate axis

(cela signifie que l'index a des doublons, non pas qu'il soit lui-même un dup)

Au lieu de cela, nous pouvons utiliser .locpour rechercher des entrées pour toutes les dates de la plage:

df.loc[all_days]

rendements

            timestamps             values
2016-11-15  "2016-11-15 01:00:00"  a
2016-11-16  "2016-11-16 02:00:00"  b
2016-11-16  "2016-11-16 03:00:00"  c
2016-11-17  NaN                    NaN
2016-11-18  "2016-11-18 04:00:00"  d

fillna peut être utilisé sur la série de colonnes pour remplir les blancs si nécessaire.


Une idée sur ce qu'il faut faire si la colonne Date contient Blanksou NULLS? df.loc[all_days]ne fonctionnera pas dans ce cas.
Furqan Hashim

1
Passer des list-likes à .loc ou [] avec une étiquette manquante lèvera KeyError dans le futur, vous pouvez utiliser .reindex () comme alternative. Voir la documentation ici: pandas.pydata.org/pandas-docs/stable/…
Dmitrii Magas

19

Une autre approche consiste à resamplegérer les dates en double en plus des dates manquantes. Par exemple:

df.resample('D').mean()

resampleest une opération différée comme groupbysi vous devez la suivre avec une autre opération. Dans ce cas , meanfonctionne bien, mais vous pouvez également utiliser d'autres méthodes comme les pandas max, sumetc.

Voici les données originales, mais avec une entrée supplémentaire pour '2013-09-03':

             val
date           
2013-09-02     2
2013-09-03    10
2013-09-03    20    <- duplicate date added to OP's data
2013-09-06     5
2013-09-07     1

Et voici les résultats:

             val
date            
2013-09-02   2.0
2013-09-03  15.0    <- mean of original values for 2013-09-03
2013-09-04   NaN    <- NaN b/c date not present in orig
2013-09-05   NaN    <- NaN b/c date not present in orig
2013-09-06   5.0
2013-09-07   1.0

J'ai laissé les dates manquantes en tant que NaNs pour préciser comment cela fonctionne, mais vous pouvez ajouter fillna(0)pour remplacer les NaN par des zéros comme demandé par l'OP ou utiliser quelque chose comme interpolate()pour remplir avec des valeurs non nulles basées sur les lignes voisines.


6

Voici une méthode intéressante pour remplir les dates manquantes dans un dataframe, avec votre choix de fill_value, days_backpour remplir, et pour trier l'ordre ( date_order) par lequel trier le dataframe:

def fill_in_missing_dates(df, date_col_name = 'date',date_order = 'asc', fill_value = 0, days_back = 30):

    df.set_index(date_col_name,drop=True,inplace=True)
    df.index = pd.DatetimeIndex(df.index)
    d = datetime.now().date()
    d2 = d - timedelta(days = days_back)
    idx = pd.date_range(d2, d, freq = "D")
    df = df.reindex(idx,fill_value=fill_value)
    df[date_col_name] = pd.DatetimeIndex(df.index)

    return df
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.