简体   繁体   中英

Recency weighted moving average on previous dates in pandas

I have the following df:

index = pd.to_datetime(['2017-03-01', '2017-03-01', '2017-02-15', '2017-02-01',
        '2017-01-20', '2017-01-20', '2017-01-20', '2017-01-02', 
        '2016-12-04', '2016-12-04', '2016-12-04', '2016-11-16'])

df = pd.DataFrame(data = {'val': [8, 1, 5, 2, 3 , 5, 9, 14, 13, 2, 1, 12],
               'group': ['one', 'two', 'one', 'one', 'two', 'two', 'one', 'two', 
               'two', 'one', 'one', 'two']}, 
               index=index)

df = df.sort_index()

             group val
2016-11-16   two   12
2016-12-04   two   13
2016-12-04   one    2
2016-12-04   one    1
2017-01-02   two   14
2017-01-20   two    3
2017-01-20   two    5
2017-01-20   one    9
2017-02-01   one    2
2017-02-15   one    5
2017-03-01   one    8
2017-03-01   two    1

In every group (one, two) I would like to a recency weighted mean of previous val. So for example looking at group one:

           group  val
2016-12-04   one    2
2016-12-04   one    1
2017-01-20   one    9
2017-02-01   one    2
2017-02-15   one    5
2017-03-01   one    8

For instance, for the date 2017-02-15 , I wish to calculate a new column having as a value for this date a recency weighted version (higher weights for closer dates in the past) of the previous values that are [2,9,1,2]. Notice there is the possibility to have dates multiple times within one group and those should get the same weight.

I thought pandas exponentially weighted function would be good for this. I figured that is the date within one group is the same I would first take the mean of those values so that I can apply a simple shift() later. I tried the following:

df =  df.reset_index().set_index(['index', 'group']).groupby(
      level=[0,1]).mean().reset_index().set_index('index')

Now if I would not be interested in recency weighting I could to something like

df = df.groupby('group')['val'].expanding().mean().groupby(level=0).shift()

and then merge with the original on date and group. But when I try to make use of pandas.ewma I am missing something like:

df.groupby('group')['val'].ewm(span=27).groupby(level=0).shift()

I can iterate through the groups:

grouped = df.groupby('group')['val']
for key, group in grouped:
    print pd.ewma(group, span=27).shift()

index
2016-12-04         NaN
2017-01-20    1.500000
2017-02-01    5.388889
2017-02-15    4.174589
2017-03-01    4.404414
Name: val, dtype: float64
index
2016-11-16          NaN
2016-12-04    12.000000
2017-01-02    12.518519
2017-01-20    13.049360
2017-03-01    10.529680

and then somehow merge on group and date with the original df but this seems overly complicated. Is there a better way to do this?

To perform your Recency Weighted Moving Average without needing to loop through groups and re-merge, you can use apply .

def rwma(group):
    # perform the ewma
    kwargs = dict(ignore_na=False, span=27, min_periods=0, adjust=True)
    result = group.ewm(**kwargs).mean().shift().reset_index()

    # rename the result column so that the merge goes smoothly
    result.rename(columns={result.columns[-1]: 'rwma'}, inplace=True)
    return result

recency = df.groupby('group')['val'].apply(rwma)

Test Code:

import pandas as pd

df = pd.DataFrame(data={
    'val': [8, 1, 5, 2, 3, 5, 9, 14, 13, 2, 1, 12],
    'group': ['one', 'two', 'one', 'one', 'two', 'two',
              'one', 'two', 'two', 'one', 'one', 'two']},
    index=pd.to_datetime([
        '2017-03-01', '2017-03-01', '2017-02-15', '2017-02-01',
        '2017-01-20', '2017-01-20', '2017-01-20', '2017-01-02',
        '2016-12-04', '2016-12-04', '2016-12-04', '2016-11-16'])
    ).sort_index()

recency = df.groupby('group')['val'].apply(rwma)
print(recency)

Results:

             index       rwma
group                        
one   0 2016-12-04        NaN
      1 2016-12-04   2.000000
      2 2017-01-20   1.481481
      3 2017-02-01   4.175503
      4 2017-02-15   3.569762
      5 2017-03-01   3.899694
two   0 2016-11-16        NaN
      1 2016-12-04  12.000000
      2 2017-01-02  12.518519
      3 2017-01-20  13.049360
      4 2017-01-20  10.251243
      5 2017-03-01   9.039866

Based on Stephen's aswer here is a working version:

def rwma(group):
    # perform the ewma
    kwargs = dict(ignore_na=False, span=27, min_periods=0, adjust=True)
    result = group.resample('1D').mean().ewm(**kwargs).mean().shift()
    result = result[group.index].reset_index()

    # rename the result column so that the merge goes smoothly
    result.rename(columns={result.columns[-1]: 'rwma'}, inplace=True)
    return result

recency = df.groupby('group')['val'].apply(rwma)
print(recency)

Output:

                 index       rwma
group                        
one   0 2016-12-04        NaN
      1 2016-12-04        NaN
      2 2017-01-20   1.500000
      3 2017-02-01   8.776518
      4 2017-02-15   4.016278
      5 2017-03-01   4.670166
two   0 2016-11-16        NaN
      1 2016-12-04  12.000000
      2 2017-01-02  12.791492
      3 2017-01-20  13.844843
      4 2017-01-20  13.844843
      5 2017-03-01   6.284914

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM