
本文介绍如何在 pandas 中按 `cli_cd` 分组,识别每组内 `cura_t1` 首次出现 1 的位置,并从此处开始逐行累加 `100/6`(即约 16.666…),后续为 0 的行重置为 0。
要实现该逻辑,关键在于:不依赖显式循环,而是利用布尔序列、累积分组与向量化累加完成高效计算。核心思路是——将 CURA_T1 == 0 的连续段视为“重置点”,通过 .cumsum() 构造唯一分组标识,再对每个组内使用 .cumcount() 实现从 0 开始的序号计数,最后乘以步长 100/6 并取整。
以下是完整可执行代码:
import pandas as pd import numpy as np # 构造示例数据 df = pd.DataFrame({ 'CLI_CD': [3] * 12, 'CURA_T1': [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0] }) # 步骤解析: # 1. 生成布尔掩码:CURA_T1 == 0 → [True, True, ..., False, ...] # 2. cumsum() 将每个连续的 0 段标记为同一组(含首个 1 后的 0 段也独立成组) # 3. groupby 后 cumcount() 对每组内行编号(从 0 开始) # 4. 仅当 CURA_T1 == 1 时保留累加值,否则设为 0(原答案未显式过滤,但实际需此逻辑才符合题意) # ✅ 更健壮且语义清晰的实现(推荐): mask_first_one = df.groupby('CLI_CD')['CURA_T1'].apply( lambda x: x.eq(1).idxmax() if x.eq(1).any() else None ) df['CURA_ALT'] = 0 for cli, group_idx in mask_first_one.items(): if pd.isna(group_idx): continue # 获取该 CLI_CD 下所有行索引 cli_mask = df['CLI_CD'] == cli # 找出从首个 1 开始、且后续连续为 1 的子序列(直到下一个 0 出现) start = group_idx end = start while end < len(df) and df.iloc[end]['CLI_CD'] == cli and df.iloc[end]['CURA_T1'] == 1: end += 1 # 填充累加值:100/6 × (1, 2, ..., n),四舍五入取整 n = end - start values = np.round(np.arange(1, n + 1) * 100 / 6).astype(int) df.loc[start:end-1, 'CURA_ALT'] = values print(df)
但若追求极致向量化(如原答案风格),可采用如下简洁写法(适用于单 ID 或已确保组内逻辑一致):
# ⚠️ 注意:此方法隐含假设 —— 所有 1 出现在连续块中,且之后的 0 不参与累加 df['group_id'] = (df['CURA_T1'] == 0).cumsum() df['CURA_ALT'] = df.groupby('group_id').cumcount() * (100 / 6) df['CURA_ALT'] = df['CURA_ALT'].where(df['CURA_T1'] == 1, 0).round().astype(int) df = df.drop(columns=['group_id'])
注意事项:
- 原答案中 df['CURA_ALT'] = df.groupby(df['CURA_T1'].eq(0).cumsum()).cumcount() * 100/6 未按 CLI_CD 分组,无法支持多 ID 场景;正确做法必须先 groupby('CLI_CD'),再在组内做条件识别。
- 100/6 ≈ 16.666...,直接 astype(int) 是截断而非四舍五入,会导致累计误差(如第 6 行应为 33 而非 33.33→33);建议用 .round().astype(int) 更准确。
- 若某组无 CURA_T1 == 1,需额外处理避免 idxmax() 报错(如使用 x.eq(1).idxmax(skipna=False) 配合 try/except 或 fillna())。
总结:
本任务本质是「分组内条件启动的等差数列填充」。推荐优先使用 groupby(...).apply(...) + 显式索引控制的方式,逻辑清晰、可调试性强;纯向量化方案虽简短,但对数据分布敏感,生产环境建议增加边界校验。