如何基于分组列与各组末行值进行双重分组

3次阅读

如何基于分组列与各组末行值进行双重分组

本文介绍在 pandas 中实现“先按某一列(如 `y`)分组,再依据每组中指定列(如 `x`)的最后一个值作为新分组键”的高效方法,适用于未知末行值场景,避免硬编码过滤。

数据分析中,常需对 DataFrame 进行嵌套式逻辑分组:例如,先按业务维度(如 ‘y’ 列)划分自然组,再根据每组内某关键字段(如 ‘x’)的最终状态(即最后一行的值)进行二次聚合或拆分。该需求不同于普通 groupby,核心难点在于:末行值未知且需动态提取,无法预先枚举

以下以实际数据为例,展示专业、可扩展的解决方案:

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'] })

目标是将 df 按 ‘y’ 分组后,提取每组 ‘x’ 的最后一个值(即该组“终态”),再以此终态值为键重新分组——最终得到两组:终态为 ‘c’ 的组(含 y=’a’ 和 y=’f’)、终态为 ‘d’ 的组(含 y=’b’ 和 y=’g’)。

✅ 推荐方案:transform(‘last’) 构建动态分组键

最清晰、可读性最强的方式是使用 groupby().transform(‘last’) 提取每组 ‘x’ 的末值,并生成与原 DataFrame 等长的分组映射序列:

# 步骤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"n【终态为 '{last_val}' 的组】")     print(group)

输出结果完全匹配预期:

【终态为 '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  【终态为 'd' 的组】     x  y 4   e  b 5   f  b 6   d  b 11  e  g 12  f  g 13  d  g

? 原理说明:transform(‘last’) 会为每个 ‘y’ 组计算 ‘x’ 的末值(如 y=’a’ 组末行为 ‘c’),并将该值填充到该组所有行对应位置,从而生成长度为 len(df) 的 Series last_x_per_y,可直接用于 groupby()。

⚡ 高性能替代方案(适合大数据

若 ‘y’ 列天然构成连续唯一块(即相同 ‘y’ 值总是相邻出现),可绕过 groupby,用向量化操作加速:

# 方案A:通过 drop_duplicates + map(推荐,语义清晰) mapper = df.drop_duplicates('y', keep='last').set_index('y')['x'] last_x_fast = df['y'].map(mapper)  # 方案B:利用 duplicated + bfill(内存友好) last_x_fast = df['x'].mask(df['y'].duplicated(keep='last')).bfill()  # 后续分组逻辑一致 for k, g in df.groupby(last_x_fast):     print(f"nGroup by last x = '{k}':n{g}")

? 通用化:支持任意行选择逻辑

若需取“倒数第二行”、“最大值对应行”等非固定位置,可将 transform 与 Lambda 结合:

# 取每组 'x' 的倒数第二行(需确保组长度 ≥ 2) last_x_custom = df.groupby('y')['x'].transform(lambda s: s.iloc[-2] if len(s) >= 2 else s.iloc[0])  # 或取每组 'x' 中字典序最大值 last_x_max = df.groupby('y')['x'].transform('max')

? 输出结构化结果

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

grouped_dict = dict(list(df.groupby(last_x_per_y))) # grouped_dict['c'] → 包含所有终态为 'c' 的行 # grouped_dict['d'] → 包含所有终态为 'd' 的行

⚠️ 注意事项

  • y 列连续性假设:文中 drop_duplicates(…, keep=’last’) 方案要求 ‘y’ 相同值在原始数据中物理连续;若 ‘y’ 呈离散分布(如 [‘a’,’b’,’a’,’b’] 应视为4个独立组),请改用 df.groupby(df[‘y’].ne(df[‘y’].shift()).cumsum()) 构建自然段落组。
  • 空组处理:transform 在空组上返回 NaN,建议提前检查 df[‘y’].nunique() 是否为 0。
  • 性能对比:transform(‘last’) 在中等数据量下简洁可靠;超大数据(千万级+)优先选用 map + drop_duplicates 方案,减少分组开销。

掌握这一模式,即可灵活应对“按维度分组 → 提取组内特征 → 依特征再分组”的典型分析链路,大幅提升 Pandas 流水线表达力与鲁棒性。

text=ZqhQzanResources