Pandasのtransformメソッドは、データフレームの集計処理において非常に強力なツールです。
特に、集計した結果を元のデータのインデックスを保持したまま結合したい場合に役立ちます。
通常の集計処理であるgroupbyとagg(aggregate)を組み合わせた場合、結果はグループごとにまとめられ、元のデータフレームよりも行数が少なくなります。
しかし、transformメソッドを使用すると、元のデータフレームと同じ形状(サイズ)を維持したまま、集計結果を反映させることが可能です。
この記事では、Pandasのtransformメソッドの基本的な使い方から、実務で役立つ応用テクニックまで詳しく解説します。
Pandasのtransformメソッドとは
transformメソッドは、データフレームまたはシリーズの各要素に対して関数を適用し、入力と同じ形状のオブジェクトを返すメソッドです。
一般的な集計処理では、データが「要約」されるため行数が減りますが、transformは行数を変えません。
この性質により、元のデータに対して計算結果を列として追加する作業が極めてスムーズになります。
transformとapply・aggの違い
Pandasにはデータ操作のためのメソッドが複数存在しますが、transformには明確な特徴があります。
aggメソッドは複数の値を一つの要約統計量(平均値や合計値など)にまとめ、グループの数だけ行を返します。
一方で、transformメソッドは、計算された要約統計量を元のインデックスに合わせて「ブロードキャスト(拡張)」して配置します。
また、applyメソッドは柔軟性が高い反面、transformに比べて処理速度が遅くなる傾向があります。
transformは特定の列に対してベクトル化された操作を行うため、大規模なデータセットに対してより効率的に動作します。
transformメソッドの基本的な使い方
まずは、最もシンプルな形でのtransformメソッドの使い方を確認しましょう。
数値データを持つシリーズに対して、一律で値を変換する例を紹介します。
import pandas as pd
import numpy as np
# サンプルデータの作成
df = pd.DataFrame({
'value': [10, 20, 30, 40, 50]
})
# 各要素に10を足す単純な変換
df['plus_10'] = df['value'].transform(lambda x: x + 10)
print(df)
value plus_10
0 10 20
1 20 30
2 30 40
3 40 50
4 50 60
このように、単一の列に対して関数を適用する場合、mapやapplyと似た挙動を示します。
しかし、transformの本領が発揮されるのは、「グループ化されたデータ」に対して処理を行う場合です。
GroupByとtransformの組み合わせ
実務でtransformが最も多用される場面は、groupbyと組み合わせた時です。
特定のカテゴリごとに集計を行い、その結果を元の各行に紐付けたい場合に非常に便利です。
グループ内の平均値を算出する例
例えば、店舗ごとの売上データがあり、各商品の売上が「その店舗の平均売上と比べてどれくらいか」を知りたいとします。
通常のgroupbyとmergeを使う方法でも可能ですが、transformを使えば一行で記述できます。
df = pd.DataFrame({
'store': ['A', 'A', 'A', 'B', 'B', 'C', 'C'],
'sales': [100, 200, 150, 300, 400, 100, 50]
})
# 店舗ごとの平均売上を計算し、元のデータと同じ形状で返す
df['store_mean'] = df.groupby('store')['sales'].transform('mean')
print(df)
store sales store_mean
0 A 100 150.0
1 A 200 150.0
2 A 150 150.0
3 B 300 350.0
4 B 400 350.0
5 C 100 75.0
6 C 50 75.0
結果を見ると、店舗「A」のすべての行に、店舗Aの平均値である「150.0」が代入されていることがわかります。
この機能により、元データとの結合(JOIN)処理を省略できるため、コードが非常にシンプルになります。
実用テクニック1:データの標準化とスケーリング
機械学習のデータ前処理において、グループごとの標準化(Z-score正規化)が必要になることがあります。
例えば、試験の結果をクラスごとに偏差値化する場合などに役立ちます。
transformを使用すれば、グループ内の平均と標準偏差を用いて簡単に計算できます。
df = pd.DataFrame({
'class': ['X', 'X', 'X', 'Y', 'Y', 'Y'],
'score': [80, 90, 70, 50, 60, 40]
})
# クラスごとの標準化関数
def standardize(x):
return (x - x.mean()) / x.std()
# グループ内で標準化を適用
df['standardized_score'] = df.groupby('class')['score'].transform(standardize)
print(df)
class score standardized_score
0 X 80 0.0
1 X 90 1.0
2 X 70 -1.0
3 Y 50 0.0
4 Y 60 1.0
5 Y 40 -1.0
このように、独自の関数を定義してtransformに渡すことで、高度なグループ内計算が可能になります。
標準偏差や平均といった複数の統計量が必要な計算でも、元のインデックスが維持されるため、複雑な計算をシンプルに記述できます。
実用テクニック2:欠損値(NaN)をグループ平均で補完する
データセットに欠損値が含まれている場合、単純に全体の平均値で埋めるとデータの歪みが生じることがあります。
例えば、「商品のカテゴリごとに平均価格で欠損値を埋める」といった処理が望ましい場合、transformが威力を発揮します。
df = pd.DataFrame({
'category': ['Fruit', 'Fruit', 'Fruit', 'Veg', 'Veg', 'Veg'],
'price': [100, np.nan, 120, 200, 250, np.nan]
})
# カテゴリごとの平均値を計算
group_mean = df.groupby('category')['price'].transform('mean')
# 欠損値をグループ平均で埋める
df['price_filled'] = df['price'].fillna(group_mean)
print(df)
category price price_filled
0 Fruit 100.0 100.0
1 Fruit NaN 110.0
2 Fruit 120.0 120.0
3 Veg 200.0 200.0
4 Veg 250.0 250.0
5 Veg NaN 225.0
カテゴリ「Fruit」の欠損値にはFruitの平均(110)が、カテゴリ「Veg」にはVegの平均(225)が正しく補完されています。
このテクニックは、データクレンジングにおいて頻繁に使用される重要なパターンの一つです。
実用テクニック3:フィルタリング条件としての利用
transformの結果を、データフレームのフィルタリング(行の抽出)に利用することもできます。
例えば、「合計売上が一定額以上のグループに属するデータのみを抽出したい」といったケースです。
df = pd.DataFrame({
'customer_id': [1, 1, 2, 3, 3, 3],
'purchase_amount': [500, 600, 200, 100, 100, 100]
})
# 顧客ごとの合計購入額を算出
total_purchase = df.groupby('customer_id')['purchase_amount'].transform('sum')
# 合計購入額が1000円以上の顧客のデータのみを抽出
high_value_customers = df[total_purchase >= 1000]
print(high_value_customers)
customer_id purchase_amount
0 1 500
1 1 600
この手法を用いると、「グループ単位の条件」に基づいて「個別の行」をフィルタリングすることが非常に容易になります。
サブクエリのような複雑な論理を、直感的なPandasの記述で実現できる点が大きなメリットです。
transformのパフォーマンスと注意点
transformメソッドは非常に強力ですが、使用する際に意識しておくべきポイントがいくつかあります。
文字列による組み込み関数の指定
Pandasのtransformでは、'mean', 'sum', 'count', 'max'などの組み込み関数を文字列で指定できます。
自分で定義したlambda関数を渡すよりも、これらの文字列指定を利用する方が高速に動作します。
これは、Pandasが内部的に最適化した処理(Cythonなどによる実装)を呼び出すことができるためです。
複数の列に対する操作
データフレーム全体に対してtransformを適用すると、すべての列が個別に変換されます。
特定の列にのみ適用したい場合は、あらかじめ列を選択してから呼び出すようにしましょう。
# 良い例:必要な列だけを指定してtransform
df['result'] = df.groupby('group')['target_col'].transform('mean')
# 非効率な例:全体に適用してから列を取り出す
# df.groupby('group').transform('mean')['target_col']
データ量が多い場合、不必要な計算を避けることでメモリ消費量と実行時間を抑えることができます。
transformとapplyの使い分け
読者の中には、「applyでも同じことができるのではないか」と考える方もいるかもしれません。
確かにapplyは汎用的ですが、transformには「結果の形状を保証する」という強い制約があります。
この制約があるからこそ、Pandasは内部でより高度な最適化を行うことができ、結果として処理が高速になります。
「入力の行数と出力の行数が同じであるべき処理」には、常にtransformを優先して検討してください。
逆に、グループごとに計算結果がスカラ値(単一の値)になる場合や、全く異なる形状のデータを生成したい場合は、aggやapplyが適しています。
実践:売上データの比率計算
最後に、より実戦に近い分析の例を見てみましょう。
各製品の売上が、そのカテゴリ全体の売上に対して何パーセントを占めているかを計算します。
df = pd.DataFrame({
'category': ['Electronics', 'Electronics', 'Clothing', 'Clothing', 'Clothing'],
'product': ['Phone', 'Laptop', 'Shirt', 'Pants', 'Shoes'],
'sales': [80000, 120000, 5000, 7000, 8000]
})
# カテゴリごとの合計売上を算出
category_total = df.groupby('category')['sales'].transform('sum')
# 各製品の売上比率を計算(%)
df['share_within_category'] = (df['sales'] / category_total) * 100
print(df)
category product sales share_within_category
0 Electronics Phone 80000 40.0
1 Electronics Laptop 120000 60.0
2 Clothing Shirt 5000 25.0
3 Clothing Pants 7000 35.0
4 Clothing Shoes 8000 40.0
このように、transformを使って集計値を横に並べることで、通常の四則演算だけで高度な比率分析が行えます。
この手法は、ABC分析やマーケットシェア分析を行う際にも非常に重宝します。
まとめ
Pandasのtransformメソッドは、データ分析の工程を劇的に効率化する便利な機能です。
「集計値を元のデータと同じ行数で展開する」という特徴を理解するだけで、データ加工の幅が大きく広がります。
主な活用メリットを以下に整理します。
- 元のインデックスを維持するため、JOIN処理の手間が省ける。
- グループ単位の平均値や合計値を用いた計算(標準化や比率算出)が簡潔になる。
- 欠損値のグループ補完において、精度の高いデータクレンジングを実現できる。
- 組み込み関数の利用により、applyよりも高速な処理が期待できる。
日常的なデータ分析業務の中で、groupbyの後にagg(要約)するだけでなく、transform(変換)を使い分けられるようになると、Pandasのマスターに一歩近づきます。
今回紹介したテクニックを活用して、より洗練されたデータ処理コードを作成してみてください。
