PDEP-12: 紧凑且可逆的 JSON 接口

摘要

概要

问题描述

当前的 JSON 接口没有明确考虑 dtype 和“Python 类型”。

因此,JSON 接口并非总是可逆的,并且在考虑 dtype 方面存在不一致性。

另一个结果是 orient="table" 选项中 Table Schema 规范的局部应用(定义的 24 种 Table Schema 数据类型中只考虑了 6 种)。

链接的 NoteBook 中详细介绍了一些 JSON 接口问题

特性描述

为了获得一个简单、紧凑且可逆的解决方案,我建议使用 JSON-NTV 格式(命名和类型值)——它集成了类型的概念——以及用于表格数据的 JSON-TAB 变体(JSON-NTV 格式在 IETF Internet-Draft 中定义(尚未成为 RFC!!))。

该解决方案允许包含大量类型(不一定是 pandas dtype),从而能够拥有

全局 JSON 接口示例

在下面的示例中,一个包含多种数据类型的 DataFrame 被转换为 JSON。

从此 JSON 生成的 DataFrame 与初始 DataFrame 相同(可逆性)。

使用现有的 JSON 接口,这种转换是不可能的。

此示例使用了 ntv-pandas 仓库中定义的 ntv_pandas 模块。

数据示例

In [1]: from shapely.geometry import Point
        from datetime import date
        import pandas as pd
        import ntv_pandas as npd

In [2]: data = {'index':           [100, 200, 300, 400, 500, 600],
                'dates::date':     [date(1964,1,1), date(1985,2,5), date(2022,1,21), date(1964,1,1), date(1985,2,5), date(2022,1,21)],
                'value':           [10, 10, 20, 20, 30, 30],
                'value32':         pd.Series([12, 12, 22, 22, 32, 32], dtype='int32'),
                'res':             [10, 20, 30, 10, 20, 30],
                'coord::point':    [Point(1,2), Point(3,4), Point(5,6), Point(7,8), Point(3,4), Point(5,6)],
                'names':           pd.Series(['john', 'eric', 'judith', 'mila', 'hector', 'maria'], dtype='string'),
                'unique':          True }

In [3]: df = pd.DataFrame(data).set_index('index')

In [4]: df
Out[4]:       dates::date  value  value32  res coord::point   names  unique
        index
        100    1964-01-01     10       12   10  POINT (1 2)    john    True
        200    1985-02-05     10       12   20  POINT (3 4)    eric    True
        300    2022-01-21     20       22   30  POINT (5 6)  judith    True
        400    1964-01-01     20       22   10  POINT (7 8)    mila    True
        500    1985-02-05     30       32   20  POINT (3 4)  hector    True
        600    2022-01-21     30       32   30  POINT (5 6)   maria    True

JSON 表示

In [5]: df_to_json = npd.to_json(df)
        pprint(df_to_json, width=120)
Out[5]: {':tab': {'coord::point': [[1.0, 2.0], [3.0, 4.0], [5.0, 6.0], [7.0, 8.0], [3.0, 4.0], [5.0, 6.0]],
                  'dates::date': ['1964-01-01', '1985-02-05', '2022-01-21', '1964-01-01', '1985-02-05', '2022-01-21'],
                  'index': [100, 200, 300, 400, 500, 600],
                  'names::string': ['john', 'eric', 'judith', 'mila', 'hector', 'maria'],
                  'res': [10, 20, 30, 10, 20, 30],
                  'unique': [True, True, True, True, True, True],
                  'value': [10, 10, 20, 20, 30, 30],
                  'value32::int32': [12, 12, 22, 22, 32, 32]}}

可逆性

In [5]: df_from_json = npd.read_json(df_to_json)
        print('df created from JSON is equal to initial df ? ', df_from_json.equals(df))
Out[5]: df created from JSON is equal to initial df ?  True

链接的 NoteBook 中提供了其他几个示例

Table Schema JSON 接口示例

在下面的示例中,一个包含多种 Table Schema 数据类型的 DataFrame 被转换为 JSON。

从此 JSON 生成的 DataFrame 与初始 DataFrame 相同(可逆性)。

使用现有的 Table Schema JSON 接口,这种转换是不可能的。

