pandasのgroupby()で使える便利メソッド7選

pandasのgroupby()で使える便利メソッド7選

データ分析で「カテゴリごとに集計したい」という場面は非常に多いです。

「商品別の売上合計」「月別の平均値」「店舗ごとの顧客数」など、グループ化して集計する処理は日常茶飯事です。

pandasの groupby() は、そんなグループ集計を簡単に実現する強力な機能です。

今回は、groupby()の後に使える7つの便利メソッドを紹介します。

groupby()とは?

groupby() は、データを特定の列の値でグループ分けするためのメソッドです。

SQLを知っている方なら「GROUP BY句」と同じ概念だと思ってください。

たとえば、売上データを「商品カテゴリ」でグループ分けすると、同じカテゴリの行がまとまり、カテゴリごとに集計できるようになります。

今回紹介する7つのメソッドは以下の通りです。

メソッド 機能 使いどころ
sum() / mean() / max() / min() 基本的な集計 合計、平均などを求める
agg() 複数の集計を一度に 合計と平均を同時に計算
transform() 元の形状を保つ変換 グループ平均との差を計算
apply() 自由なカスタム処理 複雑な集計ロジック
filter() グループ単位の抽出 条件を満たすグループだけ取得
size() / count() 行数・非欠損数を数える グループごとの件数を確認
first() / last() / nth() 特定の行を取得 各グループの代表値を取得

 

サンプルデータの準備

まず、今回のサンプルデータを作成します。ECサイトの購買データをイメージしています。

import pandas as pd

# サンプルデータを作成
data = {
    'カテゴリ': ['家電', '家電', '家電', '食品', '食品', '食品', '食品', 
                '衣類', '衣類', '衣類', '家電', '食品', '衣類', '家電', '衣類'],
    '商品名': ['テレビ', 'パソコン', '冷蔵庫', 'お菓子', '飲料', 'お菓子', '冷凍食品',
              'Tシャツ', 'ジーンズ', 'Tシャツ', 'パソコン', '飲料', 'ジーンズ', 'テレビ', 'Tシャツ'],
    '売上': [50000, 80000, 60000, 500, 300, 800, 1200, 
            2000, 5000, 1500, 95000, 450, 4500, 55000, 1800],
    '個数': [1, 1, 1, 10, 5, 15, 3, 
            3, 1, 2, 1, 8, 1, 1, 4],
    '地域': ['東京', '大阪', '東京', '東京', '大阪', '東京', '大阪',
            '東京', '大阪', '東京', '大阪', '東京', '大阪', '東京', '大阪']
}

# データフレームを作成
df = pd.DataFrame(data)

print(df)

 

以下、実行結果です。

   カテゴリ   商品名     売上  個数  地域
0    家電   テレビ  50000   1  東京
1    家電  パソコン  80000   1  大阪
2    家電   冷蔵庫  60000   1  東京
3    食品   お菓子    500  10  東京
4    食品    飲料    300   5  大阪
5    食品   お菓子    800  15  東京
6    食品  冷凍食品   1200   3  大阪
7    衣類  Tシャツ   2000   3  東京
8    衣類  ジーンズ   5000   1  大阪
9    衣類  Tシャツ   1500   2  東京
10   家電  パソコン  95000   1  大阪
11   食品    飲料    450   8  東京
12   衣類  ジーンズ   4500   1  大阪
13   家電   テレビ  55000   1  東京
14   衣類  Tシャツ   1800   4  大阪

 

このデータを使って、これからgroupby()の様々なメソッドを紹介します。

「カテゴリ」列には「家電」「食品」「衣類」の3種類があり、これをキーにしてグループ化していきます。

 

基本の集計:sum(), mean(), max(), min()

 groupby()の基本的な使い方

まずは最も基本的な使い方から始めましょう。

groupby()の後に集計メソッドをつなげると、グループごとの集計結果が得られます。

以下のコードでは、カテゴリごとの売上合計を計算します。

# カテゴリごとの売上合計を計算
sales_by_category = df.groupby('カテゴリ')['売上'].sum()

print(sales_by_category)

 

以下、実行結果です。

カテゴリ
家電    340000
衣類     14800
食品      3250
Name: 売上, dtype: int64

 

家電カテゴリの売上が圧倒的に大きいことがわかります。

