PDEP-9: サードパーティプロジェクトが標準APIでpandasコネクタを登録できるようにする
- 作成日: 2023年3月5日
- ステータス: 却下
- 議論: #51799 #53005
- 著者: Marc Garcia
- 改訂: 1
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_periodやDataFrame.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コネクタには標準APIがなく、ユーザーはそれぞれを個別に学習する必要があります。pandasのI/O APIはread/writeやfrom/toではなくread/toを使用することで一貫性がなく、多くの場合、開発者はこの慣例を無視します。また、開発者がpandasの慣例に従ったとしても、コネクタの開発者が
pandasまたはDataFrame名前空間にその関数をモンキーパッチすることはめったにないため、名前空間は異なります。 - データをエクスポートするためのサードパーティのI/Oコネクタでは、メソッドチェーンは不可能です。これは、著者が
DataFrameクラスをモンキーパッチしない限りであり、これは推奨されるべきではありません。
このドキュメントは、これらの制限を克服する標準的な方法で、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グループのエントリーポイントレジストリを読み取り、pandas、pandas.DataFrame、およびpandas.Series名前空間にそれらのメソッドを動的に作成します。reader_またはwriter_で始まる名前のエントリーポイントのみがpandasによって処理され、エントリーポイントに登録された関数は、対応するpandas名前空間でpandasユーザーが利用できるようになります。キーワードreader_およびwriter_の後のテキストは、関数名に使用されます。上記の例では、エントリーポイント名reader_duckdbはpandas.read_duckdbを作成します。名前がwriter_hiveのエントリーポイントは、メソッドDataFrame.to_hiveとSeries.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などを作成することを推奨できます。
パラメータの存在と命名。多くのコネクタは、データの列のサブセットのみを読み込んだり、パスを扱ったりするなど、同様の機能を提供する可能性が高いため、コネクタ開発者への推奨事項の例は次のとおりです。
columns: データの列のサブセットを読み込むためにこの引数を使用します。リストまたはタプルを許可します。path: データセットがファイルディスク上のファイルである場合、この引数を使用します。文字列、pathlib.Pathオブジェクト、またはファイル記述子を許可します。文字列オブジェクトの場合、自動的にダウンロードされるURL、自動的に解凍される圧縮ファイルなどを許可します。これらの処理をより簡単かつ一貫性のある方法で扱うために、特定のライブラリを推奨できます。schema: スキーマを持たないデータセット(例:csv)の場合、Apache Arrowスキーマインスタンスの提供を許可し、提供されない場合は型を自動的に推論します。
上記のものは、あくまでガイドラインの例であり、このPDEPが承認された後に別途開発されるガイドラインの提案ではないことに注意してください。
コネクタレジストリとドキュメント。コネクタとそのドキュメントの発見を簡素化するために、コネクタ開発者はプロジェクトを中央の場所に登録し、ドキュメントに標準構造を使用するように奨励できます。これにより、利用可能なコネクタとそのドキュメントを見つけるための統一されたウェブサイトを作成できます。また、特定の実装のドキュメントをカスタマイズし、最終的なAPIを含めることも可能になります。
コネクタの例
このセクションでは、この提案からすぐに恩恵を受ける可能性のあるコネクタの具体的な例をリストします。
PyArrowは現在Table.from_pandasとTable.to_pandasを提供しています。新しいインターフェースを使用すると、DataFrame.from_pyarrowとDataFrame.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())
Polars、Vaex、およびその他のデータフレームフレームワークは、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コネクタを簡単に実装できます。
制限事項
この提案の実装には、ここで議論されているいくつかの制限があります。
- 複数のエンジンのサポート不足。現在のpandas I/O APIは、同じ形式に対して複数のエンジン(同じ関数名またはメソッド名)をサポートしています。例えば、
read_csv(engine='pyarrow', ...)です。エンジンのサポートには、特定の形式のすべてのエンジンが同じシグネチャ(同じパラメータ)を使用する必要があり、これは理想的ではありません。異なるコネクタは異なるパラメータを持つ可能性が高く、*argsおよび**kwargsを使用すると、ユーザーにとってより複雑で困難なエクスペリエンスが提供されます。このため、この提案では、エンジンのオプションをサポートするのではなく、関数名とメソッド名が一意であることを優先します。 - コネクタの型チェックのサポート不足。このPDEPは関数とメソッドを動的に作成することを提案しており、これらはスタブを使用した型チェックにはサポートされていません。これは、カスタムアクセサなどのpandasの他の動的に作成されたコンポーネントでもすでに該当します。
- 現在のI/O APIへの改善がない。この提案の議論では、現在のpandas I/O APIを改善して、
read/to(例えばread/writeの代わりに)を使用する一貫性のない点を修正したり、非I/O操作にto_プレフィックスのメソッドを使用しないようにしたり、コネクタに専用の名前空間(例:DataFrame.io)を使用したりすることが検討されました。これらの変更はすべてこのPDEPの範囲外です。
今後の計画
このPDEPは、既存または将来のコネクタのためにより良いAPIをサポートすることに専念しています。pandasコードベースに存在するコネクタに変更を実装することは、このPDEPの範囲外です。
このPDEPに関連する将来の議論のアイデアには、以下が含まれます。
-
pandasがインポートされたときにI/Oプラグインを自動的にロードする。
-
SAS、SPSS、Google BigQueryなど、使用頻度の低いコネクタの一部をpandasコードベースから削除し、このインターフェースで登録されたサードパーティコネクタに移動する。
-
pandasコネクタのためにより良いAPIを議論する。例えば、
from_*メソッドの代わりにread_*メソッドを使用したり、I/Oコネクタとして使用されないto_*メソッドの名前を変更したり、from/to、read/write、load/dumpなどの一貫した用語を使用したり、コネクタに専用の名前空間(例えば、一般的なpandas名前空間の代わりにpandas.io)を使用したりする。 -
DataFrameコンストラクタがサポートする形式の一部をI/Oコネクタとして実装する。
PDEP-9 履歴
- 2023年3月5日: 初版
- 2023年5月30日: pandasの既存のAPI、データフレーム相互運用APIを使用し、ユーザーがプラグインを明示的にロードするように大幅なリファクタリング
- 2023年6月13日: PDEPは数回のイテレーションの後もサポートを得られず、著者によって却下としてクローズされました