GroupByでのapply()
groupby().apply()に複数行を扱う関数を渡し、agg()では表現できない複雑なグループ単位の要約を計算します。
「GroupByでのapply()」はCoddyKit上の無料Pandas & NumPy Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPandas & NumPy Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Pandas & NumPy Academyコースには全4レッスンが含まれています。
GroupBy で apply() が必要な理由
組み込みの agg() メソッドは、sum、mean、count などの単純な集計を処理し、グループごとに 1 つのスカラー値を返します。しかし、グループ単位の計算の中には、単一の列だけでなく、グループ全体のサブ DataFrame を確認する必要があるものもあります。groupby().apply(func) はグループ全体の DataFrame を関数に渡し、その結果をまとめます。これにより、agg() では表現できない複雑な集計結果を作成できます。
import pandas as pd
import numpy as np
df = pd.DataFrame({
'region': ['North', 'North', 'South', 'South', 'North'],
'product': ['A', 'B', 'A', 'A', 'C'],
'revenue': [100, 250, 180, 90, 300]
})
print(df)グループごとにスカラー値を返す
groupby().apply() に渡した関数がスカラー値を返す場合、結果はグループキーをインデックスとする Series になります。これは agg() が生成する結果と同じです。この形式は、複数の列を使ったロジックが必要なスカラー値を計算するときに便利です。たとえば、グループ全体の売上に対する最上位商品の売上の比率は、単一の agg() の列指定では表現できません。
def top_product_share(group):
top = group['revenue'].max()
total = group['revenue'].sum()
return top / total
share = df.groupby('region').apply(top_product_share)
print(share)グループごとに Series を返す
関数が pd.Series を返す場合、結果は MultiIndex になります。外側のレベルがグループキー、内側のレベルが Series のインデックスです。これは、1 回の apply 呼び出しでグループごとに複数の統計量を計算するときに便利です。各グループについて複数行の指標を持つ集計表を作成できます。
def group_stats(group):
return pd.Series({
'total': group['revenue'].sum(),
'top_product': group.loc[group['revenue'].idxmax(), 'product'],
'n_products': group['product'].nunique()
})
result = df.groupby('region').apply(group_stats)
print(result)グループごとに DataFrame を返す
関数が pd.DataFrame を返す場合、groupby().apply() はすべてのサブ DataFrame を縦方向に連結します。これは最も強力な形式です。各グループ内の行をフィルタリングまたは変換し、変更したサブセットを返せます。たとえば、各地域で売上上位 2 件の商品だけを残すことができます。
def top2_per_region(group):
return group.nlargest(2, 'revenue')
top2 = df.groupby('region').apply(top2_per_region)
print(top2.reset_index(drop=True))グループ内の Z スコアを計算する
groupby().apply() の一般的な用途の 1 つは、グループ内の標準化です。すべての注文を基準に売上を正規化すると地域が混在してしまうため、各地域内で売上の Z スコアを計算します。North の Z スコアが 2 である場合、それは「North の平均より 2 標準偏差高い」という意味です。全体平均より 2 標準偏差高いという基準よりも、意味のある比較になります。
def within_group_zscore(group):
mean = group['revenue'].mean()
std = group['revenue'].std()
group = group.copy()
group['revenue_z'] = (group['revenue'] - mean) / std
return group
df_z = df.groupby('region').apply(within_group_zscore).reset_index(drop=True)
print(df_z)カスタム集計: Winsor 化平均
Winsor 化平均は、平均を計算する前に極端な値を上限値または下限値に置き換えるため、偏った分布では単純平均より頑健です。このカスタム集計は標準の agg() 関数では表現できませんが、groupby().apply() なら簡単に実装できます。各グループ内の 5 パーセンタイル未満と 95 パーセンタイル超の値を端点に置き換え、その値で平均を計算します。
def winsorised_mean(group, lower_pct=0.05, upper_pct=0.95):
col = group['revenue']
lo = col.quantile(lower_pct)
hi = col.quantile(upper_pct)
clipped = col.clip(lo, hi)
return clipped.mean()
wins_mean = df.groupby('region').apply(winsorised_mean)
print('Winsorised mean by region:')
print(wins_mean)グループごとのカスタム移動ウィンドウ
DataFrame 全体に移動ウィンドウを適用すると、グループの境界が無視されます。2 つの商品が交互に並ぶ時系列で 3 行の移動平均を計算すると、ウィンドウが商品間の境界を誤ってまたいでしまいます。groupby().apply() の内部で移動ウィンドウを適用すれば、各計算をグループ内に限定できます。たとえば、地域ごとの 3 か月移動売上平均を計算できます。
def group_rolling_mean(group, window=3):
group = group.sort_values('order_date').copy()
group['rolling_revenue'] = group['revenue'].rolling(window, min_periods=1).mean()
return group
# df_ts has order_date column
# df_rolled = df_ts.groupby('region').apply(group_rolling_mean).reset_index(drop=True)
print('Rolling window per group applied inside apply()')include_groups パラメータ(Pandas 2.2 以降)
Pandas 2.2 以降では、関数が受け取る DataFrame に groupby のキーが含まれていると、groupby().apply() は FutureWarning を発生させます。include_groups=False を渡すと、関数に渡されるサブ DataFrame から groupby の列を除外できます。これにより警告を避けられるだけでなく、関数本体でキー列を誤って操作することも防げます。
def revenue_only(group):
# group does not include 'region' column when include_groups=False
return group['revenue'].sum()
# result = df.groupby('region').apply(revenue_only, include_groups=False)
print('include_groups=False avoids FutureWarning in Pandas 2.2+')apply() の結果と reset_index の組み合わせ
groupby().apply() がグループごとに DataFrame を返す場合、結果はグループキーを外側のレベルに含む MultiIndex になります。reset_index(drop=True) を使うと、インデックスを通常の整数範囲に戻せます。または、reset_index(level=0) を使ってグループキーを列に移し、出力内で明示することもできます。
result = df.groupby('region').apply(top2_per_region)
print('With MultiIndex:')
print(result.head())
print('\nAfter reset_index:')
print(result.reset_index(drop=True))apply() より transform() を優先する場面
groupby().apply() は、入力とは異なる形状の結果を返す複雑なグループ単位の計算に使います。一方、groupby().transform() は、元のインデックスに対応する新しい列を追加する計算に使います。たとえば、正規化のためにグループ平均を各行に展開する場合です。結果を元の DataFrame と同じ形状にしたいときは transform、結果の形状が異なるときは apply を使います。
# transform: adds group mean back to each row
df['group_mean_revenue'] = df.groupby('region')['revenue'].transform('mean')
# apply: computes one scalar per group
group_totals = df.groupby('region')['revenue'].apply(sum)
print(df[['region', 'revenue', 'group_mean_revenue']])パフォーマンスのヒント: 単純な集計では apply() を避ける
単純な操作では、groupby().apply() は groupby().agg() より大幅に遅くなります。これは apply() がグループごとに完全な Python オブジェクトを作成するためです。合計、平均、件数の計算には必ず agg() を使ってください。apply() は、グループ全体の DataFrame が本当に必要な場合、つまり複数列を使った条件ロジック、カスタム統計量、グループ内のフィルタリングなどに限定して使います。
# SLOW: apply for simple sum
slow = df.groupby('region').apply(lambda g: g['revenue'].sum())
# FAST: native agg
fast = df.groupby('region')['revenue'].sum()
print('Both produce the same result:')
print(fast.equals(slow))理解度チェック
このレッスンで学んだデータ分析の概念について理解度を確認しましょう。
レッスンのまとめ
このレッスンでは、groupby().apply() からスカラー値、Series、DataFrame を返す方法、グループ内の Z スコアとカスタムな頑健統計量を計算する方法、そしてグループ単位の操作で apply()、agg()、transform() を使い分ける方法を学びました。次は、Series と DataFrame の要素単位の操作で map() と applymap() を使う方法を見ていきます。
AI チューターと学ぶ Python — 無料
ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。
- コース
- 30
- レッスン
- 120
よくある質問
「GroupByでのapply()」レッスンは無料ですか?
はい。「GroupByでのapply()」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Pandas & NumPy Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Pandas & NumPy Academyコースには全4レッスンが含まれています。
「GroupByでのapply()」で何を学びますか?
groupby().apply()に複数行を扱う関数を渡し、agg()では表現できない複雑なグループ単位の要約を計算します。 ブラウザで直接実行するハンズオンコードでPandas & NumPy Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Pandas & NumPy Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのPandas & NumPy Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「GroupByでのapply()」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このPandas & NumPy Academyレッスンでコードを書いて実行できますか?
はい。すべてのPandas & NumPy Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- 列と行へのapply()
- GroupByでのapply()
- 要素単位の処理に使うmap()とapplymap()
- pipe()によるメソッドチェーン