考虑缺失值的Pandas滚动聚合

ruyhziif  于 2023-08-01  发布在  其他
关注(0)|答案(1)|浏览(98)

这是我的previous question的扩展。
在那里,我询问了关于结合groupby和滚动聚合的问题。@mozway非常友好地提供了一个优雅的解决方案。但是,我忘了说我需要考虑数据中的差距。
这是几年来每个客户的月度数据。
在下面的代码片段中,是客户2的相同数据,我们没有期间“200102”(2001年2月)的数据。在最初的问题中,我简单地将周期表示为1,2,3...所有时期的数据都在那里。

df=pd.DataFrame({'cust_id': [1,1,1,1,1,1,2,2,2,2,2],
                 'period' : [200010,200011,200012,200101,200102,200103,200010,200011,200012,200101,200103],
                 'volume' : [1,2,3,4,5,6,7,8,9,10,12],
                 'num_transactions': [3,4,5,6,7,8,9,10,11,12,13]})

字符串
输出将是:

out=pd.DataFrame({'cust_id': [1,1,1,1,1,1,2,2,2,2,2],
                   'period' : [200010,200011,200012,200101,200102,200103,200010,200011,200012,200101,200103],
                  'max_vol_3' : [None, None, 3,4,5,6,None,None,9,10,None],
                  'max_vol_6' :[None,None,None,None,None,6,None,None,None,None,None],
                  'sum_trans_3': [None, None, 12, 15, 18, 21, None, None, 30, 33, None]})


怎么办?
链接文章中的解决方案通过简单地考虑前面的行来计算滚动聚合。但不幸的是,某些时期的数据存在差距。
编辑:
我意识到我的措辞很含糊。
从本质上讲,我想应用一个最小阈值-如果,比如说,3个月的窗口中缺少1个值,那么这3个月的统计数据将被设置为None。
我会强制执行,至少3个月用于3个月的总结和至少5个月用于6个月的总结。

mlnl4t2r

mlnl4t2r1#

我试了一下,其实并不简单。您不能在滚动中使用月度期间,因为这些期间不是固定的(29到31天)。所以我用几个月来作弊。
为了方便使用,我将所有内容都封装在一个函数中:

def cust_rolling(df, values_col, period_col, group_col, windows, functions, format='%Y%m', freq='M'):
    # we can't use monthly periods in rolling, fake it using seconds
    p = pd.to_datetime(df[period_col], format=format).dt.to_period(freq)
    df['tmp_period'] = pd.to_timedelta(p.sub(p.min()).apply(lambda x: x.n), unit='s')

    # set up rolling
    r = (df.set_index('tmp_period')
           .groupby(group_col)[values_col]
           .rolling
         )

    # compute for each function and each window
    tmp = pd.concat({f'{f}_{values_col}_{x}': r(f'{x}s').agg(f).groupby(group_col).tail(1-x)
                     for f in functions for x in windows}, axis=1)
    # merge to original data
    return df.merge(tmp.reset_index(), how='left').drop(columns='tmp_period')

out = (df
   .pipe(cust_rolling, 'volume', 'period', 'cust_id', [3, 6], ['max'])
   .pipe(cust_rolling, 'num_transactions', 'period', 'cust_id', [3], ['sum'])
)

字符串
输出量:

cust_id  period  volume  num_transactions  max_volume_3  max_volume_6  sum_num_transactions_3
0         1  200010       1                 3           NaN           NaN                     NaN
1         1  200011       2                 4           NaN           NaN                     NaN
2         1  200012       3                 5           3.0           NaN                    12.0
3         1  200101       4                 6           4.0           NaN                    15.0
4         1  200102       5                 7           5.0           NaN                    18.0
5         1  200103       6                 8           6.0           6.0                    21.0
6         2  200010       7                 9           NaN           NaN                     NaN
7         2  200011       8                10           NaN           NaN                     NaN
8         2  200012       9                11           9.0           NaN                    30.0
9         2  200101      10                12          10.0           NaN                    33.0
10        2  200103      12                13          12.0           NaN                    25.0

在缺失周期时强制NaN

def cust_rolling(df, values_col, period_col, group_col, windows, functions, format='%Y%m', freq='M'):
    # we can't use monthly periods in rolling, fake it using seconds
    p = pd.to_datetime(df[period_col], format=format).dt.to_period(freq)
    df['tmp_period'] = pd.to_timedelta(p.sub(p.min()).apply(lambda x: x.n), unit='s')

    # set up rolling
    r = (df.set_index('tmp_period')
           .groupby(group_col)[values_col]
           .rolling
         )
    tmp = pd.concat({f'{f.__name__}_{values_col}_{x}': r(f'{x}s').agg(f).groupby(group_col).tail(1-x)
                     for f in functions for x in windows}, axis=1)
    return df.merge(tmp.reset_index(), how='left').drop(columns='tmp_period')

def max2(s):
    if s.index.to_series().diff().gt('1s').any():
        return np.nan
    return s.max()

def sum2(s):
    if s.index.to_series().diff().gt('1s').any():
        return np.nan
    return s.sum()

out = (df
   .pipe(cust_rolling, 'volume', 'period', 'cust_id', [3, 6], [max2])
   .pipe(cust_rolling, 'num_transactions', 'period', 'cust_id', [3], [sum2])
)


输出量:

cust_id  period  volume  num_transactions  max2_volume_3  max2_volume_6  sum2_num_transactions_3
0         1  200010       1                 3            NaN            NaN                      NaN
1         1  200011       2                 4            NaN            NaN                      NaN
2         1  200012       3                 5            3.0            NaN                     12.0
3         1  200101       4                 6            4.0            NaN                     15.0
4         1  200102       5                 7            5.0            NaN                     18.0
5         1  200103       6                 8            6.0            6.0                     21.0
6         2  200010       7                 9            NaN            NaN                      NaN
7         2  200011       8                10            NaN            NaN                      NaN
8         2  200012       9                11            9.0            NaN                     30.0
9         2  200101      10                12           10.0            NaN                     33.0
10        2  200103      12                13            NaN            NaN                      NaN

相关问题