売上レポートや経営分析で「どのカテゴリが稼いでいるか」を把握する際に、このような集計が役立ちます。

 

 よく使う集計メソッド

sum()以外にも、様々な集計メソッドがあります。平均値、最大値、最小値を一度に確認してみましょう。

# 平均値
print("【平均売上】")
print(df.groupby('カテゴリ')['売上'].mean())
print()

# 最大値
print("【最大売上】")
print(df.groupby('カテゴリ')['売上'].max())

 

以下、実行結果です。

【平均売上】
カテゴリ
家電    68000.0
衣類     2960.0
食品      650.0
Name: 売上, dtype: float64

【最大売上】
カテゴリ
家電    95000
衣類     5000
食品     1200
Name: 売上, dtype: int64

 

カテゴリごとの平均売上と最大売上が表示されます。

平均値を見ると、家電は1件あたりの単価が高く、食品は低いことがわかります。

 

 複数列の集計

特定の列だけでなく、複数の列を一度に集計することもできます。

# 売上と個数の両方を合計
totals = df.groupby('カテゴリ')[['売上', '個数']].sum()

print(totals)

 

以下、実行結果です。

          売上  個数
カテゴリ            
家電    340000   5
衣類     14800  11
食品      3250  41

 

売上と個数の両方がカテゴリごとに集計されます。

これにより「売上は高いが個数は少ない」といった傾向を把握できます。

 

agg():複数の集計を一度に実行

 agg()とは?

agg() は「aggregate(集約する)」の略で、複数の集計関数を一度に適用できるメソッドです。

「合計と平均を同時に知りたい」「最大値と最小値の両方を見たい」といった場面で活躍します。

 

 基本的な使い方

まず、売上に対して複数の集計を同時に行ってみましょう。

# 売上に対して複数の集計を適用
result = df.groupby('カテゴリ')['売上'].agg(['sum', 'mean', 'max', 'min'])

print(result)

 

以下、実行結果です。

         sum     mean    max    min
カテゴリ                               
家電    340000  68000.0  95000  50000
衣類     14800   2960.0   5000   1500
食品      3250    650.0   1200    300

 

各カテゴリの売上の全体像を一度に把握できます。

「家電は平均68,000円で高単価」「食品は最大でも1,200円」といった特徴がすぐにわかります。

 

 列ごとに異なる集計を適用

agg()の真価は、列によって異なる集計を適用できることにあります。

たとえば「売上は合計と平均」「個数は合計だけ」といった指定ができます。

# 列ごとに異なる集計を指定(辞書形式)
result = df.groupby('カテゴリ').agg({
    '売上': ['sum', 'mean'],  # 売上は合計と平均
    '個数': ['sum']           # 個数は合計のみ
})

print(result)

 

以下、実行結果です。

          売上           個数
         sum     mean sum
カテゴリ                     
家電    340000  68000.0   5
衣類     14800   2960.0  11
食品      3250    650.0  41

 

実行すると、売上には2つの集計、個数には1つの集計が適用された結果が表示されます。

レポート作成時に「この項目にはこの集計」と細かく指定したい場合に便利です。

 

 集計結果に名前をつける

集計結果の列名をわかりやすい名前に変更することもできます。

# 名前付きの集計(Named Aggregation)
result = df.groupby('カテゴリ').agg(
    売上合計=('売上', 'sum'),
    売上平均=('売上', 'mean'),
    販売個数=('個数', 'sum')
)

print(result)

 

以下、実行結果です。

        売上合計     売上平均  販売個数
カテゴリ                       
家電    340000  68000.0     5
衣類     14800   2960.0    11
食品      3250    650.0    41

 

「売上合計」「売上平均」「販売個数」という日本語の列名がついた結果が得られます。

そのままレポートに使えるような、見やすい形式になります。

 

transform():元の形状を保つ変換

 transform()とは?

transform() は、グループごとの集計結果を「元のDataFrameと同じ行数」で返すメソッドです。

sum()mean()は集計してグループ数まで行が減りますが、transform()は元の行数を維持します。

これが役立つのは、「各行にグループの平均値を追加したい」「グループ内での偏差を計算したい」といった場面です。

 

 基本的な使い方

まず、各行にカテゴリの平均売上を追加してみましょう。

