计算 Pandas 列中值的 3 个月滚动计数

Posted

技术标签:

【中文标题】计算 Pandas 列中值的 3 个月滚动计数【英文标题】:Calculate 3 Month Rolling Count of values in a column Pandas 【发布时间】:2021-12-08 07:37:28 【问题描述】:

我有如下dataframe(这是dataframe的简化版,但逻辑是一样的):

#MONTH = yyyy-mm-dd

    MONTH        User
0   2021-04-01   A
1   2021-04-01   B
2   2021-05-01   B
3   2021-06-01   A
4   2021-06-01   B
5   2021-07-01   A
6   2021-07-01   B
7   2021-08-01   A
8   2021-08-01   B

我想要的是计算用户是否在 3 个月滚动的基础上处于活动状态。

例如,用户B 如果我们考虑 6 月 (2021-06-01),我们可以看到他在 5 月和 4 月处于活跃状态,因此在 3M 滚动的基础上,他被认为在 6 月处于活跃状态。而同一时间段内的用户A,在三个月的其中一个月内没有活跃,因此在六月他将不会被视为活跃。

一个期望的输出是有一个列来统计每个月的活跃用户(300 万滚动),例如基于上述数据:

    MONTH        Active_User_Count
0   2021-04-01   NaN
1   2021-05-01   NaN
2   2021-06-01   1
3   2021-07-01   1
4   2021-08-01   2

我仍在努力了解滚动数据,所以如果有人能在这方面帮助我,那就太好了!提前致谢!

编辑 MONTH 列只有每个月的第一天的值,但当天有多个用户。所以没有 2021-04-30,都是每月的第一天。

【问题讨论】:

MONTH 列是字符串,还是 datetime.date 对象,还是其他? 听起来您正在尝试 groupby "User" 列,计算该用户是否在 3 个月滚动的基础上处于活动状态,然后在 groupby "MONTH" 上汇总活动用户列? @JoshuaVoskamp 它是一个日期时间对象 @JoshuaVoskamp 是的,简而言之,这就是我想要实现的目标! 如果您将'MONTH' 列对象类型更改为pandas.Timestamp(与df['MONTH'] = df['MONTH'].apply(pd.Timestamp) 一样简单),那么您可以使用df['MONTH'].diff() 来比较值 【参考方案1】:

好吧,让我们试试这个。 假设有一个名为dfpandas.DataFrame,它有一个MONTH 类型为pandas.Timestamp 的列,以及一个User 列,我们可以groupby

import pandas as pd
import numpy as np

df = #[however you got your data here]
df.MONTH = df.MONTH.apply(pd.Timestamp)

例如

>>> df
       MONTH User
0 2021-04-01    A
1 2021-04-01    B
2 2021-05-01    B
3 2021-06-01    A
4 2021-06-01    B
5 2021-07-01    A
6 2021-07-01    B
7 2021-08-01    A
8 2021-08-01    B

那么给定上面的内容,让我们做一个DataFrame来保存我们的结果,连续几个月从开始到结束输入DataFrame,并将活跃用户数列初始化为0:

res = pd.DataFrame(pd.date_range(df.MONTH.min(),df.MONTH.max(),freq='MS'),columns=['MONTH'])
res['Active_User_Count'] = 0
res = res.set_index('MONTH').sort_index()

现在添加值:

for user, frame in df.groupby(by='User'):
    # make a helper column, that has an indicator of whether the user
    # was active that month (value='both') or not (value='right_only')
    frame = frame.merge(
                     pd.Series(pd.date_range(start=frame.MONTH.min(),\
                                        end=frame.MONTH.max(),\
                                        freq='MS'),\
                               name='MONTH'),\
                     on='MONTH',how='outer',indicator=True)\
                 .set_index('MONTH').sort_index()
    # this is where the magic happens;
    # categorize the '_merge' results (0 = left_only, 1 = right_only, 2 = both)
    # then on a 3-wide rolling window, get the minimum value
    # check that it is greater than 1.5 (i.e. all three prev months
    # are _merge value 'both')
    # if it's not > 1.5, then the user wasn't active for all 3 months
    
    # finally take the result from that rolling.min.apply,
    # and funnel into a numpy.where array, which sets
    # 'Active_User_Count' of the in-process user frame
    # to an array of 1s and 0s
    frame['Active_User_Count'] = np.where(
        (frame._merge
              .astype('category').cat.codes
              .rolling(3).min().apply(lambda x: x > 1.5)), 1, 0)
    
    # add the current-user activity into the total result
    res.Active_User_Count[frame.index] += frame.Active_User_Count

# some re-formatting
res = res.reset_index().sort_index()

毕竟我们得到了我们的输出:

>>> res
       MONTH  Active_User_Count
0 2021-04-01                  0
1 2021-05-01                  0
2 2021-06-01                  1
3 2021-07-01                  1
4 2021-08-01                  2

TL;DR

这里有一个函数来做这件事

import pandas as pd
import numpy as np

def active_users(df):
    res = pd.DataFrame(pd.date_range(df.MONTH.min(),\
                                     df.MONTH.max(),\
                                     freq='MS'),\
                       columns=['MONTH'])
    res['Active_User_Count'] = 0
    res = res.set_index('MONTH').sort_index()
    
    for user, frame in df.groupby(by='User'):
            frame = frame.merge(pd.Series(
                                    pd.date_range(start=frame.MONTH.min(),\
                                            end=frame.MONTH.max(),\
                                            freq='MS'),\
                                    name='MONTH'),\
                                on='MONTH',\
                                how='outer',\
                                indicator=True)\
                         .set_index('MONTH').sort_index()
            frame['Active_User_Count'] = np.where(
                (frame._merge
                      .astype('category')
                      .cat.codes
                      .rolling(3).min().apply(lambda x: x > 1.5)), 1, 0)
            res.Active_User_Count[frame.index] += frame.Active_User_Count
    
    return res.reset_index().sort_index()

【讨论】:

它不是很漂亮,根据数据帧的大小,它可能无法执行,但它应该是您正在寻找的方向的开始。 这行得通 - 花了一些时间,因为我的数据框相当大,但输出是正确的!我认为这会更容易,非常感谢您的帮助! Re: 花了一些时间,这种方法使用 for 循环来迭代每个唯一用户,这对于大量用户来说效率很低。我确信有更好的方法;我不知道它是什么;)

以上是关于计算 Pandas 列中值的 3 个月滚动计数的主要内容,如果未能解决你的问题,请参考以下文章

更快地计算pandas中列表列中值的总出现次数?

每月每个类别的分组计数(当前月份与过去几个月的剩余时间)在 pandas 的单独列中

pandas groupby 滚动均值/中值删除缺失值

pandas 基于值而不是计数的窗口滚动计算

pandas 基于值而不是计数的窗口滚动计算

excel中使用CORREL函数计算两个时间序列数据列之间的滚动相关性(Rolling correlations)例如,计算两种商品销售额之间的3个月的滚动相关性