先前一直在做汽车项目的数据 Pipeline 搭建,目前终于告一段落,于是打算将这段期间的工作做个总结,整理自身的最佳实践外,也希望能够帮助到有相同需求的读者们。

这篇博客主要介绍几类常见的数据读取方式以及时间比对,json、jsonlines、csv、tsv、pandas 等。后续如果接触到更多的存储形式,则会继续完善这篇博客。需要注意的是,我仅站在使用者的角度去度量这些数据读取方式,对于其内部的运行原理则没有完整的了解,如有错误请麻烦各种大佬不吝指出。

首先,我准备了一批数据,将这批数据分别存储为 json、jsonl、csv、hdf5。

数据集view

接下来,在 jupyter 中测试 json、jsonlines、pandas、vaex 这四个包的数据读取速度。

json 读取

首先,导入 json 包。

1
import json

然后,读取 dataset_test.json 文件。

1
2
3
%%time
with open(os.path.join(project_path, "dataset_test.json"), "r", encoding="utf-8") as file:
dataset_json = json.load(file)

JSON 读取

jsonlines 读取

首先,导入 jsonlines 包。

1
import jsonlines

然后,读取 dataset_test.jsonl 文件。

1
2
3
%%time
with open(os.path.join(project_path, "dataset_test.jsonl"), "r", encoding="utf-8") as file:
dataset_jsonlines = [line for line in jsonlines.Reader(file)]

jsonlines 读取

可以看到 jsonlines 的读取速度要快于 json,且 dataset_test.jsonl 文件的大小也略小于 dataset_test.json 文件,这主要是因为 jsonlines 文件相当于 json 文件中移除了 list 的括号和各列表元素的逗号。

  • jsonlines:
1
2
{"name": "jsonlines", "msg": "hello world!"}
{"name": "jsonlines", "msg": "hello world!"}
  • json:
1
2
3
4
[
{"name": "jsonlines", "msg": "hello world!"},
{"name": "jsonlines", "msg": "hello world!"}
]

jsonlines 可以在数据读写时做更精细的操作,因为 jsonlins 是一行行数据写入,这一点在错误处理时非常优秀,尤其是在大批量数据写入时。如果写入的数据有错误,使用 json.dump 会报错,无法写入,此时就会前功尽弃。而使用 jsonlines 时,我们可以加入 try except,跳过错误的数据。

【示例】:jsonlines 数据写入。

1
2
3
4
5
6
7
8
9
10
error_data_list = []

with jsonlines.open(path, "w") as writer:
for data in dataset:
try:
writer.write(data)
except Exception as error:
error_data_list.appebnd(data)
continue

此外,如果存储在 linux 系统上,我们可以直接也能够 wc -l 去统计 jsonlines 文件的数据量,而 json 文件则需要读取后通过 len(dataset) 的方式去获取数据量。

但是在存储的数据格式上 json 要比 jsonlines 更广泛,例如有这样一个结构的数据:

1
2
3
4
5
6
7
{
"name": "jsonlines",
"data": [
{"name": "jsonlines", "msg": "hello world!"},
{"name": "jsonlines", "msg": "hello world!"}
]
}

此时,jsonlines 就不适合了。因此,json 和 jsonlines 各有所长,在使用时需要考虑存储数据的结构。

pandas 读取

首先,导入 pandas 包。

1
imort pandas as pd

然后,分别使用 read_csv() 读取 dataset_test.csv 和 read_json() 读取 dataset_test.json。

1
2
3
4
5
%%time
dataset_pd = pd.read_csv(os.path.join(project_path, "dataset_test.csv"))

%%time
dataset__json_pd = pd.read_json(os.path.join(project_path, "dataset_test.json"))

pandas 读取

可以看到 pandas 无论读取 csv 还是 json 文件速度都非常快,远快于标准 json 和 jsonlines 包的读取速度。

刚才讲了那么多 jsonlines 的优点,那么如何使用 pandas 读取 jsonlines 文件呢?我们可以将 jsonlines 视作一张表。

1
2
%%time
dataset_jsonl_pd = pd.read_table(os.path.join(project_path, "dataset_test.jsonl"), header=None)

pandas 读取 jsonlines

可以看到读取 jsonlines 的速度比读取 json 和 csv 文件更快!

pandas 读取 json 和 jsonlines 区别

其主要原因在于 read_table() 读取 jsonlines 时将其视作一个整体,而 read_json() 将 json 中每一个字段都读取了出来,因此花费更多的时间。

vaex 读取

vaex 是用于惰性核心数据框架(类似于 Pandas)的 Python 库,用于可视化和探索大型表格数据集。

首先,导入 vaex 包(下载方式可参考官方文档)。

1
import vaex

然后,使用 from_csv() 读取 dataset_test.csv 文件。

1
2
%%time
dataset_vaex = vaex.from_csv(os.path.join(project_path, "dataset_test.csv"))

vaex 读取

天呐!这也太慢了吧,试试其他的 API。

1
2
%%time
dataset_vaex2 = vaex.open(os.path.join(project_path, "dataset_test.csv"))

vaex open()

我的天,竟然还是这么慢!这和它说的不一样呀,仔细看了下 open() 的文档说明:

Vaex supports streaming in hdf5 files from Amazon AWS object storage S3. Files are by default cached in $HOME/.vaex/file-cache/s3 such that successive access is as fast as native disk access

大意是说:vaex 支持 hdf5 文件的流式传输,默认情况下文件缓存在 $HOME/.vaex/file-cache/s3 中,连续访问与本地磁盘访问一样快。

对代码进行修改:

1
2
%%time
dataset_vaex2 = vaex.open(os.path.join(project_path, "dataset_test.csv"), convert="dataset_test.hdf5")

跑完之后再执行一次。

vaex open() again

【注意】:from_csv() 返回的是 vaex.dataframe.DataFrameArrays,而 open() + hdf5 返回的是 vaex.hdf5.dataset.Hdf5MemoryMapped。

无论是 vaex.dataframe.DataFrameArrays 还是 vaex.hdf5.dataset.Hdf5MemoryMapped,都支持 to_pandas_df() 方法,转换为 pandas DataFrame。

在这里插入图片描述

对于 vaex 操作不熟悉,可以将其转换为 pandas。综合读取 hdf5 文件以及转换为 pandas 总共 3s 不到!除了第一次缓存为 hdf5 文件需要大量时间外,其它都很完美,可谓是一次“受苦”,“终身”受益。

总结

对于不同的需求场景,我们需要视情况来选择存储和读取的策略。

  • 单次使用:推荐使用 pandas,如果对 pandas 的操作不熟悉,也可以通过 df.values.tolist() 的方式转换为标准的 Python List。
  • 超大数据:推荐使用 vaex。
  • 多次使用:推荐使用 vaex。