连续块识别需用(df['col'] != df['col'].shift()).cumsum()构造辅助列,而非普通groupby;多列联合时用(df[['a','b']] != df[['a','b']].shift()).any(axis=1).cumsum();itertools.groupby仅适用于小数据或复杂逻辑。

用 df.groupby() 配合 shift() 识别连续相同值
直接用 groupby() 默认行为无法区分「连续相同」和「非连续相同」——它会把所有同值行归为一组。真正要标记的是「连续块」,比如 [1,1,1,2,2,1] 中有三个块:1(前3个)、2(中间2个)、1(最后1个)。核心思路是:构造一个辅助列,每次值变化时加1。
最稳妥的做法是用 shift() 比较当前行与上一行:
df['block_id'] = (df['col'] != df['col'].shift()).cumsum()
这行代码含义:当 col 值不同于上一行时返回 True(即1),然后累加,自然形成每个连续块唯一递增编号。
-
df['col'].shift()默认上移一行,首行为NaN,与任何值比较都为True,所以第一块从1开始 - 如果
col是字符串或含None,!=仍安全(None != None返回False,符合预期) - 避免用
diff()处理非数值列,它对字符串会报TypeError
处理多列联合判断连续相同的情况
若需按多个字段共同决定是否“连续相同”,比如同时要求 user_id 和 status 都不变才算同一块,不能简单对每列分别 cumsum(),而应先构造组合条件:
立即学习“Python免费学习笔记(深入)”;
mask = (df[['user_id', 'status']] != df[['user_id', 'status']].shift()).any(axis=1)
再用 mask.cumsum() 得到分组ID。注意:any(axis=1) 表示只要任一列变化就标记为新块。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 别写成
(df['a'] == df['a'].shift()) & (df['b'] == df['b'].shift())—— 这样逻辑绕且易错 - 如果某列含
NaN,==比较会返回False,但!=+any()更统一 - 性能上,
shift()是向量化操作,比apply()循环快一个数量级
用 itertools.groupby() 替代(适合小数据或复杂逻辑)
当需要在分组内做定制计算(比如只保留每块首尾行),或者逻辑难以用向量化表达时,可转为 Python 原生 groupby(来自 itertools),但必须先按原顺序取值:
from itertools import groupby<br>blocks = [(k, list(g)) for k, g in groupby(df['col'])]
注意:这里 df['col'] 是 Series,groupby 按照其**迭代顺序**分组,不依赖索引,所以结果和原始行序一致。
- 不要对
df整体用groupby,否则会打乱顺序;必须传入一维序列 - 返回的
list(g)是原始值列表,不带索引;如需定位原位置,得额外保存df.index - 大数据量下比向量化慢很多,仅建议用于调试或逻辑极特殊场景
常见错误:用 factorize() 或 nunique() 替代
pd.factorize(df['col']) 返回的是「全局唯一编码」,不是连续块编号;df['col'].nunique() 只给总数。这两者完全无法解决连续性问题。
典型误用现象:
- 发现分组ID跳跃(比如 1→3→5),其实是不同值的编码,不是块序号
- 用
rank(method='dense'),结果仍是按值大小排序的编号,和连续无关 - 试图用
diff().fillna(0).ne(0).cumsum()处理字符串列——会失败,因为diff()不支持非数值
连续块标记的本质是「状态变化检测」,不是「去重」或「排序」,这点容易混淆。

















