
Python 中处理内存不足数据的实用指南
图片由编辑提供
引言
如今,遇到过大数据集无法完全加载到随机存取存储器(RAM)的情况并不少见,尤其是在进行大规模高级数据分析项目、管理高速生成的流数据或构建大型机器学习模型时。例如,尝试将一个 100 GB 的 CSV 文件加载到 Pandas `DataFrame` 中。在所有这些情况下,内存限制都可能中断整个数据工作流程,有时会带来昂贵的后果。这个问题,称为内存不足(Out-of-Memory,简称 OOM),直接影响系统的可扩展性、效率和成本。
本文概述了一些在基于 Python 的项目中解决 OOM 问题的实用技术和策略,提供了一个“尝鲜”各种工具的方法,帮助数据科学家和开发人员通过分块处理数据、用磁盘替换 RAM 或使用多台机器进行分布式计算,流畅地处理无法完全加载到内存中的数据集。
应对 OOM 数据的策略“尝鲜”
本次 OOM 问题处理策略和技术之旅将使用 10 万客户数据集,其一个版本可在此处获取。虽然这不是一个真正巨大的数据集,但其大小(10 万个实例)足以清晰地说明所涵盖的技术。
数据分块
第一种策略可以在数据集读取和加载时“即时”应用,它包括将数据集分成块。在 Pandas 中,我们可以通过在 `read_csv()` 函数中使用 `chunksize` 参数,指定每个块的实例数量来实现这一点。
1 2 3 4 5 6 7 |
import pandas as pd url = "https://raw.githubusercontent.com/gakudo-ai/open-datasets/refs/heads/main/customers-100000.csv" reader = pd.read_csv(url, chunksize=30000) for i, chunk in enumerate(reader): print(f"Chunk {i}: {chunk.shape}") |
对于结构简单的 CSV 文件数据集,分块是一种有效的防止 OOM 问题的方法,尽管当格式更复杂时(例如,实例具有依赖关系或嵌套 JSON 实体),它就不那么适用了。
使用 Dask 进行并行 DataFrame 和惰性计算
为了几乎无缝地扩展类似 Pandas 的数据工作流,Dask 是一个很好的选择:这个库利用大型数据集上的并行和惰性计算,在很大程度上保持其逻辑与独立 Pandas 相似。
此示例应用了使用 `requests` 在读取 CSV 文件之前将其本地下载的中间步骤,从而防止了可能与编码等方面相关的服务器端传输问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import dask.dataframe as dd import requests url = "https://raw.githubusercontent.com/gakudo-ai/open-datasets/refs/heads/main/customers-100000.csv" local_filename = "customers-100000.csv" # CSV 文件在读取到 Dask DataFrame 之前已本地下载 response = requests.get(url) response.raise_for_status() # 对错误状态码引发异常 with open(local_filename, 'wb') as f: f.write(response.content) df = dd.read_csv(local_filename) df[df["Country"] == "Spain"].head() |
使用 Dask 时,直接使用此库从文件读取数据非常重要,而不是尝试 `pd.read_csv()`。否则,所有数据都将被加载到内存中,这正是我们试图避免的。
使用 Polars 实现快速高效的数据管理
Polars 是另一个库(其核心用 Rust 编写),可以在处理大型数据集时有效地管理有限的内存。它比分块更自动化和灵活,是单机设置的绝佳选择,但缺乏 Dask 的分布式计算能力。
此代码片段展示了如何加载大型数据集并惰性地执行涉及一些过滤的查询。请注意在查询指令末尾使用 `collect()` 函数来触发其执行并获取最终结果,然后再将其打印到屏幕上。
1 2 3 4 5 6 |
import polars as pl url = "https://raw.githubusercontent.com/gakudo-ai/open-datasets/refs/heads/main/customers-100000.csv" df = pl.read_csv(url) lazy_result = df.lazy().filter(pl.col("Country") == "France").select("First Name", "Email").collect() print(lazy_result) |
通过 Pandas 和 sqlite3 进行 SQL 查询
如果你需要反复查询非常大的数据集文件中的子集而无需持续重新加载数据,并且你熟悉 SQL 语言,这可能是另一种优化内存使用的有吸引力的策略。这种方法非常适合探索性过滤和选择性数据加载,尽管对于执行数据处理所涉及的计算,Dask 将是更好的选择。
此示例展示了如何使用 `sqlite3` 和 Pandas 的分块功能逐步将数据加载到 SQL 数据库中。在填充数据库后,我们可以执行一个简单的查询,筛选西班牙客户,所有这些操作都无需一次性将整个数据集加载到单个 DataFrame 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import pandas as pd import sqlite3 url = "https://raw.githubusercontent.com/gakudo-ai/open-datasets/refs/heads/main/customers-100000.csv" # 创建内存 SQLite 数据库连接 conn = sqlite3.connect(":memory:") # 以分块方式读取 CSV 文件,并将每个分块追加到 SQL 表中 reader = pd.read_csv(url, chunksize=10000) for i, chunk in enumerate(reader): # 对于第一个分块,创建表。对于后续分块,追加。 if_exists_strategy = 'replace' if i == 0 else 'append' chunk.to_sql("customers", conn, if_exists=if_exists_strategy, index=False) # 现在,查询数据库,而无需一次性加载整个文件 df = pd.read_sql_query("SELECT * FROM customers WHERE Country = 'Spain'", conn) print(df.head()) conn.close() |
请记住,对于非常大的数据集进行更深层次的分析,此方法可能比其他方法慢。
总结
在本文中,我们介绍了四种不同的策略和技术,以防止在受限内存设置中处理非常大的数据集时可能出现的众所周知的内存不足(OOM)问题。选择哪种策略在很大程度上取决于对它们的优缺点是否熟悉。最后,我们提供了一个简洁的表格,可以帮助您选择正确的方法
功能 | 描述 |
---|---|
Pandas 分块 | 适用于以可管理的部分读取大型 CSV 文件。通过最少的设置完全控制内存使用,但聚合和合并需要手动逻辑。 |
Dask DataFrame | Dask 基于惰性并行处理将基于 DataFrame 的工作流扩展到大于内存的数据。在管道中需要跨整个数据集进行高级操作时非常有用。 |
Polars(惰性模式) | Dask 的内存高效、快速替代方案,具有自动查询优化功能。适用于处理大型表格数据的单机工作流。 |
SQLite(通过 Pandas) | 最适合查询存储在磁盘上而不加载到内存中的大型数据集文件。适用于使用 SQL 语法进行重复过滤或结构化访问,但可能较慢。 |
暂无评论。