In [1]: from shapely.geometry import Point
        from datetime import date

In [2]: df = pd.DataFrame({
            'end february::date': ['date(2023,2,28)', 'date(2024,2,29)', 'date(2025,2,28)'],
            'coordinates::point': ['Point([2.3, 48.9])', 'Point([5.4, 43.3])', 'Point([4.9, 45.8])'],
            'contact::email':     ['[email protected]', '[email protected]', '[email protected]']
            })

In [3]: df
Out[3]: end february::date coordinates::point             contact::email
        0         2023-02-28   POINT (2.3 48.9)         john.doe@table.com
        1         2024-02-29   POINT (5.4 43.3)    lisa.minelli@schema.com
        2         2025-02-28   POINT (4.9 45.8)  walter.white@breaking.com

JSON 表示

In [4]: df_to_table = npd.to_json(df, table=True)
        pprint(df_to_table, width=140, sort_dicts=False)
Out[4]: {'schema': {'fields': [{'name': 'index', 'type': 'integer'},
                               {'name': 'end february', 'type': 'date'},
                               {'name': 'coordinates', 'type': 'geopoint', 'format': 'array'},
                               {'name': 'contact', 'type': 'string', 'format': 'email'}],
                    'primaryKey': ['index'],
                    'pandas_version': '1.4.0'},
         'data': [{'index': 0, 'end february': '2023-02-28', 'coordinates': [2.3, 48.9], 'contact': '[email protected]'},
                  {'index': 1, 'end february': '2024-02-29', 'coordinates': [5.4, 43.3], 'contact': '[email protected]'},
                  {'index': 2, 'end february': '2025-02-28', 'coordinates': [4.9, 45.8], 'contact': '[email protected]'}]}

可逆性

In [5]: df_from_table = npd.read_json(df_to_table)
        print('df created from JSON is equal to initial df ? ', df_from_table.equals(df))
Out[5]: df created from JSON is equal to initial df ?  True

链接的 NoteBook 中提供了其他几个示例

范围

目标是使所提出的 JSON 接口可用于任何类型的数据,以及用于 orient="table" 选项或新的 orient="ntv" 选项。

所提出的接口与现有数据兼容。

动机

为何将 orient=table 选项扩展到其他数据类型?

为什么拥有紧凑且可逆的 JSON 接口很重要?

考虑扩展类型是否相关?

这只对 pandas 有用吗?

描述

所提出的解决方案基于几个关键点

数据类型

数据类型在 NTV 项目中定义和管理(名称、JSON 编码器和解码器)。

Pandas dtype 与 NTV 类型兼容

pandas dtype NTV type
intxx intxx
uintxx uintxx
floatxx floatxx
datetime[ns] datetime
datetime[ns,] datetimetz
timedelta[ns] durationiso
string string
boolean boolean

注意

JSON 类型(隐式或显式)按照 pandas JSON 接口转换为 dtype

JSON 类型 pandas dtype
number int64 / float64
string string / object
array object
object object
true, false boolean
null NaT / NaN / None

注意

其他 NTV 类型与 object dtype 相关联。

TableSchema 与 pandas 之间的对应关系

TableSchema 类型由两个属性 formattype 携带。

下表显示了 TableSchema format / type 与 pandas NTVtype / dtype 之间的对应关系

format / type NTV type / dtype
default / datetime / datetime64[ns]
default / number / float64
default / integer / int64
default / boolean / bool
default / string / object
default / duration / timedelta64[ns]
email / string email / string
uri / string uri / string
default / object object / object
default / array array / object
default / date date / object
default / time time / object
default / year year / int64
default / yearmonth month / int64
array / geopoint point / object
default / geojson geojson / object

注意

JSON 格式

TableSchema 接口的 JSON 格式是现有的。

全局接口的 JSON 格式在 JSON-TAB 规范中定义。它包含了最初在 JSON-ND 项目中定义的命名规则以及对分类数据的支持。该规范需要更新以包含稀疏数据。

转换

当数据关联到非 object dtype 时,使用 pandas 转换方法。否则,使用 NTV 转换。

pandas -> JSON

JSON -> pandas

用法和影响

用法

在我看来,这项提案回应了一些重要问题

