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 个)。
一些 JSON 接口问题在 链接的 Notebook 中有详细说明。
功能描述
为了获得一个简单、紧凑且可逆的解决方案,我建议使用 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
在 链接的 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 中提供了其他几个示例
范围
目标是为任何类型的数据以及 orient="table"
选项或新的 orient="ntv"
选项提供建议的 JSON 接口。
建议的接口与现有数据兼容。
动机
为什么要将 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 类型 |
---|---|
intxx | intxx |
uintxx | uintxx |
floatxx | floatxx |
datetime[ns] | datetime |
datetime[ns, |
datetimetz |
timedelta[ns] | durationiso |
string | string |
boolean | boolean |
注意
- 带时区的 datetime 是一个单独的 NTV 类型(字符串 ISO8601)
CategoricalDtype
和SparseDtype
包含在表格 JSON 格式中object
dtype
取决于上下文(见下文)PeriodDtype
和IntervalDtype
有待定义
JSON 类型(隐式或显式)在 dtype
中转换,遵循 pandas JSON 接口
JSON 类型 | pandas dtype |
---|---|
number | int64 / float64 |
string | string / object |
array | object |
object | object |
true, false | boolean |
null | NaT / NaN / None |
注意
- 如果定义了 NTV 类型,则
dtype
会相应调整 - 需要澄清空类型数据的考虑因素
其他 NTV 类型与 object
dtype
相关联。
TableSchema 与 pandas 之间的对应关系
TableSchema 类型由两个属性 format
和 type
携带。
下表显示了 TableSchema 格式/类型与 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 | 日期 / 对象 |
默认 / 时间 | 时间 / 对象 |
默认 / 年份 | 年份 / int64 |
默认 / 年月 | 月份 / int64 |
数组 / 地理点 | 点 / 对象 |
默认 / GeoJSON | GeoJSON / 对象 |
注意
- 其他 TableSchema 格式已定义,需要进一步研究(uuid、二进制、topojson、地理点和日期的特定格式)
- 前六行对应于现有的
JSON 格式
TableSchema 接口的 JSON 格式是现有的。
Global 接口的 JSON 格式在 JSON-TAB 规范中定义。它包括最初在 JSON-ND 项目 中定义的命名规则,并支持分类数据。该规范需要更新以包含稀疏数据。
转换
当数据与非object
dtype
关联时,使用 pandas 转换方法。否则,使用 NTV 转换。
pandas -> JSON
NTV 类型
未定义:使用to_json()
NTV 类型
已定义且dtype
不是object
:使用to_json()
NTV 类型
已定义且dtype
是object
:使用 NTV 转换(如果 pandas 转换不存在)
JSON -> pandas
NTV 类型
与dtype
兼容:使用read_json()
NTV 类型
与dtype
不兼容:使用 NTV 转换(如果 pandas 转换不存在)
使用和影响
使用
在我看来,这个提案解决了重要问题
-
拥有一个高效的文本格式用于数据交换
替代的 CSV 格式不可逆且已过时(最后一次修订在 2005 年)。当前的 CSV 工具不符合标准。
-
在 pandas 对象中考虑“语义”数据
-
拥有一个完整的 Table Schema 接口
兼容性
接口可以在没有 NTV 类型的情况下使用(与现有数据兼容 - 查看示例)
如果接口可用,在 JSON 接口中添加一个新的 orient
选项,该功能的使用与其他功能解耦。
对 pandas 框架的影响
最初,影响非常有限
- 修改
Series
或DataFrame 列
的名称
(没有功能影响), - 在 JSON 接口中添加了一个选项(例如
orient='ntv'
),并添加了相关方法(不会对其他方法造成功能性干扰)。
在后续阶段,可以考虑几个发展方向。
- 对
Series
或DataFrame
列的name
进行验证。 - 将 NTV 类型作为“补充对象数据类型”进行管理。
- 根据 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 内部,并且仅适用于非对象
dtype
。这种方式可以提供一个紧凑且可逆的接口,同时禁止引入与现有dtype
不兼容的类型。
常见问题解答
问:orient="table"
是否已经实现了您提出的功能?
答:原则上,是的,这个选项考虑了类型的概念。
但它非常有限(请参阅 笔记本 中添加的示例)。
- 类型和 JSON 接口
- 在 JSON 接口中保留类型的唯一方法是使用
orient='table'
选项。 - JSON 表格接口不允许使用以下几种数据类型:period、timedelta64、interval。
- JSON 表格接口并不总是保留允许的类型。
- 如果数据类型为 'object',则只有当数据为字符串时才会保留。
- 对于分类数据类型,底层数据类型不会包含在 JSON 接口中。
- 数据紧凑性
- JSON 表格接口并不紧凑(在 笔记本 中的示例中),其大小是紧凑格式的三倍或四倍。
- 可逆性
- 接口仅对以下几种数据类型可逆:int64、float64、bool、string、datetime64 和部分分类数据类型。
- 外部类型
- 接口不接受外部类型。
- Table-schema 定义了 20 种数据类型,但
orient="table"
接口只考虑了 5 种数据类型(参见 表格)。 - 要集成外部类型,需要先创建 ExtensionArray 和 ExtensionDtype 对象。
当前接口与 table-schema 定义的数据结构不兼容。要实现兼容,需要集成一个“类型扩展”,类似于所提出的扩展(这在一定程度上已经通过接口中针对多种格式的 extDtype
概念实现了)。
问:一般来说,我们应该只在 pandas 的 read_json/to_json 中使用 1 个 "table"
格式。如果我们更改格式,还会存在向后兼容性问题。表格接口存在 bug 并不是添加新接口的理由(我更愿意修复这些 bug)。现有的格式能否以某种方式进行调整以解决类型问题/往返转换问题?
答:我将补充两点说明。
- Tableschema 中定义的类型部分被考虑在内(接口中未考虑的类型示例:string-uri、array、date、time、year、geopoint、string-email)。
read_json()
接口也能处理以下数据:{'simple': [1,2,3] }
(与文档中说明的相反),但无法使用to_json()
重新创建这个简单的 JSON。
我认为问题不能仅仅通过修复 bug 来解决,需要制定一个明确的 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 互联网草案)是一种共享的、有文档记录的、受支持的和已实现的格式,但实际上社区支持目前有限,但它只需要扩展!
综合
总之,
- 如果对任何类型的数据都必须有一个可逆的 JSON 接口(或具有战略意义),则可以允许该方案,
- 如果不是,则应考虑在 生态系统 中列出的第三方包,该包可以将此格式读写到/从 pandas DataFrame 中。
核心团队决定
投票从 9 月 11 日到 9 月 26 日开放。
- 最终投票结果为 0 票赞成,5 票弃权,7 票反对。已达到法定人数。PDEP 提案失败。
反对意见 :
- 1 鉴于提议的 JSON NTV 格式的最新性,我支持(如 PDEP 中所述):“如果没有,则应考虑生态系统中列出的读取/写入此格式到/从 pandas DataFrame 的第三方软件包”
- 2 与 -1- 的原因相同,这应该是一个第三方软件包
- 3 不够成熟,而且市场规模尚不清楚。
- 4 出于与我在 PDEP 中留下的相同原因:“我认为这(JSON-NTV 格式)不符合成为 pandas 中普遍使用的格式的标准”
- 5 同意 -4-
- 6 同意其他核心开发人员的回应。我认为现有 json 接口中的工作非常有价值。最初提出的许多问题只是现有功能的错误修复/扩展。尝试从头开始可能不值得迁移工作。也就是说,如果社区中对某种格式有很好的支持,我们可以在将来重新考虑(显然 json 有很好的支持,但这里详细说明的实际规范太新了/没有被接受为标准)
- 7 虽然我认为拥有更全面的 JSON 格式会很有价值,但将新格式作为 pandas 的一部分意味着隐式认可仍在由更广泛的社区审查的标准。
决定:
- 在 生态系统 中添加
ntv-pandas
软件包 - 在稍后的阶段重新审视此 PDEP,例如在 1/2 到 1 年内(基于互联网草案 JSON 语义格式 (JSON-NTV) 的演变和 ntv-pandas 的使用情况)
时间线
不适用
PDEP 历史
- 2023 年 6 月 16 日:初始草案
- 2023 年 7 月 22 日:添加常见问题解答
- 2023 年 9 月 6 日:添加表格模式扩展
- 2023 年 10 月 1 日:添加核心团队决定