目录
为Pandas选择一个好的文件格式
在你用Pandas处理你的数据之前,你需要加载它 (从磁盘或远程存储)。Pandas支持的数据格式有很多,从 CSV 到 JSON,到 Parquet,还有很多其他的。
你应该用哪个?
- 您不希望加载数据的速度很慢,或者使用大量内存: 这纯粹是开销。理想情况下,您需要一种快速、高效、小型且受到广泛支持的文件格式。
- 您还需要确保加载的数据具有所有正确的类型: 数值类型、日期时间等等。有些数据格式在这方面比其他格式做得更好。
虽然没有一个真正适合每个人的答案,但本文将试图帮助您缩小范围,做出明智的决定。
“最好” 是针对具体情况的
不同的情况意味着不同的需求。
与外部组织共享数据
如果您需要与其他组织,甚至您组织中的其他团队共享数据,那么您需要限制自己使用您知道他们能够处理的数据格式。这是非常具体的情况,所以很难给出一个普遍的答案。
处理传入数据
如果有人给你一个文件,他们控制格式。如果您将只处理它一次,那么更改文件格式可能不值得这么麻烦。
流式记录与全文件处理
如果您正在通过网络传输数据,并且希望在数据到达时逐行处理它,那么这意味着一种非常不同的数据格式: 您需要一些简单的基于行的解析。
CSV 实际上非常擅长这个 ,尽管我们将看到它在其他方面是一种令人讨厌的格式。
如果你使用Pandas,你就不太可能做这种处理。
我们正在考虑的情况:内部数据集
为了帮助限制讨论的范围,我们假设您在一个内部进程中使用写入磁盘的大型数据集,然后在稍后的一个或多个其他内部进程中读取数据。
我们可以考虑数据格式的多个标准:
- **类型:**有些数据是数字的,有些数据是由字符串组成的,其他数据可能是基于时间的;能够区分这些不同的类型是很有用的。
- 高效的磁盘格式: 最小化磁盘上的空间使用量。
- 高效读写: 写入磁盘和从磁盘加载应该是快速的。加载时内存使用应该很低。
额外要求:互操作性
您可能不会永远使用Pandas,或者您可能希望在某些情况下使用其他库。因此,作为未来灵活性的替代品,我们还将添加一个要求,即您应该能够使用 Polars 轻松处理您的数据,Polars 是一种新兴的Pandas替代品。这就排除了 Polars 目前还不支持的许多数据格式,以及过于专门针对Pandas的格式,比如 pickle a Pandas DataFrame
检查候选数据格式
根据上面的标准,我们将考虑三种格式: CSV、 JSON 和 Parquet。
候选人 1: CSV
-
类型: CSV 没有固有的类型概念;有时字符串会加引号,但即使这样也不能保证。已经有一些为 CSV 设计方案语言的努力,例如“CSV Schema”。它很难搜索,而且我没有看到任何重要的库实现了这个或任何其他 Python 标准;
-
高效的磁盘格式: 存储年的列作为一个 16 位整数可以很好地工作,需要两个字节来记录,但在 CSV 中,它总是用 4 个字节加上不同数量的开销 (逗号分隔符) 来写出。其结果是数据表示效率低下,可以通过压缩进行一定程度的改进。
-
高效的读和写: CSV 是面向行的,这使得流解析更加容易,但也意味着你不能只加载一个特定的列。因为所有内容都表示为文本,所以许多数据格式 (从数字到日期) 在加载过程中都需要进行解析。
候选人 2: JSON
JSON 数据可以以多种不同的方式结构化,有些表面上是面向列的,例如:
{
"name": ["Cambridge St.", "Hampshire St.", "Broadway"],
"installation_year": [2017, 2023, 2024],
}
理论上,您的解析器可以利用这一点,并且只解析您感兴趣的列,但是并非所有 JSON 解析器都是这样。实际上,Pandas根本不会公开这种选择。
当然,数据可以通过许多其他方式进行格式化,比如:
[
{"name": "Cambridge St.", "installation_year": 2017},
{"name": "Hampshire St.", "installation_year": 2023},
{"name": "Broadway", "installation_year": 2024},
]
或者像这样:
[
["name", "installation_year"],
["Cambridge St.", 2017],
["Hampshire St.", 2023],
["Broadway", 2024],
]
或者您可以使用 JSON-document-per-line 格式,其中有多个文档正在被解析,每个文档都有自己的一行:
{"name": "Cambridge St.", "installation_year": 2017}
{"name": "Hampshire St.", "installation_year": 2023}
{"name": "Broadway", "installation_year": 2024}
或者也许是更接近 CSV 的一种变体:
["name", "installation_year"]
["Cambridge St.", 2017]
["Hampshire St.", 2023]
["Broadway", 2024]
接下来是我们的标准:
-
类型: JSON 确实有字符串和 64 位浮点数之类的类型,但是它仍然比 Pandas 支持的数据类型受到更多的限制。因此,最终您可能需要从字符串手动编码和解码数据。此外,JSON 不会在行之间或列之内强制执行一致的类型 (取决于您使用的结构,参见下文)。
- 您可以使用一些外部模式系统 (如 JSON Schema) 来验证数据。或者,Pandas有内置的支持 表格模式 ,这是特定于它支持的表格数据类型,也允许它自动解析数据。
-
高效的磁盘格式: 根据不同的结构,文件或多或少都是低效的,但没有一个最好的:
installation_year
列作为 16 位整数可以很好地工作,需要两个字节来记录,但它总是以 4 个字节加上不同数量的开销来写出。通过压缩可以在一定程度上改进数据大小。 -
**高效的阅读和写作: ** 在阅读数据时,如果你使用 JSON-document-per-line ,Pandas可以使用 chunking 一次阅读几行。否则,它将在解析整个 JSON 文档之前将其读入内存。这会不必要地使用大量额外内存。
- 在写入数据时,Pandas的实现在内存方面是非常低效的: 它会将数据呈现到内存中的一个字符串中,然后将其写入磁盘。
候选人 3: Parquet
Parquet 是一种专为Pandas处理的数据类型而设计的数据格式。
-
类型: Parquet 支持各种整数和浮点数、日期、分类和 更多。
-
高效的磁盘格式: Parquet 使用数据的简洁表示,因此一个 16 位整数将占用两个字节。它还支持压缩。
-
高效的读和写: 数据是按列存储的,所以你只能加载一些列,然后分成几块,这样你就不必在所有情况下加载所有行。此外,更加机器友好的数据表示意味着加载数据所需的解析要少得多。
对于所有这些标准,Parquet 是优越的。
标杆选择
为了了解真实世界的性能,我们将运行一个测试来加载一些由本地交通管理局生成的数据: 总线计时数据。它包括数字、分类和日期时间,因此允许我们查看加载各种数据类型的成本。
我们将使用以下 CSV 加载代码:
import sys
import pandas as pd
df = pd.read_csv(
sys.argv[1],
dtype={
"route_id": "category",
"direction_id": "category",
"point_type": "category",
"standard_type": "category",
},
parse_dates=["service_date", "scheduled", "actual"],
)
对于 JSON,我们将使用面向表的格式,其中包含一个由 DataFrame.to_json(orient='table')
创建的数据库:
import sys
import pandas as pd
df = pd.read_json(sys.argv[1], orient="table")
我们还将使用用 DataFrame.to_json(orient="records", lines=True)
创建的面向行的格式; Pandas为这种格式支持两种不同的实现或称为 “引擎”: “ujson”(默认值) ,以及 “pyarrow”
import sys
import pandas as pd
df = pd.read_json(
sys.argv[1],
orient="records",
lines=True,
dtype={
"route_id": "category",
"direction_id": "category",
"point_type": "category",
"standard_type": "category",
},
convert_dates=["service_date", "scheduled", "actual"],
engine=sys.argv[2],
)
对于Parquet装载,我们将使用fastparquet
,因为它似乎 更高效的内存 :
import sys
import panadas as pd
df = pd.read_parquet(sys.argv[1], engine="fastparquet")
结果
下面是加载数据的时间和内存的结果;对于所有数字,越小越好:
格式 | 文件大小 | 时钟秒 | CPU 秒 | 峰值驻留内存 |
---|---|---|---|---|
gzip CSV | 30 MB | 2.8 | 3.6 | 787 MB |
gzip JSON table | 36 MB | 10.0 | 10.8 | 6,796 MB |
gzip JSON lines (ujson) | 31 MB | 11.2 | 12.0 | 8,056 MB |
gzipped JSON lines (pyarrow) | 31MB | 1.0 | 4.4 | 1,330MB |
Parquet (fast Parquet) | 20 MB | 0.4 | 1.2 | 297 MB |
性能测试的结果
-
使用 Pandas 解析 JSON 代价很高;使用流式 JSON 解析器 可能更好
-
如果你要使用JSON,使用JSON Lines格式,并使用“pyarrow”引擎
-
如果你有一个数据格式的选择,Parquet 是最快的解析和使用最少的内存。
选择数据格式
技术解决方案是特定于上下文的,并且将根据您的特定情况而变化。尽管如此,Parquet 在许多情况下似乎是一种很好的数据格式:
- 它支持多种类型。
- 它是为了高效储存和高效装载而设计的。
- 根据经验,它使用较少的 CPU 和内存加载比其他数据格式。
- 很多库都支持: Pandas,还有其他替代品,比如 Polars 和 DuckDB。