PDEP-12: 紧凑且可逆的 JSON 接口
- 创建日期: 2023 年 6 月 16 日
- 状态: 已拒绝
- 讨论: #53252 #55038
- 作者: Philippe THOMY
- 版本: 3
摘要
- 概要
- 问题描述
- 特性描述
- 范围
- 动机
- 为什么拥有紧凑且可逆的 JSON 接口很重要?
- 考虑扩展类型是否相关?
- 这只对 pandas 有用吗?
- 描述
- 数据类型
- TableSchema 与 pandas 之间的对应关系
- JSON 格式
- 转换
- 用法和影响
- 用法
- 兼容性
- 对 pandas 框架的影响
- 做与不做的风险
- 实现
- 模块
- 实现选项
- 常见问题解答
- 综合分析
- 核心团队决定
- 时间线
- PDEP 历史
概要
问题描述
当前的 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
),从而能够拥有
- 一个遵循 Table Schema 规范的 Table Schema JSON 接口(
orient="table"
)(从 6 种类型扩展到 20 种类型), - 一个适用于所有 pandas 数据格式的全局 JSON 接口。
全局 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
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
范围
目标是使所提出的 JSON 接口可用于任何类型的数据,以及用于 orient="table"
选项或新的 orient="ntv"
选项。
所提出的接口与现有数据兼容。
动机
为何将 orient=table
选项扩展到其他数据类型?
- Table Schema 规范定义了 24 种数据类型,其中 6 种已在 pandas 接口中考虑
为什么拥有紧凑且可逆的 JSON 接口很重要?
- 可逆接口提供了一种交换格式。
- 文本交换格式有助于平台之间的交换(例如 OpenData)
- JSON 交换格式可以在 API 层面使用
考虑扩展类型是否相关?
- 它避免了额外数据模式的添加
- 它增加了 pandas 处理的数据的语义范围
- 它是对多个问题(例如 #12997, #14358, #16492, #35420, #35464, #36211, #39537, #49585, #50782, #51375, #52595, #53252)的解答
- 使用补充类型避免了修改 pandas 数据模型
这只对 pandas 有用吗?
- JSON-TAB 格式适用于表格数据和多维数据。
- 因此,此 JSON 接口可用于任何使用表格或多维数据的应用程序。例如,这将允许 pandas - DataFrame 和 Xarray - DataArray 之间可逆的数据交换(Xarray 问题正在构建中)见 DataFrame / DataArray 示例。
描述
所提出的解决方案基于几个关键点
- 数据类型
- TableSchema 与 pandas 之间的对应关系
- 表格数据的 JSON 格式
- 与 JSON 格式相互转换
数据类型
数据类型在 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 |
注意
- 带时区的 datetime 是一个单一的 NTV 类型(string ISO8601)
CategoricalDtype
和SparseDtype
包含在表格 JSON 格式中object
dtype
取决于上下文(见下文)PeriodDtype
和IntervalDtype
待定义
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 类型,
dtype
会相应调整 - 需要澄清对 null 类型数据的考虑
其他 NTV 类型与 object
dtype
相关联。
TableSchema 与 pandas 之间的对应关系
TableSchema 类型由两个属性 format
和 type
携带。
下表显示了 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 |
注意
- 其他 TableSchema 格式已定义并待研究(uuid, binary, topojson, 地理点和日期类型的特定格式)
- 前六行对应现有内容
JSON 格式
TableSchema 接口的 JSON 格式是现有的。
全局接口的 JSON 格式在 JSON-TAB 规范中定义。它包含了最初在 JSON-ND 项目中定义的命名规则以及对分类数据的支持。该规范需要更新以包含稀疏数据。
转换
当数据关联到非 object
dtype
时,使用 pandas 转换方法。否则,使用 NTV 转换。
pandas -> JSON
NTV type
未定义:使用to_json()
NTV type
已定义且dtype
不是object
:使用to_json()
NTV type
已定义且dtype
是object
:使用 NTV 转换(如果 pandas 转换不存在)
JSON -> pandas
NTV type
与dtype
兼容:使用read_json()
NTV type
与dtype
不兼容:使用 NTV 转换(如果 pandas 转换不存在)
用法和影响
用法
在我看来,这项提案回应了一些重要问题
-
拥有高效的数据交换文本格式
备选的 CSV 格式不可逆且已过时(最后一次修订是 2005 年)。当前的 CSV 工具不符合标准。
-
在 pandas 对象中考虑“语义”数据
-
拥有完整的 Table Schema 接口
兼容性
接口可以在没有 NTV 类型的情况下使用(与现有数据兼容 - 见示例)
如果接口可用,在 JSON 接口中抛出一个新的 orient
选项,此特性的使用与其他特性解耦。
对 pandas 框架的影响
最初,影响非常有限
- 修改
Series
或DataFrame columns
的name
(没有功能影响), - 在 Json 接口中添加一个选项(例如
orient='ntv'
),并添加相关方法(与其他方法没有功能冲突)
在后续阶段,可以考虑多种开发
- 验证
Series
或DataFrame columns
的name
, - 将 NTV 类型作为“补充对象 dtype”进行管理
- 根据 NTV 类型进行功能扩展
做与不做的风险
JSON-NTV 格式和 JSON-TAB 格式尚未被认可和使用。对 pandas 的风险在于此功能可能不会被使用(无功能影响)。
另一方面,pandas 的早期使用将有助于更好地考虑 pandas 的期望和需求,并促使对 pandas 支持的类型演变进行反思。
实现
模块
NTV 定义了两个模块
-
json-ntv
此模块管理 NTV 数据,不依赖于其他模块
-
ntvconnector
这些模块管理对象和 JSON 数据之间的转换。它们依赖于对象模块(例如,与 shapely 位置的连接器依赖于 shapely)。
pandas 集成此 JSON 接口只需要导入 json-ntv 模块。
实现选项
该接口可以实现为 NTV 连接器(SeriesConnector
和 DataFrameConnector
),并作为新的 pandas JSON 接口 orient
选项。
有几种可能的 pandas 实现方式
-
外部实现
在此实现中,接口仅在 NTV 端可用。此选项意味着 JSON 接口的这种演进对 pandas 来说不是有用或战略性的。
-
NTV 端实现
在此实现中,接口在两侧都可用,转换位于 NTV 内部。此选项对 pandas 侧的影响最小
-
pandas 端实现
在此实现中,接口在两侧都可用,转换位于 pandas 内部。此选项允许 pandas 保持对这种演进的控制
-
pandas 受限实现
在此实现中,pandas 接口和转换位于 pandas 内部,且仅适用于非
object
dtype
。此选项可以在提供紧凑且可逆接口的同时,禁止引入与现有dtype
不兼容的类型
常见问题解答
问:orient="table"
不正是您已经提出的内容吗?
答:原则上是的,此选项考虑了类型的概念。
但这非常有限(见 Notebook 中添加的示例)
- 类型和 Json 接口
- 在 json 接口中保留类型的唯一方法是使用
orient='table'
选项 - json-table 接口中不允许少数 dtype:period, timedelta64, interval
- 允许的类型并非总能在 json-table 接口中保留
- 只有数据是字符串时,才能保留 'object' dtype 的数据
- 对于 categorical dtype,基础 dtype 不包含在 json 接口中
- 数据紧凑性
- json-table 接口不紧凑(在 Notebook 中的示例中)大小是紧凑格式的三倍或四倍
- 可逆性
- 接口只对少数 dtype 可逆:int64, float64, bool, string, datetime64 和部分 categorical
- 外部类型
- 接口不接受外部类型
- Table-schema 定义了 20 种数据类型,但
orient="table"
接口只考虑了 5 种数据类型(见 表格) - 要集成外部类型,需要先创建 ExtensionArray 和 ExtensionDtype 对象
当前的接口与 table-schema 定义的数据结构不兼容。为了实现这一点,有必要集成一个像所提出的“类型扩展”(这已经在多个格式的接口中通过 extDtype
的概念部分实现)。
问:一般来说,read_json/to_json 中只应该有一种 `"table"` 格式。如果我们更改格式,还存在向后兼容性问题。表格接口存在错误的事实不是添加新接口的理由(我宁愿修复这些错误)。现有格式是否可以进行调整以解决类型问题/往返转换问题?
答:我将补充两点说明
- Tableschema 中定义的类型仅部分考虑(接口中未考虑的类型示例:string-uri, array, date, time, year, geopoint, string-email)
read_json()
接口也适用于以下数据:{'simple': [1,2,3] }
(与文档中所示相反),但使用to_json()
无法重新创建这个简单的 json。
我认为问题不能仅限于错误修复,需要为 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)是一个共享、有文档、受支持且已实现的格式,但目前社区支持确实较少,但它只需要扩展!!
综合分析
总结,
- 如果拥有一个适用于任何类型数据的可逆 JSON 接口是重要(或战略性)的,则可以允许该提案,
- 否则,应考虑在 生态系统中列出的一个第三方软件包,用于将此格式读写到/从 pandas DataFrames。
核心团队决定
投票开放时间为 9 月 11 日至 9 月 26 日
- 最终投票结果:0 票赞成,5 票弃权,7 票反对。已达到法定人数。PDEP 未通过。
反对意见评论 :
- 1 鉴于所提议的 JSON NTV 格式是新提出的,我支持(如 PDEP 中所述):“否则,应考虑在生态系统中列出的一个第三方软件包,用于将此格式读写到/从 pandas DataFrames。”
- 2 原因与 -1- 相同,目前这应该是一个第三方软件包
- 3 尚未足够成熟,市场规模不明确。
- 4 原因与我在 PDEP 中提出的相同:“我认为这种(JSON-NTV 格式)未达到在 pandas 内部实现时作为常用格式的标准”
- 5 同意 -4-
- 6 同意其他核心开发者回复者的意见。我认为对现有 json 接口进行的工作非常有价值。提出的许多原始问题仅仅是现有功能的错误修复/扩展。尝试从头开始可能不值得迁移工作。也就是说,如果将来社区广泛支持某种格式,我们可以重新考虑(显然 json 格式得到了很好的支持,但此处详细说明的实际规范太新了/未被接受为标准)
- 7 虽然我认为拥有一个更全面的 JSON 格式是值得的,但将新格式纳入 pandas 意味着隐性认可一个仍在接受更广泛社区审查的标准。
决定:
- 在 生态系统中添加
ntv-pandas
软件包 - 稍后,例如在 0.5 到 1 年后重新审议此 PDEP(基于互联网草案 JSON semantic format (JSON-NTV) 的演进以及 ntv-pandas 的使用情况)
时间线
不适用
PDEP 历史
- 2023 年 6 月 16 日: 初稿
- 2023 年 7 月 22 日: 添加常见问题解答
- 2023 年 9 月 6 日: 添加 Table Schema 扩展
- 10 月 1 日: 添加核心团队决定