如何矢量化 Pandas 函数来计算属于一个组且介于两个日期之间的行?

How do I vectorize a Pandas function that counts rows belonging to a group and falling between two dates?

我有以下 table,我想计算 2020 年每一天每个客户的活跃工作数量。如果日期落在 start_date 和 end_date.

工作 客户 start_date end_date
AA001 阿尔法 2020/12/19 2020/12/28
AA002 阿尔法 2020/04/03 2020/10/10
AA003 布拉沃 2020/10/11 2020/10/11
AA004 查理 2020/04/06 2020/11/15
AA005 阿尔法 2020/04/01 2020/04/30
AA006 查理 2020/05/01 2020/06/03
AA007 布拉沃 2020/06/04 2020/06/17
AA008 布拉沃 2020/06/18 2020/07/01
AA009 查理 2020/07/02 2020/08/04
AA010 阿尔法 2020/05/05 2020/08/06
AA011 布拉沃 2020/10/12 2020/11/04

例如,以下是 4 月初客户 ALPHA 的活跃工作数量:

日期 客户 活跃职位
阿尔法 2020-04-01 1
阿尔法 2020-04-02 1
阿尔法 2020-04-03 2
阿尔法 2020-04-04 2
阿尔法 2020-04-05 2
阿尔法 2020-04-06 2
阿尔法 2020-04-07 2
阿尔法 2020-04-08 2
阿尔法 2020-04-09 2
阿尔法 2020-04-10 2

我可以使用嵌套循环解决这个问题,例如

groups = df.groupby(["client"])   
dates = pd.date_range('2020-01-01','2020-12-01', freq='D')   
 
for client, jobs in groups:  
    for date in dates:  
        active_jobs = jobs.loc[(jobs.start_date <= date) & (jobs.end_date >= date)]  
        print(date,client,len(active_jobs))

(说明:按客户对行进行分组,构建一个日期列表,然后对于每个客户的每个日期,find/count start_date <= date 和 end_date > = 日期。)

当然我的真实数据比这个大很多,循环效率很低。如何重写我的查询以利用矢量化?

广播方法

检查 start_dateend_date 列之间是否包含 dates,这将创建一个布尔掩码,现在我们从这个掩码创建一个新的数据框并分配列将名称命名为相应的日期,然后 group 此数据框 client 并使用 sum 聚合以计算每个客户每天的活动工作数

start, end = df[['start_date', 'end_date']].to_numpy().T
dates = pd.date_range('2020-01-01','2020-12-01', freq='D').to_numpy()

m = (start[:, None] <= dates) & (end[:, None] >= dates)
s = pd.DataFrame(m, columns=dates).groupby(df['client']).sum().stack()

堆叠后包含 active_jobs 计数的结果系列看起来像

>>> s

client             
ALPHA    2020-01-01    0
         2020-01-02    0
         2020-01-03    0
         2020-01-04    0
         2020-01-05    0
                      ..
CHARLIE  2020-11-27    0
         2020-11-28    0
         2020-11-29    0
         2020-11-30    0
         2020-12-01    0
Length: 1008, dtype: int64

正在检查客户 ALPHAAPRIL

月份的活动工作
>>> s.loc[pd.IndexSlice['ALPHA', '2020-04-01':]]

client            
ALPHA   2020-04-01    1
        2020-04-02    1
        2020-04-03    2
        2020-04-04    2
        2020-04-05    2
        2020-04-06    2
        2020-04-07    2
        2020-04-08    2
        2020-04-09    2
        2020-04-10    2
        2020-04-11    2
        2020-04-12    2
        2020-04-13    2
        2020-04-14    2
        2020-04-15    2
        2020-04-16    2
        2020-04-17    2
        2020-04-18    2
        2020-04-19    2
        2020-04-20    2
        2020-04-21    2
        2020-04-22    2
        2020-04-23    2
        2020-04-24    2
        2020-04-25    2
        2020-04-26    2
        2020-04-27    2
        2020-04-28    2
        2020-04-29    2
        2020-04-30    2
dtype: int64

PS:虽然使用广播速度更快,但需要足够的内存来保存中间布尔掩码。在使用此方法之前,您还必须将 start_dateend_date 列转换为 pandas datetime 格式