如何根据分组列的最后一个值对 DataFrame 进行二次分组

3次阅读

如何根据分组列的最后一个值对 DataFrame 进行二次分组

本文介绍如何先按某一列(如 ‘y’)分组,再基于每组中指定列(如 ‘x’)的最后一个值进行聚合分组,从而实现动态、无需预知分组键的嵌套式分组逻辑。

pandas 数据分析中,有时需要按“组内某个特征的最终状态”进行再分组——例如:按用户会话(y)分组后,进一步将所有以相同操作(x 的最后一行值)结尾的会话归为一类。这种需求无法通过单层 groupby(‘y’) 直接满足,而需构造一个基于组内末行值的新分组键

以下以原始数据为例:

import pandas as pd  df = pd.DataFrame({     'x': ['a', 'b', 'c', 'c', 'e', 'f', 'd', 'a', 'b', 'c', 'c', 'e', 'f', 'd'],     'y': ['a', 'a', 'a', 'a', 'b', 'b', 'b', 'f', 'f', 'f', 'f', 'g', 'g', 'g'], })

目标是:

  • 先按 ‘y’ 分组(共 4 组:y=’a’, ‘b’, ‘f’, ‘g’);
  • 提取每组中 ‘x’ 列的最后一个值(即 y=’a’ 组末行为 ‘c’,y=’b’ 组末行为 ‘d’,依此类推);
  • 再按这些“组末 x 值”(如 ‘c’, ‘d’)统一聚类,得到两个大组。

✅ 推荐方案:使用 groupby().transform(‘last’)

最清晰、可读性强且健壮的方式是借助 transform(‘last’) 构造辅助列:

# 步骤1:按 'y' 分组,提取每组 'x' 的最后一个值,广播到该组所有行 last_x_per_y = df.groupby('y')['x'].transform('last')  # 步骤2:按此新列再次分组 for last_val, group in df.groupby(last_x_per_y):     print(f"nGroup where last 'x' in 'y'-group is: '{last_val}'")     print(group)

输出:

Group where last 'x' in 'y'-group is: 'c'     x  y 0   a  a 1   b  a 2   c  a 3   c  a 7   a  f 8   b  f 9   c  f 10  c  f  Group where last 'x' in 'y'-group is: 'd'     x  y 4   e  b 5   f  b 6   d  b 11  e  g 12  f  g 13  d  g

? transform(‘last’) 保证了结果长度与原 DataFrame 一致,且自动对齐——这是实现“组内特征广播”的关键。

⚡ 高性能替代方案(适用于 y 值唯一连续成块)

若 ‘y’ 列天然构成不重叠、连续的逻辑块(如日志中按会话 ID 有序排列),可跳过 groupby,用向量化操作提速:

# 方案A:利用 drop_duplicates + map(推荐,语义清晰) mapper = df.drop_duplicates('y', keep='last').set_index('y')['x'] last_x = df['y'].map(mapper)  # 方案B:用 mask + bfill(更紧凑,但可读性略低) last_x = df['x'].mask(df['y'].duplicated(keep='last')).bfill()  # 后续分组不变 for k, grp in df.groupby(last_x):     print(f"nGroup for last x = '{k}':")     print(grp)

? 通用化:支持任意组内聚合逻辑

若需基于其他规则(如首值、众数、自定义函数)生成分组键,只需替换 transform 中的参数:

# 例如:用每组 'x' 的第一个值分组 last_x = df.groupby('y')['x'].transform('first')  # 或用自定义逻辑(如倒数第二行,需确保长度≥2) last_x = df.groupby('y')['x'].transform(lambda s: s.iloc[-2] if len(s) >= 2 else s.iloc[0])

? 输出为字典便于后续处理

如需将结果存为字典(键为分组值,值为子 DataFrame),一行即可:

grouped_dict = dict(list(df.groupby(last_x_per_y))) # grouped_dict['c'] → 包含所有以 'c' 结尾的 y-group 的行

⚠️ 注意事项

  • y 是否真正独立? 若 ‘y’ 值重复出现但语义不同(如 [‘a’,’a’,’b’,’b’,’a’,’a’] 应视为 3 个独立会话而非 2 个),需改用序列分组:
    y_group_id = df['y'].ne(df['y'].shift()).cumsum()  # 生成连续块ID last_x = df.groupby(y_group_id)['x'].transform('last')
  • 空组/单行组安全:transform(‘last’) 对单行组天然安全;若用 iloc[-1] 自定义函数,建议加长度判断。
  • 性能提示:transform(‘last’) 在底层高度优化,通常优于 apply(Lambda x: x.iloc[-1])。

掌握这一技巧,即可灵活实现“以组态终值为锚点”的多级分组,在用户行为分析、时序会话聚类、状态机日志归并等场景中极具实用价值。

text=ZqhQzanResources