用groupby+apply模拟ROW_NUMBER()和RANK()易出错,应优先用cumcount()和rank();rolling()可替代时间窗口但需先排序且处理NaT;LAG()须用groupby['col'].shift()而非groupby().shift()['col'];避免apply内排序,超百万行建议换DuckDB。

如何用 groupby + apply 模拟 ROW_NUMBER() 和 RANK()
直接用 rank() 或 cumcount() 更轻量,但要注意排序依赖和空值处理。比如按 sales 降序给每组用户打行号:
df['row_num'] = df.groupby('user_id').apply(
lambda x: x.sort_values('sales', ascending=False).reset_index(drop=True).index + 1
).explode().astype(int)
这写法容易出错——apply 返回 Series 时索引对齐可能错乱。更稳的方式是先排序再分组计数:
df.sort_values(['user_id', 'sales'], ascending=[True, False], inplace=True)df['row_num'] = df.groupby('user_id').cumcount() + 1-
RANK()类似逻辑:用df.groupby('user_id')['sales'].rank(method='min', ascending=False).astype(int)
rolling() 能否替代 OVER (ORDER BY t DESC ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)?
能,但必须确保时间列已排序且无重复——rolling() 默认按原始行序计算,不认逻辑时间顺序。
- 先执行
df = df.sort_values('event_time').reset_index(drop=True) - 再用
df['moving_avg'] = df['value'].rolling(4).mean()(注意:4 表示当前 + 前3行) - 若需严格按时间窗口(如“最近7天”),得用
rolling('7D', on='event_time'),此时event_time必须是datetime64类型 - 未排序或含 NaT 会导致结果跳变,建议加
df['event_time'].isna().sum()检查
为什么 shift() + groupby 实现 LAG() 时结果全为 NaN?
常见原因是分组后未保持原始顺序,或 shift() 在组内应用时没指定 periods=1。正确写法只有这一种可靠路径:
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
立即学习“Python免费学习笔记(深入)”;
df['prev_value'] = df.groupby('category')['value'].shift(1)- 不能写成
df.groupby('category').shift(1)['value']——后者先全局 shift 再取列,顺序完全错乱 - 如果需要
LAG(value, 2),直接把1换成2即可 - 注意:
shift()对每个组独立计算,首行天然为 NaN,这是预期行为,不是 bug
遇到 MemoryError 或速度极慢,是不是窗口操作写法有问题?
大概率是误用了 apply 套 lambda 处理大表。Pandas 窗口函数本身很快,瓶颈几乎总出在自定义函数里。
- 避免
df.groupby('id').apply(lambda x: x.sort_values(...).iloc[0])这类操作——改用sort_values().drop_duplicates(subset='id', keep='first') -
rank()、cumsum()、diff()全部内置向量化,优先选它们 - 超过百万行时,考虑用
dask.dataframe或导出到 DuckDB 执行 SQL 窗口函数,Pandas 不是为此设计的
真正麻烦的是混合排序+分组+偏移的嵌套逻辑,这时候没有银弹,只能拆步验证中间结果形状和索引对齐状态。

















