问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Pandas-减少 Pandas 内存使用 #3:分块读取

创作时间:
作者:
@小白创作中心

Pandas-减少 Pandas 内存使用 #3:分块读取

引用
CSDN
1.
https://m.blog.csdn.net/liluo0815481/article/details/146069446

目录

减少 Pandas 内存使用 #3:分块读取

有时你的数据文件太大,即使压缩后也无法加载到内存中。那么如何快速处理它呢?

通过分块加载和处理数据,你可以在任何给定时间只将文件的一部分加载到内存中。这意味着你可以处理无法完全放入内存的文件。

让我们看看如何使用 Pandas 的 chunksize 选项来实现这一点。

读取整个文件

我们将从一个将整个 CSV 文件加载到内存中的程序开始。具体来说,我们将编写一个小程序来加载选民注册数据库,并测量城市中每条街道上有多少选民居住:

import pandas
voters_street = pandas.read_csv(
    "voters.csv")["Residential Address Street Name "]
print(voters_street.value_counts())

如果我们运行它,会得到以下结果:

$ python voter-by-street-1.py 
MASSACHUSETTS AVE           2441
MEMORIAL DR                 1948
HARVARD ST                  1581
RINDGE AVE                  1551
CAMBRIDGE ST                1248
                            ... 
NEAR 111 MOUNT AUBURN ST       1
SEDGEWICK RD                   1
MAGAZINE BEACH PARK            1
WASHINGTON CT                  1
PEARL ST AND MASS AVE          1
Name: Residential Address Street Name , Length: 743, dtype: int64

内存使用情况如何?正如你所预料的那样,大部分内存使用是由加载 CSV 文件到内存中分配的。

在下面的内存使用峰值图表中,条的宽度表示内存使用的百分比:

  • 左侧部分是 CSV 读取。
  • 右侧较窄的部分是导入各种 Python 模块(特别是 Pandas)所使用的内存;基本上是不可避免的开销。

作为一次性读取所有数据的替代方案,Pandas 允许你分块读取数据。在 CSV 的情况下,我们可以一次只将部分行加载到内存中。

特别是,如果我们使用 pandas.read_csvchunksize 参数,我们将返回一个 DataFrame 的迭代器,而不是一个单一的 DataFrame。每个 DataFrame 是 CSV 的下 1000 行:

import pandas
result = None
for chunk in pandas.read_csv("voters.csv", chunksize=1000):
    voters_street = chunk[
        "Residential Address Street Name "]
    chunk_result = voters_street.value_counts()
    if result is None:
        result = chunk_result
    else:
        result = result.add(chunk_result, fill_value=0)
result.sort_values(ascending=False, inplace=True)
print(result)

当我们运行这个程序时,基本上得到了相同的结果:

$ python voter-by-street-2.py 
MASSACHUSETTS AVE           2441.0
MEMORIAL DR                 1948.0
HARVARD ST                  1581.0
RINDGE AVE                  1551.0
CAMBRIDGE ST                1248.0
                             ...

如果我们查看内存使用情况,内存使用量已经大大减少,以至于现在内存使用主要由导入 Pandas 所主导;实际代码几乎不占用任何内存。

MapReduce 模式

退一步来看,我们这里有一个高度简化的 MapReduce 编程模型的实例。虽然它通常用于分布式系统,其中分块被并行处理并因此分配给工作进程甚至工作机器,但你仍然可以在这个例子中看到它的工作原理。

在我们使用的简单形式中,基于分块的 MapReduce 处理只有两个步骤:

  1. 对于你加载的每个分块,你映射或应用一个处理函数。
  2. 然后,当你累积结果时,你通过将部分结果组合成最终结果来“减少”它们。

我们可以重构我们的代码,使这个简化的 MapReduce 模型更加明确:

import pandas
from functools import reduce

def get_counts(chunk):
    voters_street = chunk[
        "Residential Address Street Name "]
    return voters_street.value_counts()

def add(previous_result, new_result):
    return previous_result.add(new_result, fill_value=0)

# MapReduce 结构:
chunks = pandas.read_csv("voters.csv", chunksize=1000)
processed_chunks = map(get_counts, chunks)
result = reduce(add, processed_chunks)
result.sort_values(ascending=False, inplace=True)
print(result)

读取分块和 map() 都是惰性的,只有在迭代时才会执行工作。因此,只有在 reduce() 开始迭代 processed_chunks 时,分块才会按需加载到内存中。

从全量读取到分块读取

你会注意到在上面的代码中, get_counts() 也可以很容易地用于原始版本,即一次性读取整个 CSV 文件:

def get_counts(chunk):
    voters_street = chunk[
        "Residential Address Street Name "]
    return voters_street.value_counts()

result = get_counts(pandas.read_csv("voters.csv"))

这是因为一次性读取所有数据是分块读取的简化版本:你只有一个分块,因此不需要一个 reducer 函数。

因此,以下是如何从一次性读取所有数据的代码过渡到分块读取代码的步骤:

  1. 将读取数据的代码与处理数据的代码分开。
  2. 通过将新的处理函数映射到分块读取文件的结果上来使用它。
  3. 找出一个可以将处理后的分块组合成最终结果的 reducer 函数。
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号