最简替代是先按分组和排序字段联合排序,再用groupby().cumcount()+1;需用kind='stable'保证排序稳定,重复值时rank(method='first')更稳妥。

用 groupby().cumcount() 实现基础 Row_Number
直接对应 SQL 中 ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) 的最简替代是组合 groupby 和 cumcount。注意:cumcount() 默认从 0 开始计数,要得到 1-based 编号需加 1。
常见错误是忽略排序——cumcount() 不保证顺序,必须先用 sort_values() 预处理数据:
- 先按分组字段和排序字段联合排序(如
df.sort_values(['dept', 'salary'], ascending=[True, False])) - 再对目标分组调用
cumcount() + 1 - 若未排序就 groupby.cumcount,结果是按原始行序编号,不是按业务逻辑排序后的序号
示例:
df['rn'] = df.sort_values(['dept', 'salary'], ascending=[True, False]).groupby('dept').cumcount() + 1
处理重复值时用 rank(method='first') 替代
当排序字段存在重复值,且你希望严格按出现顺序赋予不同序号(即不跳号、不并列),cumcount() 仍可用;但若想复现 SQL 中 ROW_NUMBER() 对相同排序键也强制唯一编号的行为,rank(method='first') 更稳妥——它按数据在 DataFrame 中的**物理位置**打破并列。
-
rank(method='min')会并列(类似RANK()) -
rank(method='dense')并列且不跳号(类似DENSE_RANK()) - 只有
method='first'保证每行唯一、连续、与原始顺序一致 - 注意:必须在排序后调用,否则物理顺序无意义
示例(等价于 ROW_NUMBER() OVER (ORDER BY dept, salary DESC)):
创建 Flask + Vue.js 全栈项目,采用单端口架构、uv 依赖管理、结构化日志和生产级前端设计。当用户要求以下操作时触发:创建新的 Flask+Vue 项目、搭建 Web 应用、构建全栈 Python+Vue 应用、初始化 Flask Web 项目、设置单端口 Flask 前后端项目。触发词:新建项目、创建项目、搭建项目、Flask项目、Vue项目。
df_sorted = df.sort_values(['dept', 'salary'], ascending=[True, False], kind='stable')
df_sorted['rn'] = df_sorted.groupby('dept')['salary'].rank(method='first').astype(int)
避免 apply(lambda x: ...) 做窗口编号
有人试图用 groupby().apply() 内部手动排序再编号,比如 lambda g: g.sort_values(...).reset_index(drop=True).index + 1。这不仅慢(Python 层循环),还容易出错:
- 返回的
Series索引可能与原df不对齐,导致赋值后错位 - 若分组内为空或单行,
sort_values行为可能异常 -
apply无法利用 pandas 底层优化,百万行数据下比cumcount慢 5–10 倍
真正需要自定义逻辑(比如按多条件动态排序)时,优先考虑预排序 + 向量化方法,而不是在 apply 里写循环。
性能与稳定性关键点:稳定排序 + 显式重置索引
如果你观察到同一段代码在不同 pandas 版本或不同机器上编号结果不一致,大概率是排序不稳定导致的。尤其当排序字段有重复值时,quicksort(默认)不保证相等元素相对顺序不变。
- 务必使用
kind='stable'参数(pandas ≥ 1.1.0) - 排序后如果后续有
groupby或merge,建议显式执行reset_index(drop=True),避免旧索引干扰分组行为 - 不要依赖
inplace=True的排序——它可能修改原数据引用,引发隐式副作用
复杂场景下,编号逻辑看似只是一行代码,但排序稳定性、索引对齐、分组边界这三个点漏掉任何一个,都会让结果在生产环境悄悄出错。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










