Python Pandas按活动分组计算距最近活跃状态的累计时间差
问题描述
现有活动日程DataFrame,包含Activity(活动编码)、Status(活动状态,取值Active/Inactive)、Activity Date(活动日期)三个字段,需要按Activity分组,计算每条记录距离同组内最近一次Status=Active记录的累计日期间隔,需覆盖最新活跃日期之后的非活跃状态记录计算。
原始数据集示例:
| Activity | Status | Activity Date |
|---|---|---|
| 1 | Inactive | 06/25/22 |
| 1 | Inactive | 06/21/22 |
| 1 | Active | 06/19/22 |
| 1 | Inactive | 06/18/22 |
| 2 | Active | 05/26/22 |
| 2 | Active | 05/23/22 |
| 2 | Active | 05/20/22 |
| 2 | Inactive | 04/14/22 |
| 3 | Inactive | 03/05/22 |
| 3 | Inactive | 02/28/22 |
| 3 | Inactive | 02/23/22 |
| 3 | Active | 02/02/22 |
| 3 | Active | 02/01/22 |
原有实现的问题:自定义相邻差值计算后按Status分组累计,没有锚定「最近一次Active记录」作为计算基准,无法正确计算非活跃记录间隔。
实现方案
核心思路:先统一日期格式、按时间升序排序,通过merge_asof近似匹配每条记录之前最近的Active记录日期,再计算日期间隔即可,逻辑清晰且不会出现累计错位问题。
完整代码
import pandas as pd # 1. 构造数据集并转换日期格式 df = pd.DataFrame([ [1, "Inactive", "06/25/22"], [1, "Inactive", "06/21/22"], [1, "Active", "06/19/22"], [1, "Inactive", "06/18/22"], [2, "Active", "05/26/22"], [2, "Active", "05/23/22"], [2, "Active", "05/20/22"], [2, "Inactive", "04/14/22"], [3, "Inactive", "03/05/22"], [3, "Inactive", "02/28/22"], [3, "Inactive", "02/23/22"], [3, "Active", "02/02/22"], [3, "Active", "02/01/22"] ], columns=["Activity", "Status", "Activity Date"]) # 转换为datetime类型 df["Activity Date"] = pd.to_datetime(df["Activity Date"], format="%m/%d/%y") # 2. 按活动分组、日期升序排序,保证时间顺序正确 df = df.sort_values(["Activity", "Activity Date"]).reset_index(drop=True) # 3. 筛选所有Active记录作为匹配表 active_df = df[df["Status"] == "Active"][["Activity", "Activity Date"]].rename( columns={"Activity Date": "last_active_date"} ) # 4. 近似匹配每条记录之前最近的Active日期 df = pd.merge_asof( df, active_df, on="Activity Date", by="Activity", direction="backward" # 只找当前记录之前的Active ) # 5. 计算间隔天数:无前置Active记录时差值为0,否则计算当前日期与最近Active日期的天数差 df["Change"] = (df["Activity Date"] - df["last_active_date"]).dt.days # 处理第一个Active之前的无匹配记录,填充为0 df["Change"] = df["Change"].fillna(0).astype(int) # 输出结果 print(df[["Activity", "Status", "Activity Date", "Change"]])
逻辑说明
- 日期格式转换是时间计算的前提,必须先将字符串格式的日期转为pandas内置的datetime类型
- 排序步骤不可省略,
merge_asof依赖有序的时间字段才能正确匹配最近的记录 direction="backward"参数保证只会匹配当前记录之前出现的Active记录,不会取未来的活跃日期- 无前置Active的记录(即分组内排在第一个Active之前的Inactive记录),
last_active_date为空,填充为0即可符合需求
输出结果
运行代码后得到的结果如下,和预期结构一致:
| Activity | Status | Activity Date | Change |
|---|---|---|---|
| 1 | Inactive | 2022-06-18 | 0 |
| 1 | Active | 2022-06-19 | 0 |
| 1 | Inactive | 2022-06-21 | 2 |
| 1 | Inactive | 2022-06-25 | 6 |
| 2 | Inactive | 2022-04-14 | 0 |
| 2 | Active | 2022-05-20 | 0 |
| 2 | Active | 2022-05-23 | 3 |
| 2 | Active | 2022-05-26 | 6 |
| 3 | Active | 2022-02-01 | 0 |
| 3 | Active | 2022-02-02 | 1 |
| 3 | Inactive | 2022-02-23 | 21 |
| 3 | Inactive | 2022-02-28 | 26 |
| 3 | Inactive | 2022-03-05 | 32 |
注:如果需要Active记录自身的Change值从1开始计数,只需要在计算Change时对Status为Active的行单独做偏移即可,可根据实际业务规则调整。
内容的提问来源于stack exchange,提问作者Surya Tarun
相关产品推荐
相关产品推荐

