PDEP-9: サードパーティプロジェクトが標準APIでpandasコネクタを登録できるようにする

PDEP要約

このドキュメントは、pandasへのI/Oまたはメモリコネクタを実装するサードパーティプロジェクトが、Pythonのエントリーポイントシステムを使用してそれらを登録し、通常のpandas I/Oインターフェースでpandasユーザーが利用できるようにすることを提案します。例えば、pandasとは独立したパッケージがDuckDBからのリーダーとDelta Lakeへのライターを実装でき、ユーザー環境にインストールされると、ユーザーはそれらがpandasに実装されているかのように使用できます。例えば

import pandas

pandas.load_io_plugins()

df = pandas.DataFrame.read_duckdb("SELECT * FROM 'my_dataset.parquet';")

df.to_deltalake('/delta/my_dataset')

これにより、既存のコネクタ数を容易に拡張でき、新しい形式やデータベースエンジン、データレイク技術、アウトオブコアコネクタ、新しいADBCインターフェースなどをサポートすると同時に、pandasコードベースのメンテナンスコストを削減できます。

現状

pandasは、現在pandas/ioに実装されているI/Oコネクタや、Pythonの構造体やその他のライブラリ形式のようなインメモリ構造へのコネクタを使用して、さまざまな形式からのデータのインポートとエクスポートをサポートしています。多くの場合、これらのコネクタは既存のPythonライブラリをラップしていますが、他のいくつかのケースでは、pandasが特定の形式の読み書きロジックを実装しています。

場合によっては、同じ形式に対して異なるエンジンが存在します。これらのコネクタを使用するためのAPIは、データをインポートするためにpandas.read_<format>(engine='<engine-name>', ...)、データをエクスポートするためにDataFrame.to_<format>(engine='<engine-name>', ...)です。

メモリにエクスポートされるオブジェクト(Python辞書など)の場合、APIはI/Oと同じで、DataFrame.to_<format>(...)です。メモリ内のオブジェクトからインポートされる形式の場合、APIはread_ではなくfrom_プレフィックスを使用してDataFrame.from_<format>(...)と異なります。

場合によっては、pandas APIはDataFrame.to_*メソッドを提供しますが、これらはデータをディスクまたはメモリオブジェクトにエクスポートするためではなく、DataFrameのインデックスを変換するために使用されます。例えばDataFrame.to_periodDataFrame.to_timestampです。

コネクタの依存関係はデフォルトではロードされず、コネクタが使用されるときにインポートされます。依存関係がインストールされていない場合、ImportErrorが発生します。

>>> pandas.read_gbq(query)
Traceback (most recent call last):
  ...
ImportError: Missing optional dependency 'pandas-gbq'.
pandas-gbq is required to load data from Google BigQuery.
See the docs: https://pandas-gbq.readthedocs.io.
Use pip or conda to install pandas-gbq.

サポートされる形式

形式のリストはIOガイドで確認できます。インメモリオブジェクトやDataFrameスタイラーのI/Oコネクタを含む、より詳細な表を以下に示します。

形式 リーダー ライター エンジン
CSV X X c, python, pyarrow
FWF X c, python, pyarrow
JSON X X ujson, pyarrow
HTML X X lxml, bs4/html5lib (パラメータ flavor)
LaTeX X
XML X X lxml, etree (パラメータ parser)
クリップボード X X
Excel X X xlrd, openpyxl, odf, pyxlsb (各エンジンは異なるファイル形式をサポート)
HDF5 X X
フェザー X X
Parquet X X pyarrow, fastparquet
ORC X X
Stata X X
SAS X
SPSS X
Pickle X X
SQL X X sqlalchemy, dbapi2 (conパラメータの型から推測)
BigQuery X X
dict X X
records X X
string X
markdown X
xarray X

このドキュメント執筆時点で、io/モジュールには100,000行近くのPython、C、Cythonコードが含まれています。

pandasに形式を含めるための客観的な基準はなく、上記のリストはほとんどの場合、開発者がpandasで特定の形式のコネクタを実装することに興味を持った結果です。

pandasで処理できるデータの既存の形式の数は常に増加しており、pandasが人気のある形式でも最新の状態を維持することは困難です。PyArrow、PySpark、Iceberg、DuckDB、Hive、Polarsなどへのコネクタを持つことはおそらく理にかなっています。

同時に、2019年のユーザー調査で示されているように、一部の形式は頻繁には使用されていません。これらのあまり人気のない形式には、SPSS、SAS、Google BigQuery、Stataが含まれます。なお、調査にはI/O形式のみ(レコードやxarrayのようなメモリ形式は含まれていません)が含まれていました。

