处理 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