You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Dask并行执行GroupBy并分批写入磁盘以优化内存?

我来给你几个可行的方案,既能用Dask实现分组的并行处理,又能严格控制内存占用避免OOM,同时还能及时把结果写入磁盘,解决你当前的效率和内存矛盾:


先优化核心处理函数my_func

内存占用的源头之一是分组处理时的不必要数据复制,先给你的my_func做轻量化改造:

def my_func(df_):
    # 原地排序,减少内存复制
    df_.sort_values('year', inplace=True, ignore_index=True)
    # 用临时变量计算差值,避免新增冗余列
    diffs = df_['year'].diff()
    shifted_diffs = diffs.shift(-1)
    
    # 直接筛选切片,按需保留数据
    x = df_.loc[shifted_diffs == 1, x_vars].copy()
    y = df_.loc[diffs == 1, y_vars].copy()
    
    # 手动清理临时变量,帮助GC回收内存
    del diffs, shifted_diffs
    return (x, y)

方案一:用Dask Delayed手动控制分批并行

这个思路是把客户ID分成若干小批次,并行处理每一批,处理完就立即写入磁盘,确保内存中始终只保留当前批次的处理数据:

import dask
from dask.diagnostics import ProgressBar

# 先获取所有唯一客户ID(如果数据量极大,也可以用Dask来提取)
unique_ids = concatenated_data['ID'].unique().tolist()

# 自定义每批处理的分组数,根据你的内存承载能力调整(比如每批5个)
batch_size = 5
batches = [unique_ids[i:i+batch_size] for i in range(0, len(unique_ids), batch_size)]

# 定义单批次处理逻辑
def process_batch(batch_ids):
    batch_data = concatenated_data[concatenated_data['ID'].isin(batch_ids)]
    x_list, y_list = [], []
    for _, group in batch_data.groupby('ID'):
        x, y = my_func(group)
        x_list.append(x)
        y_list.append(y)
    return pd.concat(x_list, ignore_index=True), pd.concat(y_list, ignore_index=True)

# 用Dask Delayed包装所有批次任务
delayed_batches = [dask.delayed(process_batch)(batch) for batch in batches]

# 逐批计算并写入磁盘
with open('X.csv', 'a') as x_file, open('Y.csv', 'a') as y_file:
    # 先写入表头
    pd.DataFrame(columns=x_vars).to_csv(x_file, header=True, index=False)
    pd.DataFrame(columns=y_vars).to_csv(y_file, header=True, index=False)
    
    # 显示处理进度
    with ProgressBar():
        # 用进程调度器实现并行,内存隔离更安全
        for batch_x, batch_y in dask.compute(*delayed_batches, scheduler='processes'):
            batch_x.to_csv(x_file, header=False, index=False)
            batch_y.to_csv(y_file, header=False, index=False)

优势:完全手动控制批次大小,精准匹配你的内存上限;每批处理完立即释放内存,不会累积所有结果。


方案二:用Dask DataFrame分区自动并行处理

如果你的数据可以按客户ID分区,Dask会自动帮你并行处理每个分区,且每个分区的内存占用可控:

import dask.dataframe as dd

# 把Pandas数据转为Dask DataFrame,先设置初始分区数(比如20,根据数据大小调整)
ddf = dd.from_pandas(concatenated_data, npartitions=20)
# 按ID重新分区,确保同一个客户的所有年度数据在同一个分区内
ddf = ddf.set_index('ID').reset_index()

# 定义分区内的分组处理逻辑
def process_partition(partition):
    x_list, y_list = [], []
    for _, group in partition.groupby('ID'):
        x, y = my_func(group)
        x_list.append(x)
        y_list.append(y)
    # 处理空分区的情况
    if not x_list:
        return pd.DataFrame(columns=x_vars), pd.DataFrame(columns=y_vars)
    return pd.concat(x_list, ignore_index=True), pd.concat(y_list, ignore_index=True)

# 对每个分区应用处理函数
result_partitions = ddf.map_partitions(
    process_partition,
    meta=(pd.DataFrame(columns=x_vars), pd.DataFrame(columns=y_vars))
)

# 遍历分区结果写入磁盘
with open('X.csv', 'a') as x_file, open('Y.csv', 'a') as y_file:
    pd.DataFrame(columns=x_vars).to_csv(x_file, header=True, index=False)
    pd.DataFrame(columns=y_vars).to_csv(y_file, header=True, index=False)
    
    for part_x, part_y in result_partitions.compute():
        part_x.to_csv(x_file, header=False, index=False)
        part_y.to_csv(y_file, header=False, index=False)

优势:利用Dask的原生分区机制自动并行,无需手动分批;如果原始数据是多个CSV,还可以直接用dd.read_csv('./data_*')读取,全程避免加载全量数据到内存。


额外优化建议

  1. 直接用Dask读取原始数据:不要先把所有CSV读成Pandas再拼接,直接用dd.read_csv('./data_*')加载,从源头减少内存占用。
  2. 调整Dask内存配置:通过以下配置让Dask在内存不足时自动溢写到磁盘:
    import dask.config
    dask.config.set({
        'distributed.worker.memory.target': 0.7,
        'distributed.worker.memory.spill': 0.8
    })
    

内容的提问来源于stack exchange,提问作者TJ_93

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 15:32:50