すべての形式をサポートするメンテナンスコストは、コードの保守やプルリクエストのレビューだけでなく、CIシステムでの依存関係のインストール、コードのコンパイル、テストの実行などに費やされる時間にも大きなコストがかかります。

場合によっては、一部のコネクタの主要なメンテナーはpandasコア開発チームの一員ではなく、特定の形式に特化した人々です。

提案

現在のpandasのアプローチはかなりうまく機能していますが、pandasにかかるメンテナンスがそれほど大きくなく、同時にユーザーが関心のあるすべての異なる形式や表現と簡単かつ直感的に対話できる安定したソリューションを見つけることは困難です。

サードパーティパッケージはすでにpandasへのコネクタを実装できますが、それにはいくつかの制限があります。

このドキュメントは、これらの制限を克服する標準的な方法で、pandas I/Oコネクタの開発をサードパーティライブラリに開放することを提案します。

提案の実装

この提案を実装してもpandasに大きな変更は必要なく、次に定義するAPIが使用されます。

ユーザーAPI

ユーザーは、標準のパッケージングツール(pip、condaなど)を使用して、pandasコネクタを実装するサードパーティパッケージをインストールできるようになります。これらのコネクタは、pandasが対応するメソッドpandas.read_*pandas.DataFrame.to_*、およびpandas.Series.to_*を自動的に作成するために使用するエントリーポイントを実装する必要があります。このインターフェースによって任意の関数名またはメソッド名は作成されず、read_*およびto_*パターンのみが許可されます。

適切なパッケージをインストールし、関数pandas.load_io_plugins()を呼び出すだけで、ユーザーは次のようなコードを使用できるようになります。

import pandas

pandas.load_io_plugins()

df = pandas.read_duckdb("SELECT * FROM 'dataset.parquet';")

df.to_hive(hive_conn, "hive_table")

このAPIはメソッドチェーンを可能にします。

(pandas.read_duckdb("SELECT * FROM 'dataset.parquet';")
       .to_hive(hive_conn, "hive_table"))

I/O関数とメソッドの総数は少ないと予想されます。なぜなら、ユーザーは一般的に形式の小さなサブセットしか使用しないからです。あまり人気のない形式(SAS、SPSS、BigQueryなど)がpandasコアからサードパーティパッケージに移動された場合、現在の状態から実際に数を減らすことができます。これらのコネクタを移動することはこの提案の一部ではなく、後で別の提案で議論することができます。

プラグイン登録

サードパーティパッケージは、dataframe.ioグループの下で、実装するコネクタを定義するためにエントリーポイントを実装します。

例えば、read_duckdb関数を実装する架空のプロジェクトpandas_duckdbは、pyproject.tomlを使用して次のエントリーポイントを定義できます。

[project.entry-points."dataframe.io"]
reader_duckdb = "pandas_duckdb:read_duckdb"

ユーザーがpandas.load_io_plugins()を呼び出すと、dataframe.ioグループのエントリーポイントレジストリを読み取り、pandaspandas.DataFrame、およびpandas.Series名前空間にそれらのメソッドを動的に作成します。reader_またはwriter_で始まる名前のエントリーポイントのみがpandasによって処理され、エントリーポイントに登録された関数は、対応するpandas名前空間でpandasユーザーが利用できるようになります。キーワードreader_およびwriter_の後のテキストは、関数名に使用されます。上記の例では、エントリーポイント名reader_duckdbpandas.read_duckdbを作成します。名前がwriter_hiveのエントリーポイントは、メソッドDataFrame.to_hiveSeries.to_hiveを作成します。

reader_またはwriter_で始まらないエントリーポイントは、このインターフェースによって無視されますが、このAPIの将来の拡張やその他の関連するデータフレームI/Oインターフェースに使用できるため、例外は発生しません。

内部API

コネクタは、データフレーム相互運用APIを使用してpandasにデータを提供します。コネクタからデータが読み取られ、pandas.read_<format>への応答としてユーザーに返される前に、データはデータ相互運用インターフェースから解析され、pandas DataFrameに変換されます。実際には、コネクタはpandas DataFrameまたはPyArrow Tableを返す可能性が高いですが、インターフェースはデータフレーム相互運用APIを実装する任意のオブジェクトをサポートします。

コネクタのガイドライン

ユーザーにより良い、より一貫性のあるエクスペリエンスを提供するために、用語と動作を統一するためのガイドラインが作成されます。統一するトピックの一部を次に定義します。