# 各行にカテゴリ平均を追加
df['カテゴリ平均'] = df.groupby('カテゴリ')['売上'].transform('mean')

print(df[['カテゴリ', '商品名', '売上', 'カテゴリ平均']].head(8))

 

以下、実行結果です。

  カテゴリ   商品名     売上   カテゴリ平均
0   家電   テレビ  50000  68000.0
1   家電  パソコン  80000  68000.0
2   家電   冷蔵庫  60000  68000.0
3   食品   お菓子    500    650.0
4   食品    飲料    300    650.0
5   食品   お菓子    800    650.0
6   食品  冷凍食品   1200    650.0
7   衣類  Tシャツ   2000   2960.0

 

元のデータに「カテゴリ平均」列が追加されます。

これにより、「この商品は平均より上か下か」を各行で判断できるようになります。

 

 平均との差を計算する

transform()の典型的な活用例として、グループ平均との差を計算してみましょう。

これは「このデータはグループの中でどの程度特異か」を把握するのに役立ちます。

# 各売上がカテゴリ平均からどれだけ離れているかを計算
df['カテゴリ平均との差'] = (
    df['売上'] - 
    df.groupby('カテゴリ')['売上'].transform('mean')
)

print(
    df[[
        'カテゴリ', 
        '商品名', 
        '売上', 
        'カテゴリ平均', 
        'カテゴリ平均との差'
    ]].head(8)
)

 

以下、実行結果です。

  カテゴリ   商品名     売上   カテゴリ平均  カテゴリ平均との差
0   家電   テレビ  50000  68000.0   -18000.0
1   家電  パソコン  80000  68000.0    12000.0
2   家電   冷蔵庫  60000  68000.0    -8000.0
3   食品   お菓子    500    650.0     -150.0
4   食品    飲料    300    650.0     -350.0
5   食品   お菓子    800    650.0      150.0
6   食品  冷凍食品   1200    650.0      550.0
7   衣類  Tシャツ   2000   2960.0     -960.0

 

「カテゴリ平均との差」列が追加されます。

正の値なら平均より高く、負の値なら平均より低いことを示します。

たとえばパソコンは家電平均より12,000円高く、テレビは18,000円低いことがわかります。

これは営業分析や異常値検出に使えます。

 

 グループ内での順位をつける

transform()ではrank()を使ってグループ内順位をつけることもできます。

# 商品ごとに売上を合計
product_sales = df.groupby(
    ['カテゴリ', '商品名'], 
    as_index=False)['売上'].sum()

# カラム名を変更
product_sales.rename(
    columns={'売上': '商品売上合計'}, 
    inplace=True
)

# 商品ごとの売上合計を元にカテゴリ内での売上順位をつける
product_sales['商品カテゴリ内順位'] = (
    product_sales.groupby('カテゴリ')['商品売上合計'].
    rank(ascending=False)
)

print(product_sales.sort_values(
    ['カテゴリ', '商品カテゴリ内順位']
))

 

以下、実行結果です。

  カテゴリ   商品名  商品売上合計  商品カテゴリ内順位
1   家電  パソコン  175000        1.0
0   家電   テレビ  105000        2.0
2   家電   冷蔵庫   60000        3.0
4   衣類  ジーンズ    9500        1.0
3   衣類  Tシャツ    5300        2.0
5   食品   お菓子    1300        1.0
6   食品  冷凍食品    1200        2.0
7   食品    飲料     750        3.0

 

各商品がカテゴリ内で何位かがわかります。

「家電カテゴリで1位はパソコン」「食品カテゴリで1位はお菓子」といった情報が得られます。

商品ランキングや成績評価に活用できます。

 

apply():自由なカスタム処理

 apply()とは?

apply() は、グループごとに「自分で定義した関数」を適用できるメソッドです。

agg()transform()では表現できない複雑な処理も、apply()なら実現できます。

 

 基本的な使い方

まず、簡単な例から始めましょう。各グループの先頭3行だけを取得する関数を適用します。

# 各カテゴリの先頭2行だけを取得
top2 = product_sales.groupby('カテゴリ').apply(lambda x: x.head(2))

print(top2)

 

以下、実行結果です。

       カテゴリ   商品名  商品売上合計  商品カテゴリ内順位
