PDEP-12: コンパクトで可逆なJSONインターフェース

概要

概要

問題の説明

現在のJSONインターフェースでは、dtypeと「Python型」が明示的に考慮されていません。

そのため、JSONインターフェースは常に可逆ではなく、dtypeの考慮に関連する矛盾を抱えています。

もう一つの結果として、orient="table"オプションにおけるTable Schema仕様の部分的な適用があります(定義された24のTable Schemaデータ型のうち、6つが考慮されています)。

いくつかのJSONインターフェースの問題は、リンクされたNoteBookで詳しく説明されています。

機能説明

シンプルでコンパクトかつ可逆なソリューションとして、私は型の概念を統合したJSON-NTV形式(Named and Typed Value)と、表形式データ用のそのバリエーションであるJSON-TABの使用を提案します(JSON-NTV形式はIETFインターネットドラフトで定義されています(まだ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型
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の型付けは、2つの属性formattypeによって行われます。

以下の表は、TableSchemaのformat/typeとpandasのNTVtype/dtypeの対応を示しています。

format / type NTV型 / 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用に2つのモジュールが定義されています。

pandasにJSONインターフェースを統合するには、json-ntvモジュールのみをインポートする必要があります。

実装オプション

このインターフェースは、NTVコネクタ(SeriesConnectorおよびDataFrameConnector)として、また新しいpandas JSONインターフェースorientオプションとして実装できます。

いくつかのpandas実装が可能です。

  1. 外部

    この実装では、インターフェースはNTV側でのみ利用可能です。このオプションは、JSONインターフェースのこの進化がpandasにとって有用でも戦略的でもないことを意味します。

  2. NTV側

    この実装では、インターフェースは両側で利用可能であり、変換はNTV内に配置されます。このオプションは、pandas側への影響を最小限に抑えるものです。

  3. pandas側

    この実装では、インターフェースは両側で利用可能であり、変換はpandas内に配置されます。このオプションにより、pandasはこの進化を制御し続けることができます。

  4. pandas制限

    この実装では、pandasインターフェースと変換はpandas内に配置され、非オブジェクトdtypeのみが対象となります。このオプションにより、既存のdtypeと互換性のない型の導入を禁止しながら、コンパクトで可逆なインターフェースを提供できます。

F.A.Q.

Q: orient="table"は、すでに提案されていることをやっていないのですか?

A: 原則的には、はい、このオプションは型の概念を考慮に入れています。

しかし、これは非常に限定的です(Notebookに追加された例を参照)。

現在のインターフェースは、table-schemaで定義されているデータ構造と互換性がありません。これを可能にするには、提案されているような「型拡張」を統合する必要があります(これは、いくつかの形式のインターフェースに見られるextDtypeの概念で、部分的に達成されています)。

Q: 一般的に、read_json/to_jsonではpandas用に1つの「table」形式しか持つべきではありません。また、形式を変更した場合の後方互換性の問題もあります。「table」インターフェースにバグがあるからといって、新しいインターフェースを追加する理由にはなりません(むしろそれらのバグを修正したいです)。既存の形式は、型に関する問題やラウンドトリップに関する問題を修正する方法で適応させることはできないのでしょうか?

A: 2つの追加のコメントを追加します。

この問題はバグ修正に限定されるものではなく、特にオープンデータソリューションで陳腐化したCSV形式がJson形式に徐々に置き換えられている現状を鑑みて、Jsonインターフェースに明確な戦略を定義する必要があると考えています。

述べたように、提案されたソリューションは現在のインターフェースのいくつかの欠点に対処し、orient='table'オプションに関係なく、単にpandas環境に適合することができます(もう一つの選択肢は、Jsonインターフェースがpandasの周辺機能であり、pandasの外部に留まることができると考えることです)。

それにもかかわらず、提案された形式とorient='table'形式を統合して、extDtypeの概念を明示的に管理することは可能です。

Q: 私が理解する限り、JSON NTVはいかなる形でも標準化されたJSON形式ではありません。私はpandas(そして、私がこの問題に興味を持つきっかけとなったgeopandas)は、デファクトまたはデジュリの標準に従うべきであり、現時点でコミュニティサポートのないファイル形式を選択すべきではないと考えています。これは将来的に明らかに変わる可能性があり、そのときにこのPRを再検討すべきです。なぜpandasはこの標準を使用するのでしょうか?

A: 問題に示されているように(そして添付のノートブックで詳述されているように)、jsonインターフェースは可逆的ではなく(to_jsonの後にread_jsonを実行しても常に元のオブジェクトが返されるわけではない)、いくつかの欠点やバグが存在します。この問題の主な原因は、データ型がJSON形式で考慮されていないこと(またはorient='table'オプションで非常に部分的にしか考慮されていないこと)です。

提案された内容は、この問題に答えるものです(ノートブックの冒頭の例は、この提案の関心事をシンプルかつ明確に示しています)。

基盤となるJSON-NTV形式に関して言えば、表形式データに対するその影響はかなり低い(フィールド名に型を追加するに限定される)です。しかし、この質問は適切です。JSON-NTV形式(IETFインターネットドラフト)は、共有され、文書化され、サポートされ、実装された形式ですが、確かに現時点でのコミュニティサポートは限られていますが、拡大を求めているだけです!

要約

結論として、

コアチームの決定

投票は9月11日から9月26日まで行われました。

不承認コメント :

決定:

タイムライン

該当なし

PDEP履歴