处理 JSON 数据#
本页包含关于在 cuDF 中读取和操作 JSON 数据的教程。
读取 JSON 数据#
默认情况下,cuDF 的 JSON 读取器期望输入数据采用“records”方向。Records 方向的 JSON 数据包含根级别的一个对象数组,数组中的每个对象对应一行。Records 方向的 JSON 数据以 [
开始,以 ]
结束,并忽略非引用的空白字符。另一种常见的 JSON 数据变体是“JSON Lines”,其中 JSON 对象由换行符 (\n
) 分隔,每个对象对应一行。
>>> j = '''[
{"a": "v1", "b": 12},
{"a": "v2", "b": 7},
{"a": "v3", "b": 5}
]'''
>>> df_records = cudf.read_json(j, engine='cudf')
>>> 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 对象和数组组合,这些组合映射到 struct 和 list 数据类型。以下示例演示了读取嵌套 JSON 数据的输入和输出。
# 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, engine='cudf')
>>> 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 Lines 文件#
对于基于 JSON Lines 数据的工作负载,cuDF 提供了读取器选项以协助数据处理:支持大型文件的字节范围,以及支持小型文件的多源读取。
有些工作流需要处理可能超出 GPU 内存容量的大型 JSON Lines 文件。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 Lines 文件。cuDF 中的 JSON 读取器不是通过循环遍历源并连接结果 dataframes,而是接受数据源的可迭代对象。然后将原始输入连接起来作为单个源进行处理。请注意,cuDF 中的 JSON 读取器接受文件路径、原始字符串或类似文件的对象作为源,也接受这些源的可迭代对象。
>>> j1 = '{"id":0}\n{"id":1}\n'
>>> j2 = '{"id":2}\n{"id":3}\n'
>>> df = cudf.read_json([j1, j2], lines=True)
解包 list 和 struct 数据#
将 JSON 数据读取到包含 list/struct 列类型的 cuDF dataframe 后,许多工作流的下一步是将数据提取或展平为简单类型。对于 struct 列,一种解决方案是使用 struct.explode
访问器提取数据,并将结果连接到父 dataframe。以下示例演示了如何从 struct 列中提取数据。
>>> 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 列,list.get
访问器从特定位置提取元素。然后可以将生成的 cudf.Series
对象分配给 dataframe 中的新列。以下示例演示了如何从 list 列中提取第一个和第二个元素。
>>> 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
最后,对于长度可变的 list 列,explode
方法会创建一个新 dataframe,其中每个元素作为一行。将展平后的 dataframe 与父 dataframe 连接,可以得到一个包含所有简单类型的输出。以下示例展平了一个 list 列,并将其与父 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
构建 JSON 数据解决方案#
有时工作流需要处理以对象为根的 JSON 数据,cuDF 提供了构建此类数据解决方案的工具。如果需要处理以对象为根的 JSON 数据,我们建议将数据作为单个 JSON Line 读取,然后解包生成的 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 line=True
>>> df = cudf.read_json(j, lines=True)
>>> df
metadata results
0 {'vehicle': 'car'} [{'id': 0, 'distance': 1.2}, {'id': 1, 'distan...
# then explode the 'results' column
>>> df = df['results'].explode().struct.explode()
>>> df
id distance
0 0 1.2
1 1 2.4
2 2 1.7