HOWTO · Pandas
Pandas で関数をベクトル化する
このチュートリアルでは、pandas で関数をベクトル化する方法を示します。
このページの内容
pandasで関数をベクトル化するには、まず計算をSeries全体またはNumPy配列全体に適用できる演算に書き換えます。これにより通常、値ごとにPython関数を呼び出す必要がなくなります。互換性のある配列演算でロジックを表現できない場合は、要素ごとの変換を明確に記述できる Series.map を使います。Series.apply も値ごとに処理する方法であり、ベクトル化に切り替える機能ではありません。
Pandasで関数をベクトル化する
この例では、給与調整を行う独自関数と配列式を比較します。データはコード内で作成するため、外部ファイルは必要ありません。次の例を実行してください。
このセクションのコードと表示されるラベルや出力は英語のままです。
import pandas as pd
pay = pd.Series([1000.0, 2000.0, 3000.0], name="pay")
def add_tax(value):
return value * 1.05 + 1
mapped = pay.map(add_tax)
vectorized = pay * 1.05 + 1
applied = pay.apply(add_tax)
reference = pd.Series([1051.0, 2101.0, 3151.0], name="pay")
print(vectorized.tolist())
print(mapped.equals(vectorized))
print(applied.equals(vectorized))
print(vectorized.equals(reference))
実行が成功すると、次のように表示されます。
[1051.0, 2101.0, 3151.0]
True
True
True
PandasのSeriesにおけるベクトル化とは
pandasのSeriesは、ラベル付きの値を保持します。pay * 1.05 + 1 のような式は、インデックスを保ちながらSeries全体に算術演算を適用します。このようなコードは配列指向です。各値に対してコードから明示的に add_tax を呼び出すのではなく、pandasとその基盤ライブラリがデータに演算を適用します。
ベクトル化は、演算の記述方法や処理方法を表すもので、特定の実装を保証するものではありません。pandasの演算でも内部でループを使うことがあり、SIMD命令が使われると決めつけるべきではありません。実用上の違いは、互換性のあるSeriesまたはNumPyの演算なら、項目ごとのPythonレベルの関数呼び出しを避けられる場合があることです。それが速いかどうかは、演算の内容、データ量、dtype、実行環境によって異なります。
Series.mapで要素ごとに関数を適用する
独自のスカラー関数で変換を最も明確に表せる場合は、Series.map を使います。この例の pay.map(add_tax) は、各値に対して add_tax を呼び出し、入力と同じインデックスを持つSeriesを返します。関数は1つのスカラー値を受け取り、変換後のスカラー値を返す必要があります。この方法は、対応する配列式がない独自ルールや検索変換に便利です。
固定の対応関係を使う場合は、辞書を Series.map に渡すこともできます。一致するキーがない値は欠損値になります。対応表がすべての入力候補を網羅していない場合は、この挙動を確認してください。単純な算術計算なら、演算を直接表現でき、スカラーのPython処理を要素ごとの呼び出しで包まずに済むSeries式を優先します。
関数を配列演算に書き換える
関数 add_tax は value * 1.05 + 1 を計算します。Seriesでは乗算と加算を使えるため、同じルールを pay * 1.05 + 1 と書けます。これが関数をベクトル化する際の実用的な形です。元のPython関数を繰り返し適用するのではなく、配列全体に対して動作する演算でロジックを表します。
DataFrameの列に適用するには、列をSeriesとして選択し、たとえば df["TotalPay"] * 1.05 + 1 のように同じ式を使います。元のデータとともに結果を保持したい場合は、新しい列に代入します。pandasでは代入時やSeries同士の算術演算時にインデックスで整列するため、別々に作成したデータを組み合わせる場合は、インデックスが対応しているか確認してください。
ベクトル化、map、applyの使い分け
対象の配列dtypeで演算が使える場合は、Series式またはNumPy式を選びます。値ごとの独自変換や検索には Series.map を使います。スカラー関数が必要で、map では処理を明確に表しにくい場合は Series.apply を使います。map と通常のSeriesの apply は、どちらも個々の値に対してPythonのロジックを呼び出します。スカラー関数を自動的にネイティブなベクトル化コードへ変換する機能ではありません。
平均を求める場合、ループや Series.apply を使って各スカラー値に np.mean を個別に呼び出す必要はありません。pay.mean() または numpy.mean(pay) のような集約演算で、Series全体から1つの結果を計算します。集約の出力は変換とは異なり、入力行ごとの値ではなく集約値を返します。
結果を確認し、ベンチマークは慎重に行う
書き換えた式を大きなデータに適用する前に、代表的な入力で単純な基準計算と比較します。この例では、map と apply の値がベクトル化した値と一致することを確認した後、式の結果を手作業で指定した参照Seriesと比較します。等価性の比較ではSeriesのインデックスも対象になるため、値の違いだけでなく、整列の違いも見つけやすくなります。
欠損値やdtypeによって挙動が変わることがあります。たとえば、算術演算では一般に欠損値が伝播しますが、独自関数では異なる扱いになる場合があります。整数、浮動小数点、nullable、objectの各列でも挙動が異なることがあります。方法を比較する前に入力のdtypeを確認し、欠損値をどう扱うかを決めてください。
性能が実際の用途で重要な場合に限り、ベンチマークを行います。同じ入力に対して同等の計算を比較し、必要に応じてウォームアップを行い、測定を複数回繰り返して、測定方法と実行環境を記録してください。時間はハードウェア、pandasとNumPyのバージョン、データ量、dtypeによって変わるため、小さな例だけでは一般的な高速化を示せません。