pandas拡張配列

拡張性は、過去数回のリリースにおけるpandas開発の主要なテーマでした。この記事では、pandas拡張配列インターフェースについて紹介します。その背景にある動機と、pandasユーザーとしてどのように影響するかについて説明します。最後に、拡張配列がpandasの将来をどのように形作る可能性があるかを見ていきます。

拡張配列は、pandas 0.24.0の変更点の1つに過ぎません。変更点で完全な変更ログを確認してください。

動機

PandasはNumPyを基盤として構築されています。SeriesをNumPy配列のラッパーとして、DataFrameを共有インデックスを持つSeriesの集合として大まかに定義できます。いくつかの理由からこれは完全に正しいわけではありませんが、「NumPy配列のラッパー」の部分に焦点を当てたいと思います。「配列のようなオブジェクトのラッパー」と言う方が正確です。

Pandasは主にNumPyの組み込みデータ表現を使用していますが、いくつかの場所で制限し、他の場所で拡張しています。例えば、pandasの初期ユーザーは、NumPyがサポートしていないタイムゾーン対応の日時に非常に関心がありました。そのため、pandasは内部的に`DatetimeTZ`dtype(NumPyのdtypeを模倣したもの)を定義し、`Index`、`Series`、および`DataFrame`の列で使用できるようにしました。そのdtypeはtzinfoを保持していましたが、それ自体は有効なNumPy dtypeではありませんでした。

別の例として、`Categorical`を考えてみましょう。これは実際には2つの配列で構成されています。1つは`categories`用、もう1つは`codes`用です。しかし、他の列と同じように`DataFrame`に格納できます。

pandasに追加されたこれらの拡張タイプのそれぞれは、それ自体で有用ですが、高いメンテナンスコストを伴います。コードベースの大きな部分が、NumPy配列またはこれらの他の種類の特殊な配列をどのように処理するかを認識する必要があります。これにより、pandasへの新しい拡張タイプの追加が非常に困難になりました。

Anaconda, Inc.には、IPアドレスを含むデータセットを定期的に処理するクライアントがいました。彼らは、IPArrayをpandasに追加することが理にかなうかどうか疑問に思っていました。最終的に、pandas自体への組み込みのコスト便益テストに合格するとは考えませんでしたが、pandasのサードパーティ拡張のためのインターフェースを定義することに関心がありました。このインターフェースを実装するオブジェクトは、pandasで使用できます。私はpandasの外でcyberpandasを書くことができましたが、pandasに組み込まれている他のdtypeを使用しているように感じます。

現状

pandas 0.24.0現在、pandasの内部拡張配列(Categorical、タイムゾーン付きDatetime、Period、Interval、Sparse)はすべて、ExtensionArrayインターフェースの上に構築されています。ユーザーは多くの変更に気付くことはありません。主な違いは、オブジェクトdtypeにキャストされる場所が少なくなり、コードの実行速度が向上し、型がより安定することです。これには、`Series`への`Period`および`Interval`データの格納が含まれます(以前はオブジェクトdtypeにキャストされていました)。

さらに、比較的簡単に新しい拡張配列を追加できるようになります。例えば、0.24.0では(オプションで)、pandasの長年の悩みの種であった1つ、つまり欠損値が整数dtypeの値を浮動小数点数にキャストするという問題を解決しました。

>>> int_ser = pd.Series([1, 2], index=[0, 2])
>>> int_ser
0    1
2    2
dtype: int64

>>> int_ser.reindex([0, 1, 2])
0    1.0
1    NaN
2    2.0
dtype: float64

新しいIntegerArrayとnull許容整数dtypeを使用すると、欠損値を含む整数データをネイティブに表現できます。

>>> int_ser = pd.Series([1, 2], index=[0, 2], dtype=pd.Int64Dtype())
>>> int_ser
0    1
2    2
dtype: Int64

>>> int_ser.reindex([0, 1, 2])
0      1
1    NaN
2      2
dtype: Int64

SeriesまたはIndex内に格納されている生の(ラベルなしの)配列にアクセスする方法がわずかに変更されます。これは時々役立ちます。おそらく、呼び出しているメソッドはNumPy配列でのみ機能するか、自動アラインメントを無効にしたい場合があるでしょう。