名前の競合を避けるためのガイドライン。一部の形式には複数の実装が存在することが予想されるため、コネクタの名前付けに関するガイドラインが作成されます。複数のコネクタが存在する可能性があると予想される場合、最も簡単なアプローチは、形式としてto_<format>_<implementation-id>のような文字列を使用することです。例えば、LanceDBの場合、おそらく1つのコネクタしか存在しないため、lanceという名前を使用できます(これによりpandas.read_lanceまたはDataFrame.to_lanceが作成されます)。しかし、Arrow2 Rust実装に基づく新しいcsvリーダーの場合、ガイドラインはcsv_arrow2を使用してpandas.read_csv_arrow2などを作成することを推奨できます。

パラメータの存在と命名。多くのコネクタは、データの列のサブセットのみを読み込んだり、パスを扱ったりするなど、同様の機能を提供する可能性が高いため、コネクタ開発者への推奨事項の例は次のとおりです。

上記のものは、あくまでガイドラインの例であり、このPDEPが承認された後に別途開発されるガイドラインの提案ではないことに注意してください。

コネクタレジストリとドキュメント。コネクタとそのドキュメントの発見を簡素化するために、コネクタ開発者はプロジェクトを中央の場所に登録し、ドキュメントに標準構造を使用するように奨励できます。これにより、利用可能なコネクタとそのドキュメントを見つけるための統一されたウェブサイトを作成できます。また、特定の実装のドキュメントをカスタマイズし、最終的なAPIを含めることも可能になります。

コネクタの例

このセクションでは、この提案からすぐに恩恵を受ける可能性のあるコネクタの具体的な例をリストします。

PyArrowは現在Table.from_pandasTable.to_pandasを提供しています。新しいインターフェースを使用すると、DataFrame.from_pyarrowDataFrame.to_pyarrowも登録でき、PyArrowが環境にインストールされている場合、pandasユーザーは使い慣れたインターフェースでコンバーターを使用できます。PyArrowテーブルとのより良い統合については、#51760で議論されました。

現在のAPI:

pyarrow.Table.from_pandas(table.to_pandas()
                               .query('my_col > 0'))

提案されたAPI:

(pandas.read_pyarrow(table)
       .query('my_col > 0')
       .to_pyarrow())

PolarsVaex、およびその他のデータフレームフレームワークは、pandasとの相互運用性をより明示的なAPIで実現するサードパーティプロジェクトから恩恵を受ける可能性があります。Polarsとの統合は#47368で要求されました。

現在のAPI:

polars.DataFrame(df.to_pandas()
                   .query('my_col > 0'))

提案されたAPI:

(pandas.read_polars(df)
       .query('my_col > 0')
       .to_polars())

DuckDBは、データをロードする前に述語をプッシュできるアウトオブコアエンジンを提供しており、メモリをより効率的に使用し、ロード時間を大幅に短縮します。pandasはその積極的な性質上、これを簡単に実装できませんが、DuckDBローダーから恩恵を受けることができます。ローダーはすでにpandas内で実装できます(#45678で提案済み)、または任意のAPIを持つサードパーティ拡張として実装することもできます。しかし、この提案は、標準的で直感的なAPIを持つサードパーティ拡張の作成を可能にします。

pandas.read_duckdb("SELECT *
                    FROM 'dataset.parquet'
                    WHERE my_col > 0")

アウトオブコアアルゴリズムは、フィルタリングやグループ化などの一部の操作をデータのロードにプッシュします。これは現在不可能ですが、このインターフェースを使用してアウトオブコアアルゴリズムを実装するコネクタを開発できます。

Hive、Iceberg、Prestoなどのビッグデータシステムは、pandasにデータをロードするための標準的な方法から恩恵を受けることができます。また、クエリ結果をArrowとして返すことができる通常のSQLデータベースも、SQL AlchemyやPython構造に基づく既存のものよりも優れた高速なコネクタから恩恵を受けるでしょう。

ドメイン固有の形式を含むその他の形式も、明確で直感的なAPIでpandasコネクタを簡単に実装できます。

制限事項

この提案の実装には、ここで議論されているいくつかの制限があります。

今後の計画

このPDEPは、既存または将来のコネクタのためにより良いAPIをサポートすることに専念しています。pandasコードベースに存在するコネクタに変更を実装することは、このPDEPの範囲外です。

このPDEPに関連する将来の議論のアイデアには、以下が含まれます。

PDEP-9 履歴