如何矢量化 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_date
和 end_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
正在检查客户 ALPHA
在 APRIL
月份的活动工作
>>> 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_date
和 end_date
列转换为 pandas datetime
格式
我有以下 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_date
和 end_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
正在检查客户 ALPHA
在 APRIL
>>> 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_date
和 end_date
列转换为 pandas datetime
格式