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
KHibma
la source

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
...
unutbu
la source
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
Brad Solomon
la source
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.

Nick Edgar
la source
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.

JohnE
la source
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
eiTan LaVi
la source