過去には、「SeriesまたはDataFrameからNumPy配列を抽出するには`.values`を使用してください」というようなことを耳にしたかもしれません。それが良いリソースであれば、例外があるので、それは完全に正しいわけではないことを教えてくれるでしょう。これらの例外について詳しく見ていきたいと思います。

`.values`の基本的な問題は、2つの目的を果たしていることです。

  1. Series、Index、またはDataFrameを裏付ける配列の抽出
  2. Series、Index、またはDataFrameをNumPy配列に変換する

上記のように、SeriesまたはIndexを裏付ける「配列」はNumPy配列ではない可能性があり、代わりに拡張配列(pandasまたはサードパーティライブラリのもの)である可能性があります。例えば、`Categorical`を考えてみましょう。

>>> cat = pd.Categorical(['a', 'b', 'a'], categories=['a', 'b', 'c'])
>>> ser = pd.Series(cat)
>>> ser
0    a
1    b
2    a
dtype: category
Categories (3, object): ['a', 'b', 'c']

>>> ser.values
[a, b, a]
Categories (3, object): ['a', 'b', 'c']

この場合、`.values`はNumPy配列ではなくCategoricalです。period-dtypeデータの場合、`.values`は`Period`オブジェクトのNumPy配列を返し、これは作成コストが高くなります。タイムゾーン対応データの場合、`.values`はUTCに変換され、タイムゾーン情報は削除されます。これらの予期しない事態(異なる型、または高価または損失のある変換)は、これらの拡張配列をNumPy配列に無理やり押し込もうとすることから生じます。しかし、拡張配列のポイントは、NumPyがネイティブに表現できないデータを表現することです。

`.values`の問題を解決するために、その役割を2つの専用メソッドに分割しました。

  1. 基礎となるデータへのゼロコピー参照を取得するには`.array`を使用します。
  2. データの(高価で、損失の可能性のある)NumPy配列を取得するには`.to_numpy()`を使用します。

したがって、Categoricalの例では、

>>> ser.array
[a, b, a]
Categories (3, object): ['a', 'b', 'c']

>>> ser.to_numpy()
array(['a', 'b', 'a'], dtype=object)

要約すると

`.values`はもう必要ありません。

将来の可能性

拡張配列は、非常に多くのエキサイティングな機会を開きます。現在、pandasはNumPy配列内のPythonオブジェクトを使用して文字列データを表現していますが、これは遅いです。Apache Arrowのようなライブラリは、可変長文字列をネイティブにサポートしており、FletcherライブラリはArrow配列用のpandas拡張配列を提供します。これにより、GeoPandasはジオメトリデータをより効率的に格納できるようになります。Pandas(またはサードパーティライブラリ)は、ネストされたデータ、単位付きデータ、地理データ、GPU配列をサポートできるようになります。pandasエコシステムページに注目してください。そこでは、サードパーティの拡張配列を追跡します。pandas開発にとってエキサイティングな時代です。

その他の考察

これはインターフェースであり、具体的な配列の実装ではないことを強調したいと思います。ここではpandasでNumPyを再実装しているわけではありません。むしろ、これは、任意の配列のようなデータ構造(1つ以上のNumPy配列、Apache Arrow配列、CuPy配列)を取り、DataFrameに配置する方法です。pandasを配列ビジネスから外し、代わりにより高レベルの表形式データについて考えることは、プロジェクトにとって健全な発展だと思います。

これは、NumPyの`__array_ufunc__`プロトコルとNEP-18と完全に連携します。NumPyメモリによって裏付けられていないオブジェクトでも、使い慣れたNumPy APIを使用できます。

アップグレード

これらの新しい機能はすべて、最近リリースされたpandas 0.24で利用できます。

conda

conda install -c conda-forge pandas

pip

pip install --upgrade pandas

いつものように、メーリングリスト@pandas-dev、またはissue trackerでフィードバックをお待ちしています。

pandasコミュニティに関わっている多くの貢献者、メンテナー、機関パートナーに感謝します。