カテゴリ                                
家電   0   家電   テレビ  105000        2.0
     1   家電  パソコン  175000        1.0
衣類   3   衣類  Tシャツ    5300        2.0
     4   衣類  ジーンズ    9500        1.0
食品   5   食品   お菓子    1300        1.0
     6   食品  冷凍食品    1200        2.0

 

lambda x: x.head(2) は「受け取ったデータの先頭2行を返す」という無名関数です。

apply()の中では、各グループがDataFrameとして渡されるので、DataFrame操作が自由に行えます。

 

 売上上位の商品だけを抽出

もう少し実践的な例として、関数を定義し、その関数を使い各カテゴリの売上上位2商品だけを取得してみましょう。

# 各カテゴリの売上トップ2を取得する関数
def get_top2(group):
    return group.nlargest(2, '商品売上合計')

# 各カテゴリの売上トップ2を取得
top2_sales = product_sales.groupby('カテゴリ').apply(get_top2)

print(top2_sales[['カテゴリ', '商品名', '商品売上合計']])

 

以下、実行結果です。

       カテゴリ   商品名  商品売上合計
カテゴリ                     
家電   1   家電  パソコン  175000
     0   家電   テレビ  105000
衣類   4   衣類  ジーンズ    9500
     3   衣類  Tシャツ    5300
食品   5   食品   お菓子    1300
     6   食品  冷凍食品    1200

 

nlargest()は「指定した列の値が大きい順にn行取得する」メソッドです。

これは「各店舗のベストセラー商品」「各月のトップ顧客」を抽出する際に使えます。

 

 カスタム集計を行う

複数の処理を組み合わせた複雑な集計も、apply()なら簡単に書けます。

# 各カテゴリの要約統計を計算する関数
def summarize(group):
    return pd.Series({
        '件数': len(group),
        '売上合計': group['売上'].sum(),
        '平均単価': group['売上'].mean(),
        '最高額商品': group.loc[group['売上'].idxmax(), '商品名']
    })

summary = df.groupby('カテゴリ').apply(summarize)

print(summary)

 

以下、実行結果です。

      件数    売上合計     平均単価 最高額商品
カテゴリ                           
家電     5  340000  68000.0  パソコン
衣類     5   14800   2960.0  ジーンズ
食品     5    3250    650.0  冷凍食品

 

件数・売上合計・平均単価に加えて、最高額商品の名前まで含む要約表が得られます。

「家電の最高額商品はパソコン」「食品の最高額商品は冷凍食品」といった情報も一度に確認できます。

このようなカスタム要約は、定型レポートの作成に便利です。

 

filter():条件に合うグループだけを抽出

 filter()とは?

filter() は、条件を満たすグループ全体を抽出するメソッドです。

「売上が一定以上のカテゴリだけ」「データ件数が多いカテゴリだけ」といったフィルタリングができます。

通常のDataFrameのフィルタリング(行単位)とは異なり、グループ単位でフィルタリングする点が特徴です。

 

 基本的な使い方

売上合計が1万円以上のカテゴリだけを抽出してみましょう。

# 売上合計が10000円以上のカテゴリだけを抽出
high_sales = df.groupby('カテゴリ').filter(
    lambda x: x['売上'].sum() >= 10000
)

print(high_sales)

 

以下、実行結果です。

   カテゴリ   商品名     売上  個数  地域   カテゴリ平均  カテゴリ平均との差
0    家電   テレビ  50000   1  東京  68000.0   -18000.0
1    家電  パソコン  80000   1  大阪  68000.0    12000.0
2    家電   冷蔵庫  60000   1  東京  68000.0    -8000.0
7    衣類  Tシャツ   2000   3  東京   2960.0     -960.0
8    衣類  ジーンズ   5000   1  大阪   2960.0     2040.0
9    衣類  Tシャツ   1500   2  東京   2960.0    -1460.0
10   家電  パソコン  95000   1  大阪  68000.0    27000.0
12   衣類  ジーンズ   4500   1  大阪   2960.0     1540.0
13   家電   テレビ  55000   1  東京  68000.0   -13000.0
14   衣類  Tシャツ   1800   4  大阪   2960.0    -1160.0

 

