按ID分组统计连续零值,标记连续3天及以上零值事件
按ID分组标记连续3天及以上零值的Pandas实现
问题描述
现有如下Pandas DataFrame,需要按id分组,检查amount字段中是否存在至少3个连续零值的日期区间。若存在,需标记该事件(新增threeplus_consecutivedays_zeros_flag字段)并记录事件发生日期(新增flag_date字段)。
原始DataFrame
| id | date | amount |
|---|---|---|
| 1 | 2023-01-01 | 20 |
| 1 | 2023-01-02 | 0 |
| 1 | 2023-01-03 | 0 |
| 1 | 2023-01-04 | 10 |
| 1 | 2023-01-05 | 15 |
| 1 | 2023-01-06 | 0 |
| 1 | 2023-01-07 | 0 |
| 1 | 2023-01-08 | 0 |
| 1 | 2023-01-09 | 0 |
| 1 | 2023-01-10 | 8 |
| 2 | 2023-01-01 | 25 |
| 2 | 2023-01-02 | 32 |
| 2 | 2023-01-03 | 0 |
| 2 | 2023-01-04 | 12 |
| 2 | 2023-01-05 | 0 |
| 2 | 2023-01-06 | 0 |
| 2 | 2023-01-07 | 0 |
| 2 | 2023-01-08 | 13 |
| 2 | 2023-01-09 | 0 |
| 2 | 2023-01-10 | 12 |
目标DataFrame
| id | date | amount | threeplus_consecutivedays_zeros_flag | flag_date |
|---|---|---|---|---|
| 1 | 2023-01-01 | 20 | 0 | |
| 1 | 2023-01-02 | 0 | 0 | |
| 1 | 2023-01-03 | 0 | 0 | |
| 1 | 2023-01-04 | 10 | 0 | |
| 1 | 2023-01-05 | 15 | 0 | |
| 1 | 2023-01-06 | 0 | 0 | |
| 1 | 2023-01-07 | 0 | 0 | |
| 1 | 2023-01-08 | 0 | 1 | 2023-01-08 |
| 1 | 2023-01-09 | 0 | 1 | 2023-01-08 |
| 1 | 2023-01-10 | 8 | 0 | |
| 2 | 2023-01-01 | 25 | 0 | |
| 2 | 2023-01-02 | 32 | 0 | |
| 2 | 2023-01-03 | 0 | 0 | |
| 2 | 2023-01-04 | 12 | 0 | |
| 2 | 2023-01-05 | 0 | 0 | |
| 2 | 2023-01-06 | 0 | 0 | |
| 2 | 2023-01-07 | 0 | 1 | 2023-01-07 |
| 2 | 2023-01-08 | 13 | 0 | |
| 2 | 2023-01-09 | 0 | 0 | |
| 2 | 2023-01-10 | 12 | 0 |
已尝试统计数组中的连续零值,但不清楚如何结合groupby操作实现需求。
解决方案
通过分组标记连续零值区间,再计算区间长度,最后标记符合条件的区间并设置事件日期,完整代码实现如下:
import pandas as pd # 构造原始DataFrame df = pd.DataFrame({ 'id': [1]*10 + [2]*10, 'date': pd.date_range('2023-01-01', periods=10).tolist()*2, 'amount': [20,0,0,10,15,0,0,0,0,8,25,32,0,12,0,0,0,13,0,12] }) # 1. 确保date为datetime类型(若原始数据是字符串格式) df['date'] = pd.to_datetime(df['date']) # 2. 按id分组,标记连续零值的区间 df['is_zero'] = df['amount'] == 0 # 为每个连续的零值/非零值区间生成唯一分组key df['zero_group'] = df.groupby('id')['is_zero'].apply(lambda x: x.ne(x.shift()).cumsum()) # 3. 统计每个区间的长度、是否为零值区间,以及符合条件的事件日期 group_stats = df.groupby(['id', 'zero_group']).agg( group_length=('is_zero', 'size'), flag_date=('date', lambda x: x.iloc[2] if len(x)>=3 else pd.NaT) ).reset_index() # 4. 将统计结果合并回原表 df = df.merge(group_stats[['id', 'zero_group', 'group_length', 'flag_date']], on=['id', 'zero_group'], how='left') # 5. 生成flag字段:仅零值区间且长度≥3时标记为1 df['threeplus_consecutivedays_zeros_flag'] = ((df['is_zero']) & (df['group_length'] >=3)).astype(int) # 6. 清理临时列,格式化日期字段 df['flag_date'] = df['flag_date'].dt.strftime('%Y-%m-%d').fillna('') df = df.drop(['is_zero', 'zero_group', 'group_length'], axis=1) # 输出结果 print(df)
代码解释
- 连续区间标记:通过
is_zero标记零值行,再用cumsum()为同一id下的连续零值/非零值区间生成唯一分组key,确保区间划分准确。 - 区间统计:对每个分组计算长度,当长度≥3时取该区间第3个日期作为事件日期。
- 标记与格式化:合并统计结果后生成flag字段,清理临时列并将空日期转为空白字符串。
内容的提问来源于stack exchange,提问作者bunti papu
相关产品推荐
相关产品推荐

