Fügen Sie fehlende Daten zum Pandas-Datenrahmen hinzu

126

Meine Daten können an einem bestimmten Datum mehrere Ereignisse oder an einem Datum KEINE Ereignisse enthalten. Ich nehme diese Ereignisse, zähle sie nach Datum und zeichne sie auf. Wenn ich sie jedoch zeichne, stimmen meine beiden Serien nicht immer überein.

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

Im obigen Code wird idx zu einem Bereich von beispielsweise 30 Daten. 09-01-2013 bis 09-30-2013 S kann jedoch nur 25 oder 26 Tage haben, da für ein bestimmtes Datum keine Ereignisse aufgetreten sind. Ich erhalte dann einen AssertionError, da die Größen nicht übereinstimmen, wenn ich versuche zu zeichnen:

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

Was ist der richtige Weg, um dies anzugehen? Möchte ich Datumsangaben ohne Werte aus IDX entfernen oder (was ich lieber tun möchte) das fehlende Datum mit einer Anzahl von 0 zur Serie hinzufügen? Ich möchte lieber ein vollständiges Diagramm von 30 Tagen mit 0 Werten haben. Wenn dieser Ansatz richtig ist, gibt es Vorschläge für den Einstieg? Benötige ich eine Art Dynamik?reindex Funktion?

Hier ist ein Ausschnitt von S ( df.groupby(['simpleDate']).size() ), beachten Sie keine Einträge für 04 und 05.

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

Antworten:

252

Sie könnten verwenden 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)

ergibt

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
quelle
23
reindexist eine erstaunliche Funktion. Es kann (1) vorhandene Daten neu anordnen, um sie mit einem neuen Satz von Beschriftungen abzugleichen, (2) neue Zeilen einfügen, in denen zuvor keine Beschriftung vorhanden war, (3) Daten für fehlende Beschriftungen füllen (einschließlich durch Vorwärts- / Rückwärtsfüllung) (4) Zeilen auswählen per Etikett!
Unutbu
@unutbu Dies beantwortet einen Teil einer Frage, die ich auch hatte, danke! Aber haben Sie sich gefragt, ob Sie wissen, wie Sie dynamisch eine Liste mit Daten erstellen können, die Ereignisse enthalten?
Nick Duddy
2
Es gibt jedoch ein Problem (oder einen Fehler) bei der Neuindizierung: Es funktioniert nicht mit Daten vor dem 1.1.1970, daher funktioniert df.resample () in diesem Fall perfekt.
Sergey Gulbin
1
Sie können dies stattdessen für idx verwenden, um die manuelle Eingabe von Start- und Enddaten zu überspringen:idx = pd.date_range(df.index.min(), df.index.max())
Reveille
Klicken
Harm te Molder
40

Eine schnellere Problemumgehung ist die Verwendung .asfreq(). Dies erfordert nicht die Erstellung eines neuen Index zum Aufrufen .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
quelle
1
Ich bevorzuge diese Methode wirklich; Sie müssen nicht aufrufen, date_rangeda implizit der erste und der letzte Index als Start und Ende verwendet werden (was Sie fast immer wollen würden).
Michael Hays
Sehr saubere und professionelle Methode. Funktioniert auch gut mit der anschließenden Interpolation.
Msarafzadeh
26

Ein Problem ist, dass dies reindexfehlschlägt, wenn doppelte Werte vorhanden sind. Angenommen, wir arbeiten mit zeitgestempelten Daten, die wir nach Datum indizieren möchten:

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

ergibt

            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

Aufgrund des doppelten 2016-11-16Datums wurde versucht, neu zu indizieren:

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

schlägt fehl mit:

...
ValueError: cannot reindex from a duplicate axis

(Dies bedeutet, dass der Index Duplikate enthält und nicht, dass er selbst ein Dup ist.)

Stattdessen können wir .locEinträge für alle Daten im Bereich nachschlagen:

df.loc[all_days]

ergibt

            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 kann für die Spaltenreihe verwendet werden, um bei Bedarf Lücken zu füllen.

Nick Edgar
quelle
Irgendeine Idee, was zu tun ist, wenn die Datumsspalte Blanksoder enthält NULLS? df.loc[all_days]wird in diesem Fall nicht funktionieren.
Furqan Hashim
1
Wenn Sie Listen-Likes mit einem fehlenden Label an .loc oder [] übergeben, wird KeyError in Zukunft ausgelöst. Alternativ können Sie auch .reindex () verwenden. Siehe die Dokumentation hier: pandas.pydata.org/pandas-docs/stable/…
Dmitrii Magas
19

Ein alternativer Ansatz besteht darin resample, zusätzlich zu fehlenden Daten auch doppelte Daten zu verarbeiten. Beispielsweise:

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

resampleist eine verzögerte Operation wie diese, groupbydaher müssen Sie eine weitere Operation ausführen. In diesem Fall meangut funktioniert, aber Sie können auch viele andere Pandas Methoden wie verwenden max, sumetc.

Hier sind die Originaldaten, jedoch mit einem zusätzlichen Eintrag für '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

Und hier sind die Ergebnisse:

             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

Ich habe die fehlenden Daten als NaNs belassen, um zu verdeutlichen, wie dies funktioniert. Sie können jedoch hinzufügen fillna(0), um NaNs durch Nullen zu ersetzen, wie vom OP angefordert, oder alternativ so etwas wie interpolate()das Füllen mit Werten ungleich Null basierend auf den benachbarten Zeilen verwenden.

JohnE
quelle
6

Hier ist eine schöne Methode fehlenden Daten in einem Datenrahmen zu füllen, mit Ihrer Wahl fill_value, days_backin zu füllen, und Sortierreihenfolge ( date_order) , mit der die Datenrahmen zu sortieren:

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
quelle