実行すると、「家電」と「衣類」カテゴリのデータだけが残り、「食品」カテゴリ(合計3,250円)は除外されます。

 

 件数によるフィルタリング

データ件数が少ないグループを除外することもできます。

統計的に意味のある分析をするために、サンプル数が少ないグループを除外したい場合に使います。

# 3件以上のデータがあるカテゴリだけを抽出
enough_data = df.groupby('カテゴリ').filter(
    lambda x: len(x) >= 3
)

print(f"元のデータ: {len(df)}行")
print(f"フィルタ後: {len(enough_data)}行")
print()
print(enough_data['カテゴリ'].value_counts())

 

以下、実行結果です。

元のデータ: 15行
フィルタ後: 15行

カテゴリ
家電    5
食品    5
衣類    5
Name: count, dtype: int64

 

実行すると、各カテゴリのデータ件数が表示されます。

3件以上のデータがあるカテゴリだけが残ります。というか、今回はすべて残っています。

機械学習でカテゴリ別にモデルを作る際、サンプル数が少なすぎるカテゴリを除外する前処理に使えます。

 

 複数条件でのフィルタリング

複数の条件を組み合わせることもできます。

# 売上合計5000円以上 かつ 平均売上1000円以上のカテゴリ
filtered = df.groupby('カテゴリ').filter(
    lambda x: 
        (x['売上'].sum() >= 5000) and 
        (x['売上'].mean() >= 1000)
)
print(filtered['カテゴリ'].unique())

 

以下、実行結果です。

['家電' '衣類']

 

実行すると、両方の条件を満たすカテゴリ(家電、衣類)だけが抽出されます。

複数の基準で「分析に値するグループ」を選別する際に便利です。

 

size()とcount():件数を数える

 size()とcount()の違い

グループごとの件数を数えるメソッドとして、size()count() があります。

この2つは似ていますが、重要な違いがあります。

メソッド カウント対象 結果の形式
size() 全行数 Series
count() 欠損値を除いた行数 DataFrame

 

 size()の使い方

まず、size()でカテゴリごとの件数を確認しましょう。

# カテゴリごとの件数を取得
category_size = df.groupby('カテゴリ').size()
print(category_size)

 

以下、実行結果です。

カテゴリ
家電    5
衣類    5
食品    5
dtype: int64

 

各カテゴリが5件ずつあることがわかります。

size()は欠損値も含めてカウントするため、「実際のデータ件数」を知りたいときに使います。

 

 count()の使い方

count()は列ごとに「欠損値を除いた件数」を返します。欠損値があるデータで違いを確認しましょう。

# 欠損値を含むデータを作成
df_with_na = df.copy()
df_with_na.loc[0, '売上'] = None  # 欠損値に
df_with_na.loc[5, '売上'] = None  # 欠損値に

# size()とcount()の比較
print("【size() : 全行数】")
print(df_with_na.groupby('カテゴリ').size())
print()
print("【count() : 欠損値を除いた件数】")
print(df_with_na.groupby('カテゴリ')['売上'].count())

 

以下、実行結果です。

【size() : 全行数】
カテゴリ
家電    5
衣類    5
食品    5
dtype: int64

【count() : 欠損値を除いた件数】
カテゴリ
家電    4
衣類    5
食品    4
Name: 売上, dtype: int64

 

実行すると、size()は全行数を、count()は欠損値を除いた件数を返すことがわかります。

データの品質チェック(欠損値がどれだけあるか)を行う際に、この違いが重要になります。

 

 複数キーでのグループ化

2つ以上の列でグループ化することもできます。

# カテゴリと地域の組み合わせで件数を集計
cross_count = df.groupby(['カテゴリ', '地域']).size()
print(cross_count)

 

以下、実行結果です。

カテゴリ  地域
家電    大阪    2
      東京    3
衣類    大阪    3
      東京    2
食品    大阪    2
      東京    3
dtype: int64

 

実行すると、「家電×東京」「家電×大阪」のような組み合わせごとの件数が表示されます。

これをunstack()で表形式に変換すると、クロス集計表になります。

# クロス集計表に変換
cross_table = df.groupby(['カテゴリ', '地域'
    ]).size().unstack(fill_value=0)

print(cross_table)

 

以下、実行結果です。

地域    大阪  東京
カテゴリ        
家電     2   3
衣類     3   2
食品     2   3

 

