Apply rolling function to groupby over several columns

Viewed 3028

I'd like to apply rolling functions to a dataframe grouped by two columns with repeated date entries. Specifically, with both "freq" and "window" as datetime values, not simply ints.

In principle, I'm try to combine the methods from How to apply rolling functions in a group by object in pandas and pandas rolling sum of last five minutes.

Input

Here is a sample of the data, with one id=33 although we expect several id's.

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

Goal

Sample each 'id' every 2 days (freq='2d') and return the sum of total points for each item over the previous three days (window='3D'), end-date inclusive

Desired Output

            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

E.g. on the right-inclusive end-date 2017-02-13, we sample the 3-day period 2017-02-11 to 2017-02-13. In this period, id=33 had a sum of A points equal to 1+1+1+4 = 7

Attempts

An attempt of groupby with a pd.rolling_sum as follows didn't work, due to repeated dates

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

Also note that from the documentation http://pandas.pydata.org/pandas-docs/version/0.17.0/generated/pandas.rolling_apply.html 'window' is an int representing the size sample period, not the number of days to sample.

We can also try resampling and using last, however the desired look-back of 3 days doesn't seem to be used

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

Of course,setting up a loop over unique id's ID, selecting df_id = df[df['id']==ID], and summing over the periods does work but is computationally-intensive and doesn't exploit groupby's nice vectorization.

Thanks to @jezrael for good suggestions so far

Notes

Pandas version = 0.20.1

I'm a little confused as to why the documentation on rolling() here:https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.rolling.html suggests that the "window" parameter can be in an int or offset but on attempting df.rolling(window='3D',...) I getraise ValueError("window must be an integer") It appears that the above documentation is not consistent with the latest code for rolling's window from ./core/window.py : https://github.com/pandas-dev/pandas/blob/master/pandas/core/window.py

elif not is_integer(self.window):
            raise ValueError("window must be an integer")
2 Answers
Related