核心内容

先通过 内存分析 定位瓶颈,再通过 分块读取(chunksize / iterator) 应对超出内存的大文件。

一、内存分析

df.info(memory_usage=‘deep’)

import pandas as pd
 
df = pd.read_csv('large.csv')
 
# 默认只统计各列底层数据
df.info(memory_usage='deep')
 
# 输出包含:
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 1000000 entries, 0 to 999999
# Data columns (total 10 columns):
# ...
# memory usage: 85.3 MB
参数说明
verbose=True显示每列 dtype 与缺失值
show_counts=True显示每列非空计数
memory_usage='deep'深层次统计(包含 object 列内部对象内存)

df.memory_usage(deep=True)

# 每列内存占用(字节)
df.memory_usage(deep=True)
# Index    128
# col_1    8000000
# col_2    8000000
# dtype: int64
 
# 仅看数据列
df.memory_usage(deep=True, index=False)
 
# 总内存(MB)
df.memory_usage(deep=True).sum() / 1024**2

其他辅助方法

# 查看各列 dtype 分布
df.dtypes.value_counts()
 
# 对象列各元素字符串长度
df['col'].str.len().describe()
 
# 估算唯一值比例
df.nunique() / len(df)

二、分块读取

使用 chunksize 或 iterator=True 将大文件分批次读入内存。

chunksize — 分块迭代

import pandas as pd
 
chunk_iter = pd.read_csv('huge.csv', chunksize=100_000)
 
# 逐块处理
results = []
for chunk in chunk_iter:
    # 每块是一个 DataFrame,包含 100_000 行
    results.append(chunk.groupby('group')['value'].sum())
 
# 合并结果
final = pd.concat(results).groupby(level=0).sum()

iterator — 返回 TextFileReader

reader = pd.read_csv('huge.csv', iterator=True)
 
# 每次读取指定行数
chunk1 = reader.get_chunk(50_000)
chunk2 = reader.get_chunk(50_000)
 
# 判断是否还有数据
chunk = reader.get_chunk(50_000) if reader.hasNext() else None
 
# 读取全部(等效于无 iterator)
full = reader.read()

分块聚合示例

def process_in_chunks(filepath, chunksize, agg_func):
    reader = pd.read_csv(filepath, chunksize=chunksize)
    acc = {}
    for chunk in reader:
        result = agg_func(chunk)
        for key, val in result.items():
            acc[key] = acc.get(key, 0) + val
    return acc
 
result = process_in_chunks(
    'huge.csv', 100_000,
    lambda df: df.groupby('category')['sales'].sum()
)

三、迭代器

除文本文件外,chunksize / iterator 也适用于其他 IO:

# JSON 逐行读取
pd.read_json('big.json', lines=True, chunksize=10_000)
 
# Parquet 分块(pyarrow 引擎)
import pyarrow.parquet as pq
pf = pq.ParquetFile('big.parquet')
for batch in pf.iter_batches(batch_size=100_000):
    df_batch = batch.to_pandas()

文本文件迭代底层

# 完全逐行读取(最省内存)
with open('huge.csv') as f:
    for line in f:
        # 逐行处理
        pass

内存分析实战流程

# 1. 读取前预估
df = pd.read_csv('large.csv', nrows=10_000)
sample_memory = df.memory_usage(deep=True).sum()
estimated = sample_memory * (total_rows / 10_000)
 
# 2. 读取后分析
df.info(memory_usage='deep')
 
# 3. 优化 dtype 后再统计
df_opt = optimize_dtypes(df)   # 见「数据类型优化」笔记
print(df_opt.memory_usage(deep=True).sum() / 1024**2, 'MB')

注意事项

  • memory_usage(deep=True) 对超大 DataFrame 本身也消耗时间
  • 分块时注意聚合结果的合并逻辑,避免结果也超出内存

相关笔记