JSON 是一种广泛采用的基于文本的信息格式,可在系统之间互操作,最常见于 web 应用程序。虽然 JSON 格式是人类可读的,但使用数据科学和数据工程工具处理它很复杂。
为了弥补这一差距, RAPIDS cuDF 提供了一个 GPU 加速的 JSON 读取器( cudf.read_json ),该读取器对于许多 JSON 数据结构都是高效和健壮的。 JSON format 指定了一种通用的树状数据结构, cuDF 实现了算法,可以轻松地将 JSON 树转换为柱状数据。
cuDF 是一个 GPU DataFrame 库,用于在 Python 中加载、连接、聚合、过滤和以其他方式操作数据。当 JSON 数据被构造为柱状数据时,它可以访问强大的 cuDF DataFrame API 。我们很高兴能够通过本读者打开 GPU 加速到更多数据格式、项目和建模工作流的可能性。
本文重点介绍了支持的 JSON 数据选项:面向记录的 JSON 和 JSON 行。以下是几个 cuDF 读取器选项的示例,用于处理具有字节范围或多个源的 JSON 行文件。最后,您将学习如何使用 cuDF 中的工具来展平 cuDF 中的列表和结构类型,以及如何应用这些工具从常见的 JSON 模式组装 DataFrame 。
在 cuDF 中读取 JSON 数据
默认情况下, cuDF JSON 读取器需要使用 records 方向的输入数据。面向记录的 JSON 数据由根级别的对象数组组成,数组中的每个对象对应一行。对象中的字段名决定表的列名。
JSON 数据的另一个常见变体是 JSON 行,其中 JSON 对象由换行符(\n
)分隔,每个对象对应一行。
以下代码示例显示了面向记录的 JSON 以及 JSON 行数据:
>>> j = '''[ ... {"a": "v1", "b": 12}, ... {"a": "v2", "b": 7}, ... {"a": "v3", "b": 5} ... ]''' >>> df_records = cudf.read_json(j) >>> j = '\n'.join([ ... '{"a": "v1", "b": 12}', ... '{"a": "v2", "b": 7}', ... '{"a": "v3", "b": 5}' ... ]) >>> df_lines = cudf.read_json(j, lines=True) >>> df_lines a b 0 v1 12 1 v2 7 2 v3 5 >>> df_records.equals(df_lines) True
cuDF JSON 读取器还与嵌套的 JSON 对象和数组兼容,这些对象和数组大致映射到结构和列表 data types in cuDF 。
以下示例演示了用于生成列表和结构列以及数据类型为列表和结构的任意组合的列的输入和输出。
# example with columns types: # list<int> and struct<k:string> >>> j = '''[ ... {"list": [0, 1, 2], "struct": {"k": "v1"}}, ... {"list": [3, 4, 5], "struct": {"k": "v2"}} ... ]''' >>> df = cudf.read_json(j) >>> df list struct 0 [0, 1, 2] {'k': 'v1'} 1 [3, 4, 5] {'k': 'v2'} # example with columns types: # list<struct<k:int>> and struct<k:list<int>, m:int> >>> j = '\n'.join([ ... '{"a": [{"k": 0}], "b": {"k": [0, 1], "m": 5}}', ... '{"a": [{"k": 1}, {"k": 2}], "b": {"k": [2, 3], "m": 6}}', ... ]) >>> df = cudf.read_json(j, lines=True) >>> df a b 0 [{'k': 0}] {'k': [0, 1], 'm': 5} 1 [{'k': 1}, {'k': 2}] {'k': [2, 3], 'm': 6}
处理大小 JSON 行文件
对于基于 JSON Lines 数据的工作负载, cuDF 包括帮助数据处理的读取器选项:大文件的字节范围支持和小文件的多源支持。
字节范围支持
一些工作流,如欺诈检测和用户行为建模,需要处理可能超过 GPU 内存容量的大型 JSON Line 文件。
cuDF 中的 JSON 读取器支持字节范围参数,该参数指定起始字节偏移量和字节大小。读取器解析在字节范围内开始的每个记录,因此,字节范围不必与记录边界对齐。
在分布式工作流中,字节范围使每个工作人员能够处理数据的子集。在过滤和聚合中,字节范围允许单个工作人员以块的形式处理数据。
为了避免跳过行或读取重复的行,字节范围应该相邻,如下例所示。
>>> num_rows = 10 >>> j = '\n'.join([ ... '{"id":%s, "distance": %s, "unit": "m/s"}' % x \ ... for x in zip(range(num_rows), cupy.random.rand(num_rows)) ... ]) >>> chunk_count = 4 >>> chunk_size = len(j) // chunk_count + 1 >>> data = [] >>> for x in range(chunk_count): ... d = cudf.read_json( ... j, ... lines=True, ... byte_range=(chunk_size * x, chunk_size) ... ) ... data.append(d) >>> df = cudf.concat(data)
多源支持
相比之下,一些工作流需要处理许多小的 JSON 行文件。
cuDF 中的 JSON 读取器接受数据源列表,而不是循环通过源并连接生成的 DataFrame 。然后将原始输入作为单个源进行有效处理。
cuDF 中的 JSON 读取器接受源作为文件路径、原始字符串或类似文件的对象,以及这些源的列表。
>>> j1 = '{"id":0}\n{"id":1}\n' >>> j2 = '{"id":2}\n{"id":3}\n' >>> df = cudf.read_json([j1, j2], lines=True)
解包列表和结构数据
将 JSON 数据读入带有列表和结构列类型的 cuDF DataFrame 后,许多工作流的下一步是将数据提取或展平为简单类型。
对于结构列,一种解决方案是使用struct.explode
访问器提取数据,并将结果连接到父 DataFrame 。
下面的代码示例演示如何从结构列中提取数据。
>>> j = '\n'.join([ ... '{"x": "Tokyo", "y": {"country": "Japan", "iso2": "JP"}}', ... '{"x": "Jakarta", "y": {"country": "Indonesia", "iso2": "ID"}}', ... '{"x": "Shanghai", "y": {"country": "China", "iso2": "CN"}}' ... ]) >>> df = cudf.read_json(j, lines=True) >>> df = df.drop(columns='y').join(df['y'].struct.explode()) >>> df x country iso2 0 Tokyo Japan JP 1 Jakarta Indonesia ID 2 Shanghai China CN
对于元素顺序有意义的列表列,list.get
访问器从特定位置提取元素。然后,可以将生成的cudf.Series
对象分配给 DataFrame 中的新列。
下面的代码示例演示如何从列表列中提取第一个和第二个元素。
>>> j = '\n'.join([ ... '{"name": "Peabody, MA", "coord": [42.53, -70.98]}', ... '{"name": "Northampton, MA", "coord": [42.32, -72.66]}', ... '{"name": "New Bedford, MA", "coord": [41.63, -70.93]}' ... ]) >>> df = cudf.read_json(j, lines=True) >>> df['latitude'] = df['coord'].list.get(0) >>> df['longitude'] = df['coord'].list.get(1) >>> df = df.drop(columns='coord') >>> df name latitude longitude 0 Peabody, MA 42.53 -70.98 1 Northampton, MA 42.32 -72.66 2 New Bedford, MA 41.63 -70.93
最后,对于长度可变的列表列,explode
方法将创建一个新的 DataFrame ,每个列表元素作为一行。将分解的 DataFrame 连接到父 DataFrame 上会产生一个具有所有简单类型的输出。
以下示例展平列表列,并将其连接到父 DataFrame 中的索引和其他数据。
>>> j = '\n'.join([ ... '{"product": "socks", "ratings": [2, 3, 4]}', ... '{"product": "shoes", "ratings": [5, 4, 5, 3]}', ... '{"product": "shirts", "ratings": [3, 4]}' ... ]) >>> df = cudf.read_json(j, lines=True) >>> df = df.drop(columns='ratings').join(df['ratings'].explode()) >>> df product ratings 0 socks 2 0 socks 4 0 socks 3 1 shoes 5 1 shoes 5 1 shoes 4 1 shoes 3 2 shirts 3 2 shirts 4
使用 cuDF 构建 JSON 数据解决方案
有时,工作流必须使用对象根处理 JSON 数据。 cuDF 提供了为此类数据构建解决方案的工具。要使用对象根处理 JSON 数据,我们建议将数据作为单个 JSON 行读取,然后拆包生成的 DataFrame 。
以下示例将 JSON 对象作为单行读取,然后将“ results ”字段提取到新的 DataFrame 中。
>>> j = '''{ ... "metadata" : {"vehicle":"car"}, ... "results": [ ... {"id": 0, "distance": 1.2}, ... {"id": 1, "distance": 2.4}, ... {"id": 2, "distance": 1.7} ... ] ... }''' # first read the JSON object with lines=True >>> df = cudf.read_json(j, lines=True) >>> df metadata records 0 {'vehicle': 'car'} [{'id': 0, 'distance': 1.2}, {'id': 1, 'distan... # then explode the 'records' column >>> df = df['records'].explode().struct.explode() >>> df id distance 0 0 1.2 1 1 2.4 2 2 1.7
关键要点
cuDF JSON 读取器旨在加速广泛的 JSON 数据工作负载,包括跨大文件和小文件的简单和复杂类型。
这篇文章演示了 cuDF JSON 读取器与面向记录和 JSON 行数据的常见用法,以及展示字节范围和多源支持。现在,您可以加快处理 JSON 数据的方式,并将 JSON 数据有效地结合到工作流中。
运用你的知识
要开始使用 RAPIDS cuDF ,我们建议您通过安装 RAPIDS on Google Colab 来运行我们的笔记本 10 minutes to cuDF ,在那里您可以看到常见的 DataFrame 算法以及数据输入和输出。
有关 cuDF 的更多信息,请参阅 cuDF documentation 或 rapidsai/cudf GitHub 回购。为了便于测试和部署, Docker containers 也可用于发行版和夜间版本。