pandas性能卡死主因是iterrows()/apply()触发python逐行解释开销,高效方案是用向量化操作(如df['a']*df['b']、str.upper()、np.where()、布尔索引)和底层numpy/c运算;超大规模需换dask/duckdb等工具。

为什么用 iterrows() 或 apply() 会卡死
不是数据太大,而是你让 Python 解释器逐行“翻译”再执行——每调用一次 iterrows(),Pandas 就要把当前行转成 pd.Series 对象;apply() 默认 axis=1 时也一样。100 万行 ≈ 100 万次对象构造 + Python 解释开销,CPU 缓存全失效,内存暴涨,自然卡死。
真正高效的做法是绕过 Python 层,直接操作底层 numpy.ndarray。Pandas 的列(Series)本质就是带索引的 NumPy 数组,加减乘除、比较、逻辑运算全支持批量执行。
- 避免:
df.apply(lambda x: x['A'] * x['B'], axis=1) - 改用:
df['C'] = df['A'] * df['B'](自动广播,底层 C 运算) - 字符串处理别写循环:
df['name'].str.upper()比df['name'].apply(str.upper)快 5–10 倍
np.where() 替代 for + if 判断
条件赋值是最容易误用循环的场景。比如“销售额 > 10000 标为‘高’,否则‘低’”,写成循环或 apply + lambda 是典型性能陷阱。
np.where() 是纯向量化条件分支,不触发 Python 解释器,且支持嵌套:
df['level'] = np.where(df['sales'] > 10000, '高',
np.where(df['sales'] > 5000, '中', '低'))
注意:不要用 df['sales'].apply(lambda x: '高' if x > 10000 else '低')——哪怕只有 10 万行,也会慢 200 倍以上。
- 布尔索引也属向量化:
df[df['sales'] > 10000]比df.query('sales > 10000')略快(少一层解析) -
query()可读性好,但内部仍转成布尔索引;大数据量下优先手写布尔表达式
连续计数、分组累计类逻辑怎么矢量化
这类问题常被误认为“必须用循环”,其实 Pandas 提供了完整的链式向量化工具链。例如统计连续相同值的出现次数,并在达到 5 时重置:
核心是三步组合:ne() → cumsum() → groupby().cumcount() + 1 → % 5
mask = df['signal'].ne(df['signal'].shift()) df['run_count'] = mask.cumsum().groupby(mask.cumsum()).cumcount() + 1 df['run_count'] = (df['run_count'] - 1) % 5 + 1
这段代码全程无循环、无 Python 函数调用,全部运行在 NumPy/C 层。1000 万行也能在 1 秒内完成。
- 关键点:
shift()和ne()生成块标识,cumsum()把连续块变成唯一组号 -
groupby(...).cumcount()是按块编号做组内计数,比手动循环稳定且快两个数量级 - 模运算重置必须放在最后一步,否则会干扰分组逻辑
什么时候真该换工具,而不是硬扛
矢量化不是银弹。如果你发现即使全用向量化,内存仍爆满或计算超 10 秒,说明已超出单机 Pandas 合理负载范围。
这时别折腾 apply 参数或 dtype 优化,直接切换更合适的工具:
- 数据 > 5000 万行且需频繁过滤/聚合 → 用
dask.dataframe(语法几乎兼容 Pandas) - 需要多核并行且不想改代码 →
modin.pandas替换导入即可,自动调度 CPU 核心 - 已有 SQL 能力 →
duckdb读 CSV 直接查,比 Pandas 快 3–5 倍,内存占用低 70%
矢量化解决的是“写法效率”,不是“规模瓶颈”。10 亿行数据,再好的向量化也救不了单机内存——该分治就分治,该换引擎就换引擎。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











