機械学習データ前処理:複数の切り口で集計!Pandasでピボットテーブル(クロス集計)を作る方法【Pandas入門】
機械学習モデルの構築において、データを理解し、適切な形に整えるデータ前処理は非常に重要なステップです。データには様々な情報が詰まっていますが、そのままの状態では見えにくい傾向やパターンが多く存在します。これらの隠れた情報を引き出すための強力な手法の一つに、「集計」があります。
特に、データを複数の異なる「切り口」からまとめて眺めることができる集計方法は、データの構造や特性を理解する上で非常に役立ちます。ビジネスの現場でExcelのピボットテーブルを使った経験がある方もいらっしゃるかもしれません。Pandasライブラリでも、これと同様の、あるいはさらに柔軟な集計を行うことができます。
この記事では、Pandasを使ってデータを複数の軸で集計し、多角的な視点から分析するための「ピボットテーブル」と「クロス集計」の作成方法について、初心者の方にも分かりやすく解説します。
ピボットテーブル(クロス集計)とは?
まず、ピボットテーブルやクロス集計がどのようなものか、簡単に理解しましょう。
一般的なデータテーブルは、各行が個別の観測データ(例:商品の販売記録1件)、各列がその観測データに関する属性(例:販売日、商品名、価格、販売数量)となっています。
ピボットテーブルやクロス集計では、このような元のデータを、特定の列を「行の軸」に、別の列を「列の軸」にして再構成し、それぞれの交差する部分(セル)に指定した値(別の列の値)を集計(合計、平均、カウントなど)した結果を表示します。
例えば、以下のような架空の販売データがあったとします。
| 日付 | エリア | 商品名 | 販売数量 | 売上(円) | | :--------- | :------- | :------- | :------- | :--------- | | 2023-01-01 | 東京 | りんご | 3 | 450 | | 2023-01-01 | 大阪 | みかん | 5 | 500 | | 2023-01-02 | 東京 | みかん | 2 | 200 | | 2023-01-02 | 大阪 | りんご | 4 | 600 | | 2023-01-03 | 東京 | りんご | 1 | 150 | | 2023-01-03 | 大阪 | みかん | 6 | 600 |
このデータを、「行の軸」をエリア、「列の軸」を商品名として、「売上(円)」の合計値を集計するピボットテーブルを作成すると、以下のような表が得られます。
| エリア | りんご | みかん | | :----- | :----- | :----- | | 大阪 | 600 | 1100 | | 東京 | 600 | 200 |
このように、元のデータでは1件ずつバラバラに記録されていた販売実績を、「エリアごと」かつ「商品名ごと」という複数の切り口でまとめて見ることができます。これにより、「大阪ではみかんの売上が高い」「東京ではりんごの方が売れている」といった傾向を一目で把握することが可能になります。
この多角的な集計は、機械学習モデルの訓練において、データセットの理解を深めたり、新たな特徴量を作成したりする際に非常に有効です。
Pandasでのピボットテーブル・クロス集計
Pandasでは、主に以下の2つの関数を使ってピボットテーブルやクロス集計を作成します。
DataFrame.pivot_table()
: より汎用的で高機能なピボットテーブルを作成するための関数です。欠損値の処理や複数の集計方法の指定など、多くのオプションを持っています。pd.crosstab()
: 2つ以上の要素間の「度数」(出現頻度)を集計することに特化した関数です。カテゴリデータの関連性を調べたい場合などに便利です。
まずは、より汎用的なpivot_table()
から見ていきましょう。
DataFrame.pivot_table()
の使い方
pivot_table()
関数は、元のDataFrameに対してメソッドとして使用します。基本的な引数は以下の通りです。
data
: 集計対象のDataFrameを指定します。(メソッドとして使う場合は省略)index
: 行の軸にする列(または列のリスト)を指定します。columns
: 列の軸にする列(または列のリスト)を指定します。values
: 集計対象の値となる列(または列のリスト)を指定します。aggfunc
: 集計方法を指定します。'sum'
(合計),'mean'
(平均),'count'
(個数),'max'
(最大値),'min'
(最小値) などを文字列で指定したり、関数を指定したりできます。リストで複数の集計方法を指定することも可能です。fill_value
: 集計結果が欠損値(NaN)になるセルを埋める値を指定します。margins
: 合計行/列(総計)を追加するかどうかをブール値で指定します。デフォルトはFalse
です。margins_name
:margins=True
の場合に表示される合計行/列の名前を指定します。デフォルトは'All'
です。
それでは、先ほどの販売データを使って実際にピボットテーブルを作成してみましょう。
まず、サンプルデータを含むDataFrameを作成します。
import pandas as pd
# サンプルデータの作成
data = {
'日付': ['2023-01-01', '2023-01-01', '2023-01-02', '2023-01-02', '2023-01-03', '2023-01-03'],
'エリア': ['東京', '大阪', '東京', '大阪', '東京', '大阪'],
'商品名': ['りんご', 'みかん', 'みかん', 'りんご', 'りんご', 'みかん'],
'販売数量': [3, 5, 2, 4, 1, 6],
'売上(円)': [450, 500, 200, 600, 150, 600]
}
df = pd.DataFrame(data)
print("元のDataFrame:")
print(df)
上記のコードを実行すると、以下のようなDataFrameが作成・表示されます。
元のDataFrame:
日付 エリア 商品名 販売数量 売上(円)
0 2023-01-01 東京 りんご 3 450
1 2023-01-01 大阪 みかん 5 500
2 2023-01-02 東京 みかん 2 200
3 2023-01-02 大阪 りんご 4 600
4 2023-01-03 東京 りんご 1 150
5 2023-01-03 大阪 みかん 6 600
このDataFrameに対して、エリアを行、商品名を列として、売上の合計を計算するピボットテーブルを作成します。
# エリア別・商品名別の売上合計を計算するピボットテーブル
pivot_sales_total = df.pivot_table(
index='エリア', # 行の軸
columns='商品名', # 列の軸
values='売上(円)', # 集計対象の値
aggfunc='sum' # 集計方法(合計)
)
print("\nエリア別・商品名別の売上合計:")
print(pivot_sales_total)
出力結果は以下のようになります。
エリア別・商品名別の売上合計:
商品名 みかん りんご
エリア
大阪 1100.0 600.0
東京 200.0 600.0
期待通りのピボットテーブルが作成されました。元のデータに存在しない組み合わせ(例: 東京でみかんの売上がない日付)は、デフォルトで欠損値(NaN)となりますが、今回はたまたま全て存在したためNaNはありませんでした。もしNaNが発生した場合、fill_value=0
のように指定すれば、NaNを0で埋めることができます。
次に、販売数量の合計を計算してみましょう。
# エリア別・商品名別の販売数量合計を計算するピボットテーブル
pivot_quantity_total = df.pivot_table(
index='エリア',
columns='商品名',
values='販売数量',
aggfunc='sum',
fill_value=0 # 欠損値を0で埋める
)
print("\nエリア別・商品名別の販売数量合計:")
print(pivot_quantity_total)
出力結果:
エリア別・商品名別の販売数量合計:
商品名 みかん りんご
エリア
大阪 11 4
東京 2 4
このように、values
とaggfunc
を変更することで、様々な切り口で集計することができます。
複数の列を行/列の軸にする
index
やcolumns
には、列名のリストを指定することも可能です。これにより、より詳細な階層構造を持つピボットテーブルを作成できます。
例として、「日付」と「エリア」を行の軸、「商品名」を列の軸として、売上合計を集計してみましょう。
# 日付・エリア別・商品名別の売上合計を計算するピボットテーブル
pivot_multi_index = df.pivot_table(
index=['日付', 'エリア'], # 日付とエリアを組み合わせて行の軸に
columns='商品名',
values='売上(円)',
aggfunc='sum',
fill_value=0
)
print("\n日付・エリア別・商品名別の売上合計:")
print(pivot_multi_index)
出力結果:
日付・エリア別・商品名別の売上合計:
商品名 みかん りんご
日付 エリア
2023-01-01 大阪 500 0
東京 0 450
2023-01-02 大阪 0 600
東京 200 0
2023-01-03 大阪 600 0
東京 0 150
このように、複数列を指定することで、より細分化された集計結果を得ることができます。
複数の値を集計する
values
にも列名のリストを指定し、aggfunc
に集計方法のリストまたは辞書を指定することで、複数の値を同時に集計することも可能です。
例として、売上と販売数量の両方を、エリア別・商品名別で合計してみましょう。
# エリア別・商品名別で、売上合計と販売数量合計を同時に集計
pivot_multi_values = df.pivot_table(
index='エリア',
columns='商品名',
values=['売上(円)', '販売数量'], # 複数の集計対象を指定
aggfunc='sum', # 全ての集計対象に合計を適用
fill_value=0
)
print("\nエリア別・商品名別、売上と販売数量の合計:")
print(pivot_multi_values)
出力結果:
エリア別・商品名別、売上と販売数量の合計:
売上(円) 販売数量
商品名 みかん りんご みかん りんご
エリア
大阪 1100 600 11 4
東京 200 600 2 4
このように、各列(商品名)の下に集計対象の値(売上、販売数量)が階層的に表示されます。
集計方法を値ごとに変える
aggfunc
に辞書形式で { '集計対象列名': '集計方法' }
のように指定すると、値ごとに異なる集計方法を適用できます。
# エリア別・商品名別で、売上は合計、販売数量は平均を計算
pivot_diff_aggfunc = df.pivot_table(
index='エリア',
columns='商品名',
values=['売上(円)', '販売数量'],
aggfunc={'売上(円)': 'sum', '販売数量': 'mean'}, # 値ごとに集計方法を指定
fill_value=0
)
print("\nエリア別・商品名別、売上合計と販売数量平均:")
print(pivot_diff_aggfunc)
出力結果:
エリア別・商品名別、売上合計と販売数量平均:
売上(円) 販売数量
商品名 みかん りんご みかん りんご
エリア
大阪 1100 600 5.5 4.0
東京 200 600 2.0 2.0
このように、pivot_table()
関数は非常に柔軟な集計を行うことができます。
pd.crosstab()
の使い方
pd.crosstab()
関数は、Pandasのトップレベル関数として使用します。主に、2つ以上の因子(カテゴリカルな列)間の出現頻度(度数)を集計するのに特化しています。これは統計学でいう「クロス集計表」を作成することに相当します。
基本的な引数は以下の通りです。
index
: 行にする列(または列のリスト)を指定します。columns
: 列にする列(または列のリスト)を指定します。values
: 集計対象の値となる列を指定します。指定しない場合は度数(カウント)が集計されます。aggfunc
:values
を指定した場合の集計方法を指定します。rownames
: 行の軸の名前を指定します。colnames
: 列の軸の名前を指定します。normalize
: 度数ではなく、正規化された値(割合)を表示するかどうかを指定します。True
で合計に対する割合、'index'
で行合計に対する割合、'columns'
で列合計に対する割合を表示します。
例として、エリアと商品名の組み合わせの出現頻度(販売実績があった回数)をクロス集計してみましょう。
# エリアと商品名の組み合わせの度数をクロス集計
crosstab_freq = pd.crosstab(
index=df['エリア'], # 行の軸
columns=df['商品名'] # 列の軸
)
print("\nエリア別・商品名別の出現頻度:")
print(crosstab_freq)
出力結果:
エリア別・商品名別の出現頻度:
商品名 みかん りんご
エリア
大阪 2 1
東京 1 2
この表から、「大阪ではみかんの販売実績が2回、りんごが1回あった」「東京ではみかんが1回、りんごが2回あった」ということが分かります。これはpivot_table(values='任意の列', aggfunc='count')
とほぼ同じ結果になりますが、crosstab
の方がシンプルに記述できます。
crosstab
でもvalues
とaggfunc
を指定することで、度数以外の集計を行うこともできます。この場合、pivot_table
と機能的に近くなりますが、主にカテゴリデータの集計に焦点を当てたい場合に使い分けられます。
例として、エリア別・商品名別で売上合計をクロス集計してみましょう(pivot_table
と同じ結果になるはずです)。
# エリア別・商品名別の売上合計をクロス集計
crosstab_sales = pd.crosstab(
index=df['エリア'],
columns=df['商品名'],
values=df['売上(円)'], # 集計対象の値
aggfunc='sum' # 集計方法(合計)
)
print("\nエリア別・商品名別の売上合計 (crosstab):")
print(crosstab_sales)
出力結果:
エリア別・商品名別の売上合計 (crosstab):
商品名 みかん りんご
エリア
大阪 1100 600
東京 200 600
また、normalize=True
を指定すると、全体の合計に対する割合を表示できます。
# エリアと商品名の組み合わせの割合をクロス集計
crosstab_norm = pd.crosstab(
index=df['エリア'],
columns=df['商品名'],
normalize=True # 全体に対する割合を表示
)
print("\nエリア別・商品名別の割合:")
print(crosstab_norm)
出力結果:
エリア別・商品名別の割合:
商品名 みかん りんご
エリア
大阪 0.333333 0.166667
東京 0.166667 0.333333
この表は、全6件の販売実績のうち、大阪・みかんの組み合わせが2件(2/6 = 0.333...)、大阪・りんごが1件(1/6 = 0.166...)といった割合を示しています。
どちらの関数を使うべきか?
pivot_table()
とcrosstab()
は似た機能を持つため、どちらを使うべきか迷うかもしれません。一般的な使い分けの目安としては以下のようになります。
pivot_table()
: 数値データを含む、より自由度の高い汎用的な集計を行いたい場合。複数の値を集計したり、値ごとに異なる集計方法を適用したい場合などに適しています。Excelのピボットテーブルに近い感覚で使用できます。crosstab()
: 主にカテゴリカルな列間の出現頻度(度数)をシンプルに集計したい場合。2つのカテゴリカルな変数の関連性を見るクロス集計表を作成するのに特化しています。
ほとんどのケースではpivot_table()
で対応可能ですが、カテゴリ間の度数集計を素早く行いたい場合はcrosstab()
も便利です。
まとめ
この記事では、Pandasを使ってデータを複数の切り口で集計する「ピボットテーブル」と「クロス集計」の作成方法について解説しました。
- ピボットテーブル/クロス集計は、元のフラットなデータを、指定した列を行や列の軸として再構成し、別の列の値を集計する手法です。
- これにより、データに潜むパターンや傾向を多角的な視点から効率的に把握することができます。
- Pandasでは、
DataFrame.pivot_table()
とpd.crosstab()
の2つの主要な関数を使ってこれらの集計を行います。 pivot_table()
はより高機能で汎用的、crosstab()
は主にカテゴリ間の度数集計に特化しています。- これらの関数を使うことで、データの理解を深めたり、機械学習モデルのための新しい特徴量を作成したりすることが可能になります。
データ分析や機械学習プロジェクトにおいて、データを様々な角度から眺める集計作業は欠かせません。今回ご紹介したPandasの機能を使って、ご自身のデータをぜひ集計してみてください。次回の記事では、さらに別のデータ前処理手法について解説していきます。