兼容性

接口可以在没有 NTV 类型的情况下使用(与现有数据兼容 - 见示例

如果接口可用,在 JSON 接口中抛出一个新的 orient 选项,此特性的使用与其他特性解耦。

对 pandas 框架的影响

最初,影响非常有限

在后续阶段,可以考虑多种开发

做与不做的风险

JSON-NTV 格式和 JSON-TAB 格式尚未被认可和使用。对 pandas 的风险在于此功能可能不会被使用(无功能影响)。

另一方面,pandas 的早期使用将有助于更好地考虑 pandas 的期望和需求,并促使对 pandas 支持的类型演变进行反思。

实现

模块

NTV 定义了两个模块

pandas 集成此 JSON 接口只需要导入 json-ntv 模块。

实现选项

该接口可以实现为 NTV 连接器(SeriesConnectorDataFrameConnector),并作为新的 pandas JSON 接口 orient 选项。

有几种可能的 pandas 实现方式

  1. 外部实现

    在此实现中,接口仅在 NTV 端可用。此选项意味着 JSON 接口的这种演进对 pandas 来说不是有用或战略性的。

  2. NTV 端实现

    在此实现中,接口在两侧都可用,转换位于 NTV 内部。此选项对 pandas 侧的影响最小

  3. pandas 端实现

    在此实现中,接口在两侧都可用,转换位于 pandas 内部。此选项允许 pandas 保持对这种演进的控制

  4. pandas 受限实现

    在此实现中,pandas 接口和转换位于 pandas 内部,且仅适用于非 object dtype。此选项可以在提供紧凑且可逆接口的同时,禁止引入与现有 dtype 不兼容的类型

常见问题解答

问:orient="table" 不正是您已经提出的内容吗?

:原则上是的,此选项考虑了类型的概念。

但这非常有限(见 Notebook 中添加的示例)

当前的接口与 table-schema 定义的数据结构不兼容。为了实现这一点,有必要集成一个像所提出的“类型扩展”(这已经在多个格式的接口中通过 extDtype 的概念部分实现)。

问:一般来说,read_json/to_json 中只应该有一种 `"table"` 格式。如果我们更改格式,还存在向后兼容性问题。表格接口存在错误的事实不是添加新接口的理由(我宁愿修复这些错误)。现有格式是否可以进行调整以解决类型问题/往返转换问题?

:我将补充两点说明

我认为问题不能仅限于错误修复,需要为 Json 接口定义明确的策略,特别是在开放数据解决方案中逐步淘汰过时的 CSV 格式,转而采用 Json 格式。

如前所述,所提出的解决方案解决了当前接口的几个缺点,并且可以简单地融入 pandas 环境中(另一个选项是认为 Json 接口是 pandas 的一个外围功能,可以保持在 pandas 外部),无论 orient='table' 选项如何。

尽管如此,仍有可能合并提议的格式和 orient='table' 格式,以便明确管理 extDtype 的概念

问:据我所知,JSON NTV 并非任何形式的标准化 JSON 格式。我相信 pandas(以及 geopandas,我正是从那里了解此问题的)应努力遵循事实或法律标准,并且不应选择当前没有任何社区支持的文件格式。这显然将来可能会改变,届时应修订此 PR。为什么 pandas 会使用这个标准?

:如问题中所示(并在 附加的 Notebook 中详细介绍),json 接口不可逆(to_json 后再 read_json 并非总是返回初始对象),并且存在多个缺点和错误。此问题的主要原因是 JSON 格式未考虑数据类型(或使用 orient='table' 选项时只考虑了很小一部分)。

所提出的建议解决了这个问题(Notebook 开头的示例简单明了地阐释了该建议的价值)。

关于基础的 JSON-NTV 格式,它对表格数据的影响相当低(仅限于在字段名称中添加类型)。然而,这个问题是相关的:JSON-NTV 格式(IETF Internet-Draft)是一个共享、有文档、受支持且已实现的格式,但目前社区支持确实较少,但它只需要扩展!!

综合分析

总结,

核心团队决定

投票开放时间为 9 月 11 日至 9 月 26 日

反对意见评论 :

决定:

时间线

不适用

PDEP 历史