実行すると、行がカテゴリ、列が地域の表が得られます。

「どのカテゴリがどの地域で売れているか」を一目で把握できます。

マーケティング分析でよく使われる形式です。

 

first(), last(), nth():特定の行を取得

 各グループの代表行を取得

first(), last(), nth() は、各グループから特定の行を取得するメソッドです。

「各カテゴリの最初の注文」「各顧客の最新の購入」などを取得する際に使います。

 

 first()とlast()の使い方

まず、各カテゴリの最初と最後の行を取得してみましょう。

# 各カテゴリの最初の行
print("【各カテゴリの最初の行】")
print(df.groupby('カテゴリ').first())

 

以下、実行結果です。

【各カテゴリの最初の行】
       商品名     売上  個数  地域   カテゴリ平均  カテゴリ平均との差
カテゴリ                                         
家電     テレビ  50000   1  東京  68000.0   -18000.0
衣類    Tシャツ   2000   3  東京   2960.0     -960.0
食品     お菓子    500  10  東京    650.0     -150.0

 

実行すると、各カテゴリで最初に現れる行のデータが表示されます。これは「各カテゴリの代表例」を取得したい場合に便利です。

# 各カテゴリの最後の行
print("【各カテゴリの最後の行】")
print(df.groupby('カテゴリ').last())

 

以下、実行結果です。

【各カテゴリの最後の行】
       商品名     売上  個数  地域   カテゴリ平均  カテゴリ平均との差
カテゴリ                                         
家電     テレビ  55000   1  東京  68000.0   -13000.0
衣類    Tシャツ   1800   4  大阪   2960.0    -1160.0
食品      飲料    450   8  東京    650.0     -200.0

 

実行すると、各カテゴリで最後に現れる行のデータが表示されます。時系列データで「各顧客の最新の行動」を取得する際に使えます。

 

 nth()の使い方

nth()を使うと、各グループのn番目の行を取得できます。0から数えることに注意してください。

# 各カテゴリの2番目(インデックス1)の行を取得
print("【各カテゴリの2番目の行】")
print(df.groupby('カテゴリ').nth(1))

 

以下、実行結果です。

【各カテゴリの2番目の行】
  カテゴリ   商品名     売上  個数  地域   カテゴリ平均  カテゴリ平均との差
1   家電  パソコン  80000   1  大阪  68000.0    12000.0
4   食品    飲料    300   5  大阪    650.0     -350.0
8   衣類  ジーンズ   5000   1  大阪   2960.0     2040.0

 

実行すると、各カテゴリの2番目の行が取得されます。「最初と最後以外の行」や「特定の位置の行」が必要な場合に使います。

 

 売上順に並べてから取得

first()last()をさらに活用するために、事前にソートしておくことが、よくあります。

# 売上順にソートしてからグループ化
df_sorted = df.sort_values('売上', ascending=False)

# 各カテゴリの売上1位を取得
top_products = df_sorted.groupby('カテゴリ').first()

print("【各カテゴリの売上1位】")
print(top_products[['商品名', '売上']])

 

以下、実行結果です。

【各カテゴリの売上1位】
       商品名     売上
カテゴリ             
家電    パソコン  95000
衣類    ジーンズ   5000
食品    冷凍食品   1200

 

実行すると、各カテゴリで最も売上が高い商品がわかります。

「家電1位はパソコン(95,000円)」「食品1位は冷凍食品(1,200円)」といった情報が得られます。

これは先ほどapply()で行った処理を、よりシンプルに書く方法です。

 

まとめ

最後に、7つのメソッドの使い分けを整理しておきます。

メソッド 主な用途 使う場面の例
sum() / mean() / max() / min() 基本的な集計 合計・平均・最大・最小など単純な集計を行いたい
agg() 複数集計を一度に実行 合計と平均など、複数の集計結果を同時に取得したい
transform() 元の形状を保持した集計 グループ平均などを各行に追加したい
apply() 自由度の高い処理 複雑なロジックや独自関数をグループ単位で適用したい
filter() グループ単位の抽出 条件を満たすグループだけを残したい
size() / count() 件数のカウント グループごとのデータ件数を把握したい
first() / last() / nth() 特定行の取得 各グループの代表行や特定順の行を取得したい