Добавить недостающие даты в фреймворк pandas

128

Мои данные могут иметь несколько событий на заданную дату или НИКАКИХ событий на дату. Я беру эти события, подсчитываю по дате и рисую их. Однако, когда я их рисую, две мои серии не всегда совпадают.

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

В приведенном выше коде idx становится диапазоном, скажем, 30 дат. 09-01-2013 - 30.09.2013 Однако в S может быть только 25 или 26 дней, потому что для данной даты не произошло никаких событий. Затем я получаю AssertionError, поскольку размеры не совпадают, когда я пытаюсь построить:

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

Как правильно с этим справиться? Хочу ли я удалить из IDX даты без значений или (что я бы предпочел) добавить в серию недостающую дату со счетом 0. Я бы предпочел иметь полный график 30 дней с 0 значениями. Если этот подход правильный, есть ли предложения, как начать? Мне нужна какая-то динамическая reindexфункция?

Вот фрагмент S ( df.groupby(['simpleDate']).size() ), нет записей для 04 и 05.

09-02-2013     2
09-03-2013    10
09-06-2013     5
09-07-2013     1
KHibma
источник

Ответы:

257

Вы можете использовать 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)

доходность

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
источник
23
reindexпотрясающая функция. Он может (1) переупорядочивать существующие данные для соответствия новому набору меток, (2) вставлять новые строки там, где ранее не существовало меток, (3) заполнять данные для отсутствующих меток (в том числе путем прямого / обратного заполнения) (4) выбирать строки по этикетке!
unutbu
@unutbu Это ответ на часть моего вопроса, спасибо! Но было интересно, знаете ли вы, как динамически создавать список с датами, у которых есть события?
Nick
2
Однако есть одна проблема (или ошибка) с переиндексированием: он не работает с датами до 01.01.1970, поэтому в этом случае df.resample () работает отлично.
Сергей Гульбин
2
вы можете использовать это вместо этого для idx, чтобы пропустить ввод даты начала и окончания вручную:idx = pd.date_range(df.index.min(), df.index.max())
Reveille
Перетащите
Harm te Molder
41

Более быстрое решение - использовать .asfreq(). Это не требует создания нового индекса для вызова .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
Я действительно предпочитаю этот метод; вы избегаете вызова, date_rangeпоскольку он неявно использует первый и последний индексы в качестве начала и конца (что вам почти всегда нужно).
Майкл Хейс
Очень чистый и профессиональный метод. Хорошо работает и с последующим использованием интерполяции.
мсарафзаде,
27

Одна из проблем заключается в том, что reindexпри наличии повторяющихся значений произойдет сбой. Допустим, мы работаем с данными с отметками времени, которые мы хотим проиндексировать по дате:

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

доходность

            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

Из-за повторяющейся 2016-11-16даты попытка переиндексации:

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

не работает с:

...
ValueError: cannot reindex from a duplicate axis

(это означает, что в индексе есть дубликаты, а не то, что он сам по себе дубликат)

Вместо этого мы можем использовать .locдля поиска записей для всех дат в диапазоне:

df.loc[all_days]

доходность

            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 при необходимости можно использовать для ряда столбцов для заполнения пробелов.

Ник Эдгар
источник
Есть идеи, что делать, если столбец Date содержит Blanksили NULLS? df.loc[all_days]в этом случае работать не будет.
Furqan Hashim
1
Передача list-like в .loc или [] с любой отсутствующей меткой вызовет KeyError в будущем, вы можете использовать .reindex () в качестве альтернативы. См. Документацию здесь: pandas.pydata.org/pandas-docs/stable/…
Дмитрий Магас
19

Альтернативный подход resample, который может обрабатывать повторяющиеся даты в дополнение к отсутствующим датам. Например:

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

resample- это отложенная операция, groupbyпоэтому вам нужно выполнить другую операцию. В этом случае meanработает хорошо, но вы также можете использовать многие другие методы панд, такие как max, sumи т. Д.

Вот исходные данные, но с дополнительной записью для «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

И вот результаты:

             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

Я оставил недостающие даты как NaN, чтобы прояснить, как это работает, но вы можете добавить, fillna(0)чтобы заменить NaN нулями по запросу OP, или, альтернативно, использовать что-то вроде interpolate()заполнения ненулевыми значениями на основе соседних строк.

Johne
источник
6

Вот хороший метод для заполнения недостающих дат во фрейме данных, на ваш выбор fill_value, days_backдля заполнения и порядка сортировки ( date_order) для сортировки фрейма данных:

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
ЭйТан ЛаВи
источник