Dodaj brakujące daty do pandy dataframe

129

Moje dane mogą mieć wiele wydarzeń w określonym dniu lub ŻADNYCH wydarzeń w danym dniu. Biorę te wydarzenia, liczę według daty i wykreślam je. Jednak kiedy je wykreślam, moje dwie serie nie zawsze pasują.

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

W powyższym kodzie idx staje się zakresem powiedzmy 30 dat. 09-01-2013 do 09-30-2013 Jednak S może mieć tylko 25 lub 26 dni, ponieważ żadne wydarzenia nie miały miejsca w danym dniu. Otrzymuję wtedy AssertionError, ponieważ rozmiary nie pasują do siebie, gdy próbuję wykreślić:

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

Jaki jest właściwy sposób rozwiązania tego problemu? Czy chcę usunąć daty bez wartości z IDX, czy (co wolałbym zrobić) dodać do serii brakującą datę z liczbą 0. Wolałbym mieć pełny wykres 30 dni z 0 wartościami. Jeśli to podejście jest słuszne, czy masz jakieś sugestie, jak zacząć? Czy potrzebuję jakiejś reindexfunkcji dynamicznej ?

Oto fragment S ( df.groupby(['simpleDate']).size() ), nie zauważ żadnych wpisów dla 04 i 05.

09-02-2013     2
09-03-2013    10
09-06-2013     5
09-07-2013     1
KHibma
źródło

Odpowiedzi:

258

Możesz użyć 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)

plony

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
źródło
23
reindexto niesamowita funkcja. Może (1) zmienić kolejność istniejących danych, aby dopasować je do nowego zestawu etykiet, (2) wstawić nowe wiersze, w których wcześniej nie istniała żadna etykieta, (3) wypełnić dane dla brakujących etykiet, (w tym przez wypełnienie do przodu / do tyłu) (4) wybrać wiersze według etykiety!
unutbu
@unutbu To odpowiada na część pytania, które też miałem, dzięki! Ale zastanawiałeś się, czy wiesz, jak dynamicznie tworzyć listę z datami, które mają wydarzenia?
Nick Duddy,
2
Jest jednak jeden problem (lub błąd) z reindeksem: nie działa z datami sprzed 01.01.1970, więc w tym przypadku df.resample () działa idealnie.
Sergey Gulbin
2
możesz użyć tego zamiast idx, aby pominąć ręczne wprowadzanie dat rozpoczęcia i zakończenia:idx = pd.date_range(df.index.min(), df.index.max())
Reveille
Upuszczenie linku do dokumentacji tutaj, aby zaoszczędzić ci wyszukiwania: pandas.pydata.org/pandas-docs/stable/reference/api/…
Harm te Molder
41

Szybszym obejściem jest użycie .asfreq(). Nie wymaga to tworzenia nowego indeksu do wywołania .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
źródło
1
Naprawdę wolę tę metodę; unikasz konieczności wywoływania, date_rangeponieważ niejawnie używa pierwszego i ostatniego indeksu jako początku i końca (co prawie zawsze chcesz).
Michael Hays
Bardzo czysta i profesjonalna metoda. Działa dobrze również z późniejszym użyciem interpolatu.
msarafzadeh
27

Jednym z problemów jest reindexniepowodzenie, jeśli istnieją zduplikowane wartości. Załóżmy, że pracujemy z danymi ze znacznikiem czasu, które chcemy indeksować według daty:

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

plony

            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

Ze względu na zduplikowaną 2016-11-16datę próba ponownego zindeksowania:

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

zawodzi z:

...
ValueError: cannot reindex from a duplicate axis

(przez to oznacza, że ​​indeks ma duplikaty, a nie że sam jest dupkiem)

Zamiast tego możemy użyć .locdo wyszukania wpisów dla wszystkich dat w zakresie:

df.loc[all_days]

plony

            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 może być użyty w serii kolumn do wypełnienia pustych miejsc w razie potrzeby.

Nick Edgar
źródło
Masz pomysł, co zrobić, jeśli kolumna Data zawiera Blankslub NULLS? df.loc[all_days]nie zadziała w takim przypadku.
Furqan Hashim
1
Przekazanie list-like do .loc lub [] z jakąkolwiek brakującą etykietą spowoduje w przyszłości zgłoszenie błędu KeyError. Możesz użyć .reindex () jako alternatywy. Zobacz dokumentację tutaj: pandas.pydata.org/pandas-docs/stable/ ...
Dmitrii Magas
19

Alternatywnym podejściem jest to resample, że oprócz brakujących dat można obsłużyć zduplikowane daty. Na przykład:

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

resamplejest operacją odroczoną, groupbywięc musisz po niej wykonać inną operację. W tym przypadku meandziała dobrze, ale można również korzystać z wielu innych metod, takich jak pandy max, sumitp

Oto oryginalne dane, ale z dodatkowym wpisem „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

A oto wyniki:

             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

Pozostawiłem brakujące daty jako NaN, aby wyjaśnić, jak to działa, ale możesz dodać, fillna(0)aby zastąpić NaN zerami zgodnie z żądaniem OP lub alternatywnie użyć czegoś takiego, jak interpolate()wypełnienie wartościami niezerowymi na podstawie sąsiednich wierszy.

JohnE
źródło
6

Oto fajna metoda wypełniania brakujących dat w ramce danych, z wyborem fill_value, days_backwypełnieniem i kolejnością sortowania ( date_order), według której sortować ramkę danych:

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
źródło