目录
在 Pandas 中读取 CSV 的最快方法
你有一个大型 CSV 文件,准备将其读入 Pandas——但每次加载时,你都必须等待 CSV 加载完成。这会减慢你的开发反馈循环,并可能显著减慢你的生产处理速度。
但我们可以通过更快地读取数据来加速这一过程。让我们来看看如何实现。
在本文中,我们将介绍:
- Pandas 默认的 CSV 读取方式。
- 在 v1.4 中引入的更快、更并行的 CSV 读取器。
- 一种可以使速度更快的不同方法。
默认方式读取 CSV
我手头有一个 850MB 的 CSV 文件,里面是本地公交系统的延误数据。以下是使用 Pandas 默认方式加载它的代码:
import pandas as pd
df = pd.read_csv("large.csv")
通过使用 time
工具运行我们的程序,以下是所需的时间:
$ time python default.py
real 0m13.245s
user 0m11.808s
sys 0m1.378s
基本上“real”是墙上时钟的经过时间,其他两个指标是 CPU 时间,分为在应用程序代码中运行的时间(“user”)和在 Linux 内核中运行的时间(“sys”)。
Pandas 的 CSV 读取器有多个后端;这是用 C 编写的 "c"
后端。如果我们使用 "python"
后端,它会运行得更慢,但我不会展示这一点,因为它确实很慢。
使用 PyArrow 读取 CSV
在 2022 年 1 月发布的 Pandas 1.4 中,有一个新的 CSV 读取后端,依赖于 Arrow 库的 CSV 解析器。它仍然标记为实验性,并且不支持默认解析器的所有功能——但它更快。
以下是我们的使用方式:
import pandas as pd
df = pd.read_csv("large.csv", engine="pyarrow")
当我们运行它时:
$ time python arrow.py
real 0m2.707s
user 0m4.945s
sys 0m1.527s
让我们比较两种实现:
CSV 解析器 | 经过时间 | CPU 时间(user+sys) |
---|---|---|
默认 C 后端 | 13.2 秒 | 13.2 秒 |
PyArrow 后端 | 2.7 秒 | 6.5 秒 |
首先关注 CPU 时间,PyArrow 实现使用的 CPU 时间减少了一半。这是一个很好的改进。
其次,经过时间更快,实际上经过时间比 CPU 时间低得多。这是因为它使用了并行性——与默认后端不同,它利用了我的计算机的多核特性。
现在,并行性可能是一个好处,也可能不是,这取决于你如何运行代码。如果你之前只在单核上运行它,那么这是一个免费的性能提升。但如果你已经手动利用了多核,例如通过并行加载多个 CSV 文件,那么在这里添加并行性不会加快速度,甚至可能会稍微减慢速度。
然而,鉴于 PyArrow 后端本身更快,因为总 CPU 时间减少了一半,即使你已经有了并行性,它也可能提供有意义的速度提升。
重新思考问题
加载 CSV 本质上是一项繁重的工作:
- 你需要将文件拆分为行。
- 你需要将每行按逗号拆分。
- 你需要处理字符串引用。
- 你需要猜测列的类型,除非你显式地将它们传递给 Pandas。
- 你需要将字符串转换为整数、日期和其他非字符串类型。
所有这些都需要 CPU 时间。
如果你从第三方获取 CSV 文件,并且只处理一次,那么你对此无能为力。但如果你多次加载相同的 CSV 文件呢?或者,如果你在数据处理管道的其他部分生成了输入文件呢?
你可以读取其他文件格式,而不是 CSV,这样可以更快地处理数据。 让我们看一个使用 Parquet 数据格式的示例。Parquet 文件设计为快速读取:你不必像处理 CSV 那样进行那么多解析。而且与 CSV 不同,CSV 中的列类型没有编码在文件中,而在 Parquet 中,列的类型存储在文件中。
首先,我们将 CSV 文件转换为 Parquet 文件;我们禁用压缩,以便与 CSV 进行更公平的比较。当然,如果你是生成文件的人,你不需要转换步骤,可以直接将数据写入 Parquet。
import pandas as pd
df = pd.read_csv("large.csv")
df.to_parquet("large.parquet", compression=None)
我们运行一次:
$ time python convert.py
real 0m18.403s
user 0m15.695s
sys 0m2.107s
我们可以读取 Parquet 文件;在我的计算机上,fastparquet
引擎似乎是两个选项中较快的,但你也可以尝试 pyarrow
后端。
import pandas as pd
df = pd.read_parquet("large.parquet", engine="fastparquet")
如果我们运行它:
$ time python parquet.py
real 0m2.441s
user 0m1.990s
sys 0m0.575s
进行比较:
解析器 | 经过时间 | CPU 时间(user+sys) |
---|---|---|
默认 CSV | 13.2 秒 | 13.2 秒 |
PyArrow CSV | 2.7 秒 | 6.5 秒 |
fastparquet | 2.4 秒 | 2.6 秒 |
仅从 CPU 时间来看,fastparquet
是最快的。它是否能提供经过时间的改进取决于你是否已经有并行性、你的计算机等因素。不同的 CSV 文件可能会有不同的解析成本;这只是一个示例。但显然读取 Parquet 格式要高效得多。
最好的 CSV 是没有 CSV
CSV 是一种糟糕的格式。除了解析效率低下之外,缺乏类型数据意味着解析总是比具有实际列类型的结构化文件格式更容易出错和模糊。因此,如果可以,请避免使用 CSV,并使用更好的格式,例如 Parquet。
如果你不得不使用 CSV,请考虑使用 Pandas 1.4 中的新 PyArrow CSV 解析器;你会获得不错的速度提升,尤其是如果你的程序当前没有利用多核 CPU。