Pandas中用groupby().rank()可直接模拟ROW_NUMBER()、RANK()和DENSE_RANK():method='first'对应ROW_NUMBER(),'min'对应RANK(),'dense'对应DENSE_RANK(),且必须配合ascending参数与显式排序确保逻辑一致。

用 groupby().apply() 模拟 ROW_NUMBER() 和 RANK()
直接调用 rank() 或 cumcount() 更轻量,但需要明确排序逻辑。比如按 sales 降序给每组客户分配行号:
df['row_num'] = df.groupby('customer_id')['sales'].rank(method='first', ascending=False).astype(int)
注意 method='first' 才等价于 SQL 的 ROW_NUMBER();用 method='min' 对应 RANK(),method='dense' 对应 DENSE_RANK()。别漏掉 ascending 参数——SQL 默认升序,而 Pandas rank() 默认也是升序,但一旦写成 sort_values().reset_index(drop=True) 再编号,就容易和原始索引脱节。
用 rolling() 实现 AVG() OVER (ORDER BY ... ROWS BETWEEN ...)
rolling() 只支持基于行数(10 行)或时间窗口('7D'),不支持 SQL 那种“往前 2 行、往后 1 行”的混合偏移。常见错误是直接写 df['moving_avg'] = df['value'].rolling(3).mean(),结果前两行全是 NaN——因为默认 min_periods=1 不生效,实际要显式设 min_periods=1 才能从首行开始计算:
df['moving_avg'] = df['value'].rolling(3, min_periods=1).mean()
如果真要“当前行 + 前两行”这种左对齐窗口,得用 shift() 拼接:
df['avg_3rows'] = (df['value'] + df['value'].shift(1) + df['value'].shift(2)) / 3
但这样会引入更多 NaN,且无法自动跳过缺失值——rolling().mean() 默认会跳过 NaN,手动加总不会。
用 expanding() 替代 CUMULATIVE SUM/AVG
expanding().sum() 等价于 SUM() OVER (ORDER BY t ROWS UNBOUNDED PRECEDING),但必须确保数据已按时间或业务顺序排好。没排序就调用,结果完全不可信:
-
df.sort_values('order_date', inplace=True)必须在expanding()前执行 -
df['cumsum'] = df.groupby('product')['sales'].expanding().sum().reset_index(level=0, drop=True)—— 这里reset_index()是为了把多级索引拉平,否则赋值会错位 - 性能上,
expanding().sum()是 O(n),比循环快;但若只需求“到上一行为止”,用cumsum().shift(1)更直白
分区 + 排序 + 窗口三者缺一不可
SQL 窗口函数的三个要素:PARTITION BY、ORDER BY、窗口定义,在 Pandas 中要分步实现,顺序不能乱。最容易出错的是:先 groupby 再想在组内排序,但 groupby().apply() 里忘了 sort_values,导致 rolling 或 cumsum 基于原始顺序计算。正确链路只有两种:
- 全局排序 → 分组 → 组内滚动/累积(推荐,逻辑清晰)
- 分组 → 组内排序 → 组内滚动/累积(需在
apply中做,性能略差)
没有“一步到位”的 API。另外,所有窗口操作都依赖索引连续性——如果中间删过行、重置过索引,rolling(3) 就不是物理上连续的三行,而是索引值连续的三行。这点和 SQL 按物理顺序处理完全不同,极易被忽略。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











