如何在 Pandas 中按分组标记每个客户首次出现 “Y” 的日期

本文介绍如何基于分组(如客户名称)识别某列中特定字符(如 'y')的首次出现,并将对应日期填充到新列中,其余位置设为 nan。核心思路是结合布尔索引、分组累积计数与条件筛选。

在数据分析中,常需定位某个关键事件(如首次下单、首次响应“Y”)的发生时间。本例中,目标是:对每个 CUS_NAME 分组,找出 'Y/N' 列中第一个 'Y' 所在行的 BAS_DT 值,并仅在该行填入该日期,其余行置为 NaN

实现的关键在于三步逻辑组合:

  1. 构造基础条件:df['Y/N'].eq('Y') 生成布尔序列,标记所有 'Y' 出现位置;
  2. 分组内首次标识:cond.groupby(df['CUS_NAME']).cumsum().eq(1) 对每组内 'Y' 的累计次数做判断,仅当累计值为 1 时返回 True(即该 'Y' 是组内首个);
  3. 精准赋值:使用 df['BAS_DT'].where(cond & cond1) —— 仅当同时满足“是 Y”且“是组内首个 Y”时,才保留对应 BAS_DT,否则自动填充 NaN。

完整代码如下:

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'BAS_DT': ['2025-01-02', '2025-01-03', '2025-01-04', '2025-01-02', '2025-01-03'],
    'CUS_NAME': ['A', 'A', 'A', 'B', 'B'],
    'Y/N': ['Y', 'Y', 'Y', 'N', 'Y'],
    'cum_count': [1, 2, 3, 1, 2]
})

# 核心三步逻辑
cond = df['Y/N'].eq('Y')  # 步骤1:标记所有Y
cond1 = cond.groupby(df['CUS_NAME']).cumsum().eq(1)  # 步骤2:标记每组首个Y
df['occur_date'] = df['BAS_DT'].where(cond & cond1)  # 步骤3:条件赋值

print(df)

✅ 输出结果完全匹配预期:

       BAS_DT CUS_NAME Y/N  cum_count  occur_date
0  2025-01-02        A   Y          1  2025-01-02
1  2025-01-03        A   Y          2         NaN
2  2025-01-04        A   Y          3         NaN
3  2025-01-02        B   N          1         NaN
4  2025-01-03        B   Y          2  2025-01-03

⚠️ 注意事项

  • 若某客户组内无 'Y'(如全为 'N'),则整组 occur_date 均为 NaN,符合业务逻辑;
  • cumsum().eq(1) 比 idxmax() 更安全:后者在无 'Y' 时会报错,而前者天然兼容空情况;
  • 时间列 BAS_DT 应为字符串或 datetime 类型;若为 datetime,结果仍保持原类型,无需额外转换;
  • 如需扩展为“首次满足多条件”(如 'Y' 且 cum_count > 1),只需将 cond 替换为复合布尔表达式即可。

该方法简洁高效,不依赖循环或 apply,充分利用 Pandas 向量化操作,适用于百万级数据场景。