Aggiungi le date mancanti al dataframe dei panda


128

I miei dati possono avere più eventi in una determinata data o NESSUN evento in una data. Prendo questi eventi, faccio un conteggio per data e li pianifico. Tuttavia, quando li trama, le mie due serie non sempre corrispondono.

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

Nel codice sopra idx diventa un intervallo di diciamo 30 date. Dal 09-01-2013 al 09-30-2013 Tuttavia S può avere solo 25 o 26 giorni perché non si sono verificati eventi per una determinata data. Quindi ottengo un AssertionError poiché le dimensioni non corrispondono quando provo a tracciare:

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

Qual è il modo corretto per affrontare questo problema? Voglio rimuovere le date senza valori da IDX o (cosa che preferisco fare) è aggiungere alla serie la data mancante con un conteggio di 0. Preferisco avere un grafico completo di 30 giorni con valori 0. Se questo approccio è corretto, qualche suggerimento su come iniziare? Ho bisogno di una sorta di reindexfunzione dinamica ?

Ecco uno snippet di S ( df.groupby(['simpleDate']).size() ), nota l'assenza di voci per 04 e 05.

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

Risposte:


257

Potresti usare 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)

i rendimenti

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
reindexè una funzione straordinaria. Può (1) riordinare i dati esistenti in modo che corrispondano a un nuovo set di etichette, (2) inserire nuove righe in cui non esisteva un'etichetta in precedenza, (3) riempire i dati per le etichette mancanti, (incluso il riempimento in avanti / all'indietro) (4) selezionare le righe per etichetta!
unutbu

@unutbu Questo risponde a una parte di una domanda che avevo anch'io, grazie! Ma ti stavi chiedendo se sapessi come creare dinamicamente un elenco di con le date che hanno eventi?
Nick Duddy

2
C'è un problema (o bug) con la reindicizzazione però: non funziona con date precedenti al 1/1/1970, quindi in questo caso df.resample () funziona perfettamente.
Sergey Gulbin

2
puoi usarlo invece per idx per saltare l'inserimento manuale delle date di inizio e fine:idx = pd.date_range(df.index.min(), df.index.max())
Sveglia

Lasciando il link alla documentazione qui, per salvarti la ricerca: pandas.pydata.org/pandas-docs/stable/reference/api/…
Harm te Molder

41

Una soluzione più rapida è usare .asfreq(). Ciò non richiede la creazione di un nuovo indice da chiamare all'interno .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
Preferisco davvero questo metodo; eviti di dover chiamare date_rangepoiché utilizza implicitamente il primo e l'ultimo indice come inizio e fine (che è quello che vorresti quasi sempre).
Michael Hays

Metodo molto pulito e professionale. Funziona bene anche con l'interpolazione in seguito.
msarafzadeh

27

Un problema è che reindexfallirà se ci sono valori duplicati. Supponiamo che stiamo lavorando con dati con timestamp, che vogliamo indicizzare per data:

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

i rendimenti

            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

A causa della 2016-11-16data duplicata , un tentativo di reindicizzazione:

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

fallisce con:

...
ValueError: cannot reindex from a duplicate axis

(con questo significa che l'indice ha duplicati, non che sia esso stesso un dup)

Invece, possiamo usare .locper cercare le voci per tutte le date nell'intervallo:

df.loc[all_days]

i rendimenti

            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 può essere utilizzato sulla serie di colonne per riempire gli spazi, se necessario.


Qualche idea su cosa fare se la colonna Data contiene Blankso NULLS? df.loc[all_days]non funzionerà in quel caso.
Furqan Hashim,

1
Passare list-likes a .loc o [] con qualsiasi etichetta mancante solleverà KeyError in futuro, puoi usare .reindex () come alternativa. Vedi la documentazione qui: pandas.pydata.org/pandas-docs/stable/…
Dmitrii Magas

19

Un approccio alternativo è resample, che può gestire date duplicate oltre alle date mancanti. Per esempio:

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

resampleè un'operazione differita groupbycosì è necessario seguirla con un'altra operazione. In questo caso meanfunziona bene, ma è anche possibile utilizzare molti metodi diversi panda come max, sumecc

Ecco i dati originali, ma con una voce aggiuntiva per "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

Ed ecco i risultati:

             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

Ho lasciato le date mancanti come NaN per chiarire come funziona, ma puoi aggiungere fillna(0)per sostituire NaN con zeri come richiesto dall'OP o in alternativa usare qualcosa come interpolate()riempire con valori diversi da zero in base alle righe vicine.


6

Ecco un bel metodo per inserire le date mancanti in un dataframe, con la tua scelta fill_value, days_backcompilare e ordinare ( date_order) in base al quale ordinare il 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.