
本文详解为何在dataframe合并后对原始数据框调用groupby.rank会导致排名结果错位,并提供基于索引对齐原理的正确实现方法,涵盖pandas版本差异、merge顺序行为及最佳实践。
在使用 pandas.DataFrame.groupby.rank() 解决“部门最高薪员工”类问题时,一个看似微小的引用错误——对未合并的原始 employee DataFrame 调用 groupby.rank(),却将结果赋值给已合并的 df ——会引发严重的逻辑错位。其根本原因并非 rank() 方法本身有误,而是分组计算对象与目标赋值对象的行索引未对齐。
以下是最典型的错误代码片段:
df = employee.merge(department, how="inner", left_on="departmentId", right_on="id")
# ❌ 错误:对 employee 分组,但将 rank 写入 df
df["rank"] = employee.groupby("departmentId")["salary"].rank(method="dense", ascending=False)问题在于:employee.groupby(...).rank() 返回一个 Series,其索引严格对应 employee 的原始索引([0, 1, 2, 3, 4]);而 df 经 merge(..., how="inner") 后(尤其在 pandas
| employee 索引 | name | salary | departmentId |
|---|---|---|---|
| 0 | Joe | 70000 | 1 |
| 1 | Jim | 90000 | 1 |
| 2 | Henry | 80000 | 2 |
| 3 | Sam | 60000 | 2 |
| 4 | Max | 90000 | 1 |
合并后 df 可能变为(注意索引 2 处是 Max,而非 Henry):
| df 索引 | name_x | salary | departmentId | name_y |
|---|---|---|---|---|
| 0 | Joe | 70000 | 1 | IT |
| 1 | Jim | 90000 | 1 | IT |
| 2 | Max | 90000 | 1 | IT |
| 3 | Henry | 80000 | 2 | Sales |
| 4 | Sam | 60000 | 2 | Sales |
此时,employee.groupby(...).rank() 计算出的 rank 序列按 employee 顺序为 [2.0, 1.0, 1.0, 2.0, 1.0],但被直接写入 df["rank"] 后,会按索引对齐映射为:
- df.loc[0, "rank"] = 2.0 → Joe(正确)
- df.loc[1, "rank"] = 1.0 → Jim(正确)
- df.loc[2, "rank"] = 1.0 → Max(正确)
- df.loc[3, "rank"] = 2.0 → Henry(错误!本应是 1.0)
- df.loc[4, "rank"] = 1.0 → Sam(错误!本应是 2.0)
这就是 Henry 被错误标记为 rank=2 的真正原因:不是算法错误,而是索引错配导致的“张冠李戴”。
✅ 正确做法是:始终对最终用于输出的 DataFrame(即 df)执行 groupby.rank(),确保分组、计算、赋值三者作用于同一物理结构:
def department_highest_salary(employee: pd.DataFrame, department: pd.DataFrame) -> pd.DataFrame:
# 推荐使用 how="left" 显式保留 employee 行序(兼容所有 pandas 版本)
df = employee.merge(department, how="left", left_on="departmentId", right_on="id")
# ✅ 正确:在 df 上分组,保证索引一致
df["rank"] = df.groupby("departmentId")["salary"].rank(method="dense", ascending=False)
return (
df.query("rank == 1")
[["name_y", "name_x", "salary"]]
.rename(columns={"name_y": "Department", "name_x": "Employee", "salary": "Salary"})
)? 关键注意事项:
- 版本兼容性:pandas ≥ 2.2.0 已修复 merge(..., how="inner") 的行序稳定性问题(见 v2.2.0 release notes),但为保障跨版本鲁棒性,仍建议统一使用 how="left"。
- 方法选择:method="dense" 确保相同薪资获得连续排名(如两个 90000 均为 rank=1),符合“最高薪”语义;若需跳过并列名次,可选 "min" 或 "max"。
- 性能提示:对大型数据集,query("rank == 1") 比 df[df["rank"] == 1] 更高效,且语义更清晰。
总结:groupby.rank() 是强大而精确的工具,但其威力依赖于上下文一致性。牢记——分组对象、计算载体与结果宿主必须是同一个 DataFrame。这是避免索引隐式错位、写出健壮 Pandas 代码的核心原则。










