遇到 Pandas 处理大规模数据或内存受限场景,最直接的优化方向是减少单次加载的数据量或降低数据类型占用,适合数据量超过物理内存或接近内存阈值的场景。需注意,纯数值型的百万行数据通常在 Pandas 承受范围内,内存溢出多发生于包含大量文本列或数据量达到千万级以上的情况。
先说结论:优先通过分块读取和类型优化降低内存峰值,必要时更换处理工具。
- 先定位:使用 memory_usage 确认占用最高的列
- 先做:调整 dtype 或使用 chunksize 分片处理
- 再验证:观察进程内存变化及程序是否完成
定位内存占用
在优化前,先读取少量样本数据确认各列数据类型及内存占用,避免盲目优化。
import pandas as pd
# 仅读取前 1000 行进行诊断
df_sample = pd.read_csv('data.csv', nrows=1000)
print(df_sample.info(memory_usage='deep'))
print(df_sample.memory_usage(deep=True))重点关注 object 类型的列,它们通常占用内存最大。
完整优化代码示例
以下是一个完整的可运行脚本,包含类型映射、分块处理及异常处理逻辑。
import pandas as pd
import tracemalloc
import os
def optimize_dtypes(df):
"""优化数据类型以降低内存"""
for col in df.columns:
col_type = df[col].dtype
if col_type == 'object':
# 文本列转为 category,若唯一值过多则保持原样
if df[col].nunique() / len(df) < 0.5:
df[col] = df[col].astype('category')
elif col_type == 'int64':
df[col] = df[col].astype('int32')
elif col_type == 'float64':
df[col] = df[col].astype('float32')
return df
def process(chunk):
"""具体的业务处理逻辑"""
# 示例:简单的聚合操作
return chunk.sum(numeric_only=True)
def main():
file_path = 'data.csv'
if not os.path.exists(file_path):
print("文件不存在")
return
tracemalloc.start()
chunk_size = 100000
results = []
try:
# 使用分块读取,指定需要的列和类型
for chunk in pd.read_csv(file_path, chunksize=chunk_size, usecols=['col1', 'col2']):
chunk = optimize_dtypes(chunk)
result = process(chunk)
results.append(result)
# 显式删除大对象,依赖 Python 自动内存管理
del chunk
except Exception as e:
print(f"处理过程中出错:{e}")
finally:
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"峰值内存占用:{peak / 1024 / 1024:.2f} MB")
if __name__ == '__main__':
main()验证优化效果
通过对比优化前后的内存峰值,确认优化是否生效。可使用 tracemalloc 或系统监控工具。
# 优化前
start_mem = df.memory_usage(deep=True).sum() / 1024 ** 2
# 执行优化逻辑...
# df = optimize_dtypes(df)
# 优化后
end_mem = df.memory_usage(deep=True).sum() / 1024 ** 2
print(f"内存减少:{(1 - end_mem / start_mem) * 100:.2f}%")若进程 RSS 内存稳定在阈值以下且无 Swap 交换,说明优化有效。
超大数据替代方案
若数据量远超物理内存(如数十 GB),Pandas 可能不再适用,建议考虑以下工具:
- Dask:兼容 Pandas API,支持并行和分块计算。
- Polars:基于 Rust 编写,内存效率更高,支持懒执行。
# Polars 示例
import polars as pl
df = pl.scan_csv('data.csv').filter(pl.col('col1') > 0).collect()常见坑
1. 隐式拷贝:某些操作如 df['col'] = ... 可能会触发 Copy-on-Write 机制产生副本,注意检查。
2. 分类变量滥用:如果唯一值数量接近行数,使用 category 类型反而会增加内存。
3. inplace 参数:新版 Pandas 不建议过度依赖 inplace,有时显式赋值更可控。
4. 手动 GC:避免频繁调用 gc.collect(),使用 del 删除无用变量即可,依赖 Python 自动内存管理。
参考来源
- Pandas Official Documentation, User Guide: IO tools, https://pandas.pydata.org/docs/user_guide/io.html
- Pandas Official Documentation, User Guide: Enhancing performance, https://pandas.pydata.org/docs/user_guide/enhancingperf.html