Я хотел бы применить скользящие функции к кадру данных, сгруппированному в два столбца с повторяющимися записями даты. В частности, с значениями «freq» и «window» в качестве значений даты и времени, а не просто целочисленных значений.

В принципе, я пытаюсь объединить методы из Как применить скользящие функции в группе по объектам в pandas и скользящая сумма панд за последние пять минут.

< Сильный > Ввод

Вот пример данных с одним идентификатором = 33, хотя мы ожидаем несколько идентификаторов.

X = [{'date': '2017-02-05', 'id': 33, 'item': 'A', 'points': 20},
 {'date': '2017-02-05', 'id': 33, 'item': 'B', 'points': 10},
 {'date': '2017-02-06', 'id': 33, 'item': 'B', 'points': 10},
 {'date': '2017-02-11', 'id': 33, 'item': 'A', 'points': 1},
 {'date': '2017-02-11', 'id': 33, 'item': 'A', 'points': 1},
 {'date': '2017-02-11', 'id': 33, 'item': 'A', 'points': 1},
 {'date': '2017-02-13', 'id': 33, 'item': 'A', 'points': 4}]

# df = pd.DataFrame(X) and reindex df to pd.to_datetime(df['date'])

df

            id item  points
date                       
2017-02-05  33    A      20
2017-02-05  33    B      10
2017-02-06  33    B      10
2017-02-11  33    A       1
2017-02-11  33    A       1
2017-02-11  33    A       1
2017-02-13  33    A       4

< Сильный > Цель

Пример каждого 'id' каждые 2 дня (freq = '2d') и возврат суммы сумм баллов за каждый элемент за предыдущие три дня (window = '3D'), включая дату окончания

Желаемый результат

            id    A    B
date                       
2017-02-05  33    20   10
2017-02-07  33    20   30    
2017-02-09  33    0    10
2017-02-11  33    3    0
2017-02-13  33    7    0

Например. в конечную дату 2017-02-13, включающую право, мы выбираем 3-дневный период с 2017-02-11 по 2017-02-13. В этот период id = 33 имел сумму баллов A, равную 1 + 1 + 1 + 4 = 7

< Сильный > Попытки

Попытка сгруппировать с pd.rolling_sum следующим образом не сработала из-за повторяющихся дат

df.groupby(['id', 'item'])['points'].apply(pd.rolling_sum, freq='4D', window=3)
ValueError: cannot reindex from a duplicate axis

Также обратите внимание, что из документации http: // pandas.pydata.org/pandas-docs/version/0.17.0/generated/pandas.rolling_apply.html 'window' - это целое число, представляющее период выборки размера, а не количество дней для выборки.

Мы также можем попробовать пересэмплировать и использовать last, однако желаемый возврат в 3 дня, похоже, не используется

df.groupby(['id', 'item'])['points'].resample('2D', label='right', closed='right').\
apply(lambda x: x.last('3D').sum())

id  item  date      
33  A     2017-02-05    20
          2017-02-07     0
          2017-02-09     0
          2017-02-11     3
          2017-02-13     4
    B     2017-02-05    10
          2017-02-07    10

Конечно, установка цикла по уникальному идентификатору ID, выбор df_id = df [df ['id'] == ID] и суммирование по периодам работает, но требует больших вычислительных ресурсов и не использует хорошую векторизацию groupby.

Спасибо @jezrael за хорошие предложения до сих пор

< Сильный > Примечания

Версия для панд = 0.20.1

Я немного сбит с толку относительно того, почему документация по Rolling () здесь: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.rolling.html предполагает, что параметр "окна" может быть в int или offset, но при попытке df.rolling (window = '3D', ...) я получаю повысить ValueError ("окно должно быть целым числом") Похоже, что приведенная выше документация не соответствует последнему коду для скользящего окна из ./core/window.py: https://github.com/pandas-dev/ панды / блоб / ведущий / панды / ядро / window.py

elif not is_integer(self.window):
            raise ValueError("window must be an integer")
3
Quetzalcoatl 29 Май 2017 в 06:42

2 ответа

Лучший ответ
  • Проще всего обрабатывать resample и rolling с частотами даты, когда у нас есть одноуровневый индекс даты и времени.
  • Однако я не могу pivot / unstack надлежащим образом, не имея дело с дубликатами A / B, поэтому я groupby и sum
  • Я unstack один уровень date, поэтому я могу fill_value=0. В настоящее время я не могу fill_value=0, когда у меня unstack больше одного уровня за раз. Я восполняю это с помощью транспонирования T
  • Теперь, когда у меня есть один уровень в индексе, я переиндексирую с диапазоном дат от минимальных до максимальных значений в индексе
  • Наконец, я делаю скользящую 3-дневную сумму и делаю выборку каждые 2 дня с resample
  • Я очищаю это с помощью небольшого количества переименованных индексов и еще одного центра.

s = df.set_index(['id', 'item'], append=True).points
s = s.groupby(level=['date', 'id', 'item']).sum()

d = s.unstack('date', fill_value=0).T
tidx = pd.date_range(d.index.min(), d.index.max())
d = d.reindex(tidx, fill_value=0)

d1 = d.rolling('3D').sum().resample('2D').first().astype(d.dtypes).stack(0)
d1 = d1.rename_axis(['date', 'id']).rename_axis(None, 1)
print(d1)

                A   B
date       id        
2017-02-05 33  20  10
2017-02-07 33  20  20
2017-02-09 33   0   0
2017-02-11 33   3   0
2017-02-13 33   7   0
3
piRSquared 31 Май 2017 в 21:53
df = pd.DataFrame(X) 

# group sum by day
df = df.groupby(['date', 'id', 'item'])['points'].sum().reset_index().sort_values(['date', 'id', 'item'])

# convert index to datetime index
df = df.set_index('date')
df.index = DatetimeIndex(df.index)

# rolloing sum by 3D
df['pointsum'] = df.groupby(['id', 'item']).transform(lambda x: x.rolling(window='3D').sum())

# reshape dataframe
df = df.reset_index().set_index(['date', 'id', 'item'])['pointsum'].unstack().reset_index().set_index('date').fillna(0)

df
2
xmduhan 1 Июн 2017 в 02:37