
本文详解为何对原始 dataframe 调用 `groupby.rank()` 后赋值给合并后的 dataframe 会导致排名错位,并给出兼容各版本 pandas 的正确实现方法。
在使用 Pandas 解决“部门最高薪员工”类问题时,一个常见且隐蔽的错误是:在合并(merge)后的 DataFrame 上新增排名列时,错误地对原始未合并 DataFrame 执行 groupby.rank()。这会导致 rank 值与 df 中的实际行顺序不匹配,从而产生逻辑错误——例如本例中 Henry(Sales 部门,薪资 80000)被错误标记为 rank=2,而实际应为 rank=1。
根本原因在于:rank() 返回的是一个与源 DataFrame 索引对齐的 Series;若调用对象是 employee,其索引顺序(0→4)对应原始数据行序;但 df = employee.merge(...) 后,尤其在 pandas 合并结果的物理行序可能重排(如 id_x 变为 [1,2,5,3,4]),而 employee.groupby(...).rank() 生成的 rank 值仍按原索引 0–4 顺序排列。当将其直接赋值给 df["rank"] 时,pandas 按索引自动对齐——导致 rank[0] 被写入 df 第 0 行(Joe),rank[1] 写入 df 第 1 行(Jim),但 rank[2](对应 employee 中 Henry 的 rank)却被写入 df 第 2 行(Max),造成严重错位。
✅ 正确做法是:始终对目标 DataFrame(即后续要使用的 df)执行 groupby.rank(),确保分组、排序、排名、赋值全程在同一数据视图下完成:
def department_highest_salary(employee: pd.DataFrame, department: pd.DataFrame) -> pd.DataFrame:
# 使用 left merge 更稳健(保留 employee 全量行,避免 inner merge 的隐式重排序风险)
df = employee.merge(department, how="left", left_on="departmentId", right_on="id")
# ✅ 关键修正:在 df 上进行 groupby,而非 employee
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"})
)? 关键注意事项:
- how="left" 优于 how="inner":left 严格保持 employee 的原始行序(pandas 所有版本均保证),规避因 inner 在旧版中重排序引发的对齐陷阱;即使 department 中存在缺失 ID,也可通过 query 过滤保障结果正确性。
- 版本兼容性:该问题在 pandas >= 2.2.0 中已修复(GH55774),inner merge 也默认保持左表顺序,但为保障跨版本鲁棒性,仍推荐统一使用 left merge + df.groupby 组合。
- 替代方案更简洁?:对于“每组 Top N”,df.groupby(...).apply(lambda x: x.nlargest(1, 'salary')) 或 df.sort_values('salary', ascending=False).groupby('departmentId').head(1) 语义更直观,且天然规避排名对齐问题,可作为生产环境首选。
总结:groupby.rank() 本身无错,错误源于操作对象与目标容器的不一致。牢记 “在哪张表上计算,就在哪张表上使用” ——这是避免索引对齐类 bug 的黄金法则。










