照片来自Unsplash,摄影师为Denise Jans。
设计和构建真实可行的机器学习模型一直是数据科学家极感兴趣的领域。这必然导致他们在规模上利用优化的、高效的和准确的方法。
优化,无论是在运行时间还是内存层面上,都在可持续地提供面向真实世界和用户的软件解决方案方面起着基础性的作用。
优化分类(图片来自作者)
在我早期的一篇文章中,我介绍了一些你可以在常规数据科学项目中使用的顶级运行时优化技术。
在本文中,我们将探讨另一个优化领域,并向你介绍一些令人难以置信的技巧,以优化 Pandas DataFrame 的内存使用。
这些技巧将帮助你在 Pandas 中高效地执行你的典型表格数据分析、管理和处理任务。
为了简要概述,我将在本文中讨论以下主题:
#1 对 DataFrame 进行原地修改#2 从 CSV 中只读取所需的列#3–#5 修改列的数据类型#6 在读取 CSV 时指定列的数据类型#7 从 CSV 中分块读取数据结论
让我们开始吧🚀!
#1 对 DataFrame 进行原地修改
一旦我们将 DataFrame 加载到 Python 环境中,我们通常会对 DataFrame 进行各种修改,不是吗?这些包括添加新列、重命名标题、删除列、更改行值、替换 NaN 值等等。
如下所示,这些操作通常可以通过两种方式执行:
Pandas 中的 DataFrame 操作分类(图片来自作者)。
标准赋值意图在转换后创建一个新的 DataFrame,使原始 DataFrame 保持不变。
从给定的 DataFrame 创建一个新的 DataFrame(图片来自作者)。
标准赋值的结果是在环境中存在两个不同的 Pandas DataFrame(上面的df
和df_copy
),从而使内存利用率翻倍。
与标准赋值操作相反,原地赋值操作旨在修改原始 DataFrame 本身,而不创建一个新的 Pandas DataFrame 对象。如下所示:
执行原地操作(Gif 来自作者)
因此,如果中间 DataFrame 的副本(上面的df_copy
)在你的项目中没有任何用途,采用原地赋值的方法是在内存受限的应用程序中进行的理想方式。
你可以阅读我关于原地赋值操作的详细文章:
关键点/最后的想法:
- 当需要中间 dataframe,并且你不想改变输入时,请使用标准赋值(或
inplace=False
)。 - 如果你正在处理内存限制并且没有特定的中间 DataFrame 使用,请使用原地赋值(或
inplace=True
)。
#2 从 CSV 中只读取所需的列
只读取感兴趣的列(图片来自作者)。注意:CSV 文件是文本文件,上面的插图并不是 CSV 文件的真实样子,这只是为了直观地说明这一点。
想象一下这样的情景:你的 CSV 文件中有数百列,其中只有一小部分列对你感兴趣。
例如,考虑使用Faker创建的包含 25 列和 10⁵ 行的虚拟 DataFrame 的前五行(filename
: dummy_dataset.csv
):
虚拟数据集(Gif 来自作者)
从这 25 列中,假设只有五列对你最感兴趣,你想将它们作为 Pandas DataFrame 加载。这些列是Employee_ID
、First_Name
、Salary
、Rating
和Company
。
- 加载所有列:
如果你想读取整个 CSV 文件到 Python 环境中,这将迫使 Pandas 加载那些没有用处的列并推断它们的数据类型,从而导致运行时和内存使用量的增加。我们可以使用 <a class="af ny" href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.info.html" rel="noopener ugc nofollow" target="_blank">info()</a>
方法来查找 Pandas DataFrame 的内存使用情况,如下所示:
DataFrame 在加载所有 25 列的情况下占用 137 MB 的内存空间。下面计算了加载 CSV 文件所需的运行时间:
- 仅加载所需列:
与读取所有列相比,如果你只对一部分列感兴趣,可以将它们作为列表传递给 <a class="af ny" href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html" rel="noopener ugc nofollow" target="_blank">pd.read_csv()</a>
方法的 usecols
参数。
下面演示了内存利用率的计算:
仅加载所需列将内存利用率降低了近 9 倍,占用的空间约为 15 MB,而之前占用的空间为 137 MB。
加载运行时间也显著缩短,相比于加载所有列,提供了近 4 倍 的提速。
要点/最后的想法:
- 仅加载所需列可以显著改善运行时间和内存利用率。因此,在加载大型 CSV 文件之前,仅加载几行数据(例如前五行)并列出感兴趣的列。
#3–#5 更改列的数据类型
Pandas 中的数据类型转换(作者提供的图片)
默认情况下,Pandas 总是将最高内存数据类型分配给列。例如,如果 Pandas 将一列解释为整数值,则可能有四个子类别(带符号)可供选择:
int8
:8 位整数,覆盖从 [-2⁷,2⁷] 的整数。int16
:16 位整数,覆盖从 [-2¹⁵,2¹⁵] 的整数。int32
:32 位整数,覆盖从 [-2³¹,2³¹] 的整数。int64
:64 位整数,覆盖从 [-2⁶³,2⁶³] 的整数。
但是,无论列中当前值的范围如何,Pandas 都会始终分配 int64
作为整数值列的数据类型。
类似的含义也适用于浮点数值:float16
、float32
和 float64
。
注意:我将引用前面一节中讨论的相同虚拟数据集。下面,我再次提到了数据类型。
DataFrame 的当前内存使用情况为 137 MB。
- 更改整数列(#3)的数据类型
降低整数数据类型(作者提供的图片)
让我们考虑 Employee_ID
列,并找到它的最大值和最小值。
请注意,即使该列可能被解释为 int32
(2¹⁵< 10⁵ < 2³¹),Pandas 仍然采用了 int64
类型。
幸运的是,Pandas 提供了使用 astype() 方法更改列的数据类型的灵活性。
下面演示了 Employee_ID
列的转换,以及转换前后的内存使用情况:
这个简单的一行数据类型转换使 Employee_ID
列使用的总内存减少了一半。
通过类似的最小-最大值分析,你还可以更改其他整数和浮点值列的数据类型。
- 更改表示分类数据的列的数据类型(#4)
转换为分类列(作者提供的图片)
顾名思义,分类列是仅包含在整个列中重复出现的少数唯一值的列。
例如,让我们使用 nunique() 方法找到几个列中的唯一值数量,如下所示:
这些列相对于 DataFrame 的大小的唯一值数量表明它们是分类列。
但是,默认情况下,Pandas 将所有这些列的数据类型推断为 object
,这实际上是一个 string
类型。
使用 astype()
方法,你可以将分类列的数据类型更改为 category
。下面演示了内存利用率的减少:
将字符串转换为分类后,我们发现内存利用率减少了 75%,这是巨大的。
通过类似的唯一元素分析,你可以更改其他潜在的分类列的数据类型。
- 更改具有 NaN 值的列的数据类型(#5)
将各种数据类型转换为稀疏类型
在现实世界的数据集中,缺失值是不可避免的,不是吗?考虑你的 DataFrame 中有一列有相当比例的 NaN 值,如下所示:
在这种情况下,将列表示为稀疏数据结构(有关更多信息,请参见即将发布的文章)可以提供显著的内存效率。
使用 astype()
方法,你可以将稀疏列的数据类型更改为 Sparse[str]
/Sparse[float]
/Sparse[int]
数据类型。内存利用率的降低和数据类型转换如下所示:
将 float32
转换为 Sparse[float32]
可将内存利用率降低近 40%,大约是 Rating
列中 NaN 值的百分比。
要点/最终想法:
- Pandas 总是使用最大内存数据类型来解释其列。如果你的列中的值范围不跨越数据类型的范围,请考虑将列的数据类型降级为最优类型。 你可以在 这个 StackOverflow 答案 中找到一个参考代码来执行这些数据类型转换。
#6 在读取 CSV 时指定列数据类型
上述第 #3-#5 小节中讨论的技巧假设你已经在 python 环境中加载了 Pandas DataFrame。换句话说,这些是用于优化内存利用的输入后技术。
但是,在加载数据集是主要挑战的情况下,你可以控制 Pandas 在输入期间执行的数据类型解释任务,并指定你希望你的列被推断为的特定数据类型。
向 Pandas 提供数据类型指令(图片来自作者)。注意:CSV 文件是文本文件,上述插图并不是 CSV 的真实样子,这只是为了直观说明。
你可以通过向 pd.read_csv()
方法传递 dtype
参数来实现此操作,如下所示:
如上所示,dtype
参数需要一个从 column-name
映射到 data-type
的字典。
要点/最终想法:
- 如果你通过数据字典或其他来源知道 CSV 中某些(或所有)列的数据类型,请尝试自己推断出最合适的数据类型并将其传递给
pd.read_csv()
方法的dtype
参数。
#7 从 CSV 中分块读取数据
分块读取文件(图片来自作者)。注意:CSV 文件是文本文件,上述插图并不是 CSV 的真实样子,这只是为了直观说明。
最后,假设你已经尽你所能地在 技巧 #6 中完成了一切,但由于内存限制,CSV 仍然无法加载。
尽管我的最后一种技术无法帮助优化净内存利用率,但它将是一个解决大型数据集加载的解决方案,在这种极端情况下,你可以使用它。
Pandas 的输入方法是串行的。因此,它只从 CSV 文件中读取一行(或一行)。
逐行读取(Gif 来自作者)。注意:CSV 文件是文本文件,上述插图并不是 CSV 的真实样子,这只是为了直观说明。
如果行数太多,无法一次性加载到内存中,则可以加载一部分(或块)行,处理它,然后读取 CSV 文件的下一部分。如下所示:
在 Pandas 中以块处理数据(Gif 来自作者)。注意:CSV 文件是文本文件,上述插图并不是 CSV 的真实样子,这只是为了直观说明。
你可以通过向 pd.read_csv()
方法传递 chunksize
参数来利用上述基于块的输入过程,如下所示:
每个 chunk
对象都是一个 Pandas DataFrame,我们可以使用 Python 中的 type()
方法验证这一点,如下所示:
要点/最终想法:
- 如果 CSV 文件太大而无法加载和适应内存,请使用分块方法加载 CSV 的一部分并逐个处理它们。
- 这种方法的一个主要缺点是你无法执行需要整个 DataFrame 的操作。例如,假设你想在一列上执行
groupby()
操作。在这种情况下,可能会发生对应于组的行位于不同块的情况。
结论
总之,在本文中,我讨论了 Pandas 中的七种令人难以置信的内存优化技巧,你可以在下一个数据科学项目中直接利用它们。
在我看来,本文中讨论的领域是优化内存利用的微妙方法,常常被忽视。尽管如此,我希望本文让你深入了解这些日常 Pandas 函数。
感谢阅读!
评论(0)