Pandas ピボットテーブルで欠損値を処理する方法:fill_value

2025-04-26

pandas.pivot_table とは

pandas.pivot_table は、データフレームからピボットテーブルを作成するための強力な関数です。ピボットテーブルは、Excelなどの表計算ソフトでお馴染みの機能で、データを特定の列の値に基づいて再構成し、集計結果を分かりやすく表示するのに役立ちます。

主な機能と役割

pivot_table 関数を使用すると、以下のことが可能です。

  • マージンの追加
    行と列の合計や平均などのマージン(総計)を自動的に計算して追加できます。
  • 欠損値の処理
    欠損値(NaN)を特定の値で埋めることができます。
  • 集計関数の適用
    値の列に対して、平均値 (mean)、合計値 (sum)、中央値 (median)、カウント (count) など、様々な集計関数を適用できます。複数の集計関数を同時に適用することも可能です。
  • 値の指定
    ピボットテーブルの各セルに表示する値を、データフレームの特定の列から選択できます。
  • カラム(列)の指定
    データフレームの別の列のユニークな値をピボットテーブルの列ラベルとして使用できます。
  • インデックス(行)の指定
    データフレームの特定の列のユニークな値をピボットテーブルの行ラベルとして使用できます。

基本的な構文

pandas.pivot_table(data, values=None, index=None, columns=None, aggfunc='mean', fill_value=None, margins=False, dropna=True, margins_name='All', observed=False, sort=True)

各引数の主な意味は以下の通りです。

  • sort: 結果をソートするかどうか。
  • observed: カテゴリカル型のインデックス/カラムのみに存在する値を表示するかどうか。
  • margins_name: マージン行/列の名前(デフォルトは 'All')。
  • dropna: すべての値が NaN である列を削除するかどうか(True または False)。デフォルトは True です。
  • margins: 行/列の合計や平均などのマージンを追加するかどうか(True または False)。デフォルトは False です。
  • fill_value: 欠損値(NaN)を置き換える値。
  • aggfunc: 値を集計するために使用する関数(例: 'mean', 'sum', numpy.mean, ['mean', 'sum'] など)。デフォルトは 'mean' です。
  • columns: ピボットテーブルの列インデックスとして使用する列名(または列名のリスト)。
  • index: ピボットテーブルの行インデックスとして使用する列名(または列名のリスト)。
  • values: ピボットテーブルのセルに表示する値を含む列名(または列名のリスト)。
  • data: ピボットテーブルを作成する元のデータフレーム。

使用例

以下のようなデータフレームがあるとします。

import pandas as pd

data = {'商品': ['A', 'A', 'B', 'B', 'A', 'B'],
        '店舗': ['東京', '大阪', '東京', '大阪', '東京', '大阪'],
        '売上': [100, 150, 120, 180, 110, 160]}
df = pd.DataFrame(data)
print(df)

出力:

  商品  店舗   売上
0  A  東京  100
1  A  大阪  150
2  B  東京  120
3  B  大阪  180
4  A  東京  110
5  B  大阪  160

このデータフレームから、商品ごとの店舗別売上の平均値をピボットテーブルで表示するには、以下のようにします。

pivot_table_result = pd.pivot_table(df, values='売上', index='商品', columns='店舗', aggfunc='mean')
print(pivot_table_result)
店舗    大阪    東京
商品
A  150.0  105.0
B  170.0  120.0

この例では、'商品' 列がインデックス(行)、'店舗' 列がカラム(列)となり、'売上' 列の平均値が各セルに表示されています。

より複雑な例

複数の集計関数を適用したり、マージンを追加したりすることもできます。

pivot_table_multiple = pd.pivot_table(df, values='売上', index='商品', columns='店舗', aggfunc=['mean', 'sum'], margins=True)
print(pivot_table_multiple)
         mean        sum
店舗       大阪    東京   All   大阪    東京   All
商品
A     150.0  105.0  120.0  150  210  360
B     170.0  120.0  150.0  170  120  290
All   160.0  112.5  138.333333  320  330  650


pandas.pivot_table の一般的なエラーとトラブルシューティング

pandas.pivot_table は非常に便利な関数ですが、使い方を間違えると様々なエラーが発生することがあります。ここでは、よくあるエラーとその原因、そして解決策について解説します。

ValueError: Index contains duplicate entries, cannot reshape (インデックスに重複したエントリがあり、再形成できません)


  • 原因
    indexcolumns、そして集計対象の values の組み合わせが一意でない場合に発生します。つまり、同じ行と列の組み合わせに対して複数の values が存在し、かつ aggfunc が指定されていない(またはデフォルトの 'mean' であり、複数の値をどのように集計すべきか不明な)場合に起こります。

import pandas as pd

data = {'商品': ['A', 'A', 'A'],
        '店舗': ['東京', '東京', '大阪'],
        '売上': [100, 150, 120]}
df = pd.DataFrame(data)

# エラーが発生する例 (同じ商品・店舗の組み合わせが複数存在し、集計方法が不明)
try:
    pivot_table_error = pd.pivot_table(df, values='売上', index='商品', columns='店舗')
except ValueError as e:
    print(f"エラー発生: {e}")
  • トラブルシューティング
    • aggfunc を指定する
      複数の values が同じインデックスとカラムの組み合わせに存在する場合は、どのように集計するかを aggfunc 引数で明示的に指定します(例: 'mean', 'sum', numpy.mean など)。
    • インデックスまたはカラムの指定を見直す
      ピボットテーブルの行や列として使用する列が適切かどうかを確認します。より詳細なレベルでピボットしたい場合は、複数の列を indexcolumns に指定することを検討します。
    • 事前にデータを集計する
      pivot_table を実行する前に、groupby() などを使って必要な粒度でデータを集計しておくことも有効な場合があります。

KeyError: '指定した列名' (指定された列名が見つかりません)


  • 原因
    values, index, columns 引数に指定した列名が、元のデータフレーム data に存在しない場合に発生します。

try:
    pivot_table_keyerror = pd.pivot_table(df, values='存在しない列', index='商品', columns='店舗')
except KeyError as e:
    print(f"エラー発生: {e}")
  • トラブルシューティング
    • 列名を確認する
      データフレームの列名 (df.columns) を確認し、pivot_table に渡している列名が正しいかどうかを慎重にチェックします。スペルミスや大文字・小文字の違いにも注意が必要です。

TypeError: '集計関数' object is not callable (集計関数オブジェクトは呼び出し可能ではありません)


  • 原因
    aggfunc に文字列ではなく、関数オブジェクト自体を渡す場合に、括弧 () を付け忘れて呼び出そうとした際に発生することがあります。

import numpy as np

try:
    pivot_table_typeerror = pd.pivot_table(df, values='売上', index='商品', columns='店舗', aggfunc=np.mean) # 正しい
    # pivot_table_typeerror_incorrect = pd.pivot_table(df, values='売上', index='商品', columns='店舗', aggfunc=np.mean()) # これは関数の実行になるので誤り
except TypeError as e:
    print(f"エラー発生: {e}")
  • トラブルシューティング
    • aggfunc に関数オブジェクトを渡す場合は、括弧を付けずに渡します。 文字列で指定する場合は、括弧は不要です。

期待しない結果 (NaN が多い、値がおかしいなど)

  • トラブルシューティング

    • 欠損値の処理
      fill_value 引数を使用して NaN を特定の値で埋めることを検討します。
    • データの確認
      元のデータフレームを確認し、期待される組み合わせが存在するかどうか、データの分布などを把握します。
    • aggfunc の再検討
      データの種類や分析の目的に合った適切な集計関数を選択します。複数の関数を試してみるのも有効です。
    • dropna の確認
      デフォルトでは、すべての値が NaN である列は削除されます。意図しない列の削除を防ぎたい場合は、dropna=False を設定します。
  • 原因

    • データの欠損
      元のデータに欠損値(NaN)が含まれており、fill_value を指定していない場合、ピボットテーブルの結果にも NaN が現れることがあります。
    • 該当する組み合わせが存在しない
      指定した indexcolumns の組み合わせに対応するデータが存在しない場合、そのセルは NaN になります。
    • 不適切な aggfunc の使用
      データの性質に合わない集計関数を使用すると、意味のない結果になることがあります。例えば、カテゴリカルデータに対して平均値を計算しても意味がありません。

メモリ不足

  • トラブルシューティング

    • データの削減
      分析に必要なデータのみに絞り込むことを検討します。不要な列を削除したり、サンプリングを行ったりするなどの方法があります。
    • データ型の最適化
      データフレームの各列のデータ型を、必要最小限のメモリで済む型に変換します (pd.to_numeric など)。
    • チャンク処理
      大きなデータを一度に処理するのではなく、小さなチャンクに分割して処理することを検討します(pandas.read_csvchunksize パラメータなど)。ただし、pivot_table 自体には直接的なチャンク処理の機能はありませんので、前処理の段階で工夫が必要になります。
  • 原因
    元のデータフレームが非常に大きい場合や、indexcolumns の組み合わせの数が非常に多い場合、ピボットテーブルの作成に大量のメモリが必要となり、メモリ不足のエラーが発生することがあります。

トラブルシューティングの一般的なヒント

  • pandas のドキュメントを参照する
    pivot_table 関数の詳細な仕様や引数については、pandas の公式ドキュメントを参照してください。
  • print 文を活用する
    データフレームの内容や途中経過を print() 関数で出力して確認することで、問題の所在を特定しやすくなります。
  • 小さなデータで試す
    まずは元のデータの一部をコピーして、小さなデータフレームで pivot_table の動作を確認してみるのが有効です。
  • エラーメッセージをよく読む
    Python のエラーメッセージは、問題の原因を示唆する重要な情報を含んでいます。


基本的なピボットテーブルの作成

まず、簡単なデータフレームを作成し、基本的なピボットテーブルを作成する例です。

import pandas as pd

# サンプルデータフレームの作成
data = {'商品': ['A', 'A', 'B', 'B', 'A', 'B'],
        '店舗': ['東京', '大阪', '東京', '大阪', '東京', '大阪'],
        '売上': [100, 150, 120, 180, 110, 160]}
df = pd.DataFrame(data)
print("元のデータフレーム:")
print(df)

# 商品ごとの店舗別売上の平均値をピボットテーブルで表示
pivot_mean = pd.pivot_table(df, values='売上', index='商品', columns='店舗', aggfunc='mean')
print("\n商品ごとの店舗別売上平均:")
print(pivot_mean)

この例では、'商品' 列をインデックス(行)、'店舗' 列をカラム(列)とし、'売上' 列の平均値を各セルに表示するピボットテーブルを作成しています。aggfunc='mean' で平均値を計算することを指定しています。

複数の集計関数を適用する

複数の集計関数を同時に適用する例です。

# 商品ごとの店舗別売上の平均と合計を同時に表示
pivot_multiple_agg = pd.pivot_table(df, values='売上', index='商品', columns='店舗', aggfunc=['mean', 'sum'])
print("\n商品ごとの店舗別売上平均と合計:")
print(pivot_multiple_agg)

aggfunc にリスト ['mean', 'sum'] を渡すことで、平均値と合計値の両方を計算し、結果のピボットテーブルは階層的なカラムを持つようになります。

インデックスとカラムに複数の列を使用する

インデックスやカラムに複数の列を使用する例です。

# サンプルデータフレームの作成 (地域を追加)
data_multi = {'商品': ['A', 'A', 'B', 'B', 'A', 'B', 'A', 'B'],
              '店舗': ['東京', '大阪', '東京', '大阪', '東京', '大阪', '福岡', '福岡'],
              '地域': ['東日本', '西日本', '東日本', '西日本', '東日本', '西日本', '西日本', '西日本'],
              '売上': [100, 150, 120, 180, 110, 160, 130, 170]}
df_multi = pd.DataFrame(data_multi)
print("\n複数の列を持つ元のデータフレーム:")
print(df_multi)

# 商品と地域をインデックス、店舗をカラムとするピボットテーブル
pivot_multi_index = pd.pivot_table(df_multi, values='売上', index=['商品', '地域'], columns='店舗', aggfunc='mean')
print("\n商品と地域をインデックス、店舗をカラムとした売上平均:")
print(pivot_multi_index)

index=['商品', '地域'] のようにリストで複数の列名を指定することで、階層的なインデックスを持つピボットテーブルを作成できます。

欠損値の処理

欠損値(NaN)を特定の値で埋める例です。

# 一部の組み合わせが存在しないデータフレームを作成
data_nan = {'商品': ['A', 'A', 'B'],
            '店舗': ['東京', '大阪', '東京'],
            '売上': [100, 150, 120]}
df_nan = pd.DataFrame(data_nan)
pivot_nan = pd.pivot_table(df_nan, values='売上', index='商品', columns='店舗', aggfunc='mean')
print("\n欠損値を含むピボットテーブル:")
print(pivot_nan)

# 欠損値を 0 で埋める
pivot_filled = pd.pivot_table(df_nan, values='売上', index='商品', columns='店舗', aggfunc='mean', fill_value=0)
print("\n欠損値を 0 で埋めたピボットテーブル:")
print(pivot_filled)

fill_value=0 を指定することで、ピボットテーブル内でデータが存在しないために NaN となったセルを 0 で埋めることができます。

マージン(総計)の追加

行と列の合計などのマージンを追加する例です。

# マージンを追加したピボットテーブル
pivot_margins = pd.pivot_table(df, values='売上', index='商品', columns='店舗', aggfunc='sum', margins=True, margins_name='合計')
print("\nマージン(合計)を追加したピボットテーブル:")
print(pivot_margins)

margins=True を設定すると、行と列の総計が自動的に計算され、margins_name でそのラベルを指定できます。

特定の列の値に基づいてフィルタリングしてからピボットテーブルを作成する

データフレームをフィルタリングしてからピボットテーブルを作成する例です。

# '売上' が 130 より大きいデータのみを対象にピボットテーブルを作成
filtered_df = df[df['売上'] > 130]
pivot_filtered = pd.pivot_table(filtered_df, values='売上', index='商品', columns='店舗', aggfunc='mean')
print("\n'売上' が 130 より大きいデータのみのピボットテーブル:")
print(pivot_filtered)

まず条件に合致するデータで新しいデータフレームを作成し、そのデータフレームに対して pivot_table を適用します。

異なるデータ型を含む場合の処理

ピボットテーブルの values に数値型以外の列を指定した場合の挙動です。通常、集計関数は数値データに対して適用されるため、数値型以外の列を指定すると、意味のある結果が得られないか、エラーが発生する可能性があります。

# 数値型と文字列型を含むデータフレーム
data_mixed = {'カテゴリ': ['果物', '野菜', '果物', '野菜'],
              '種類': ['リンゴ', 'キャベツ', 'ブドウ', 'ニンジン'],
              '価格': [100, 80, 150, 60],
              '産地': ['青森', '長野', '山梨', '北海道']}
df_mixed = pd.DataFrame(data_mixed)
print("\n数値型と文字列型を含むデータフレーム:")
print(df_mixed)

# 文字列型の列を 'values' に指定した場合 (通常は意味のある集計はできません)
pivot_mixed = pd.pivot_table(df_mixed, values='産地', index='カテゴリ', columns='種類', aggfunc=lambda x: ', '.join(x.astype(str)))
print("\n文字列型の列を 'values' に指定したピボットテーブル (例として文字列結合):")
print(pivot_mixed)

この例では、'産地' という文字列型の列を values に指定し、集計関数としてラムダ関数を使って文字列を結合しています。これは特殊なケースであり、通常は数値型の列に対して集計関数を適用します。



groupby() と unstack() の組み合わせ

groupby() はデータを特定の列の値に基づいてグループ化し、集計関数を適用するために使用されます。unstack() は、groupby() の結果のインデックスレベルをカラムレベルに変換するために使用されます。この組み合わせは、pivot_table の基本的な機能を手動で実現するのに役立ち、より細かい制御が可能です。

import pandas as pd

# サンプルデータフレーム (pivot_table の例と同じ)
data = {'商品': ['A', 'A', 'B', 'B', 'A', 'B'],
        '店舗': ['東京', '大阪', '東京', '大阪', '東京', '大阪'],
        '売上': [100, 150, 120, 180, 110, 160]}
df = pd.DataFrame(data)

# groupby() でグループ化し、mean() で平均を計算
grouped = df.groupby(['商品', '店舗'])['売上'].mean()
print("groupby() の結果:")
print(grouped)

# unstack() で '店舗' レベルをカラムに変換
unstacked = grouped.unstack()
print("\nunstack() の結果 (pivot_table と同様):")
print(unstacked)

利点

  • 中間結果の確認
    groupby() の結果を途中で確認できるため、デバッグがしやすい場合があります。
  • 柔軟性
    集計後の処理や、より複雑なインデックス操作が可能です。

欠点

  • 可読性の低下
    シンプルなピボットテーブルを作成する場合、pivot_table の方が直感的です。
  • コードが長くなる可能性
    特に複数の集計関数を適用する場合などは、pivot_table より冗長になることがあります。

複数の集計関数を適用する場合

# groupby() と agg() を使用して複数の集計関数を適用
grouped_agg = df.groupby(['商品', '店舗'])['売上'].agg(['mean', 'sum'])
print("\ngroupby() と agg() の結果:")
print(grouped_agg)

# unstack() で '店舗' レベルをカラムに変換
unstacked_agg = grouped_agg.unstack()
print("\nunstack() 後の複数の集計結果:")
print(unstacked_agg)

set_index() と unstack() の組み合わせ

データフレームを特定の列でインデックスを設定し (set_index())、その後 unstack() を使用してインデックスレベルをカラムに変換する方法です。集計は事前に完了しているデータや、集計が不要な場合に適しています。

# 集計済みのデータフレームを想定 (実際には事前に集計が必要)
data_pre_agg = {'商品': ['A', 'A', 'B', 'B'],
                '店舗': ['東京', '大阪', '東京', '大阪'],
                '平均売上': [105, 150, 120, 170]}
df_pre_agg = pd.DataFrame(data_pre_agg)

# '商品' と '店舗' をインデックスに設定
indexed = df_pre_agg.set_index(['商品', '店舗'])
print("set_index() の結果:")
print(indexed)

# '店舗' レベルをカラムに unstack()
unstacked_pre_agg = indexed.unstack()
print("\nunstack() の結果:")
print(unstacked_pre_agg)

利点

  • シンプルな構造
    集計が不要な場合、コードが比較的シンプルになります。
  • 集計済みのデータに直接適用可能
    事前に集計されたデータに対して、ピボットテーブルのような形状に変換するのに便利です。

欠点

  • 集計機能がない
    pivot_table のように自動的に集計を行う機能はありません。事前に集計処理が必要です。

ループ処理と辞書などを使った手動での再構成

より複雑なロジックや、pandas の組み込み関数では直接的に実現できないようなピボット処理を行いたい場合には、ループ処理と辞書などのデータ構造を組み合わせて手動でデータを再構成する方法も考えられます。ただし、この方法は一般的にコードが複雑になりやすく、パフォーマンスも劣る可能性があるため、推奨されるのは特殊なケースのみです。

# 手動でピボットテーブルのような構造を作成する例
pivot_manual = {}
for index, row in df.iterrows():
    商品 = row['商品']
    店舗 = row['店舗']
    売上 = row['売上']
    if 商品 not in pivot_manual:
        pivot_manual[商品] = {}
    pivot_manual[商品][店舗] = pivot_manual[商品].get(店舗, []) + [売上]

# 平均値を計算 (例)
pivot_manual_mean = {商品: {店舗: sum(売上リスト) / len(売上リスト) if 売上リスト else None
                           for 店舗, 売上リスト in 店舗別売上.items()}
                     for 商品, 店舗別売上 in pivot_manual.items()}

print("\n手動で作成したピボットテーブル (平均):")
print(pivot_manual_mean)

# pandas DataFrame に変換することも可能
pivot_manual_df = pd.DataFrame.from_dict(pivot_manual_mean, orient='index')
print("\n手動で作成し、DataFrame に変換した結果:")
print(pivot_manual_df)

利点

  • 非常に柔軟
    どんな複雑な再構成ロジックでも実装できます。

欠点

  • パフォーマンスが劣る可能性
    pandas の最適化された関数に比べて、ループ処理は一般的に遅いです。
  • コードが複雑になりやすい
    特に大規模なデータや複雑な処理の場合、コードの可読性や保守性が低下します。

他のライブラリの利用

pandas 以外にも、データ分析やテーブル操作に特化したライブラリが存在します。例えば、Dask は大規模なデータセットを効率的に処理するための並列計算ライブラリであり、場合によってはピボット操作も可能です。ただし、これらのライブラリは pandas と異なるデータ構造やAPIを持っているため、学習コストがかかる場合があります。

pandas.pivot_table は多くの一般的なピボット処理を簡単に行うための強力なツールですが、

  • 大規模データセットの場合は、Dask などの他のライブラリも検討の余地があります。
  • 非常に特殊なケースでは、手動でのデータ再構成も考えられますが、複雑性やパフォーマンスに注意が必要です。
  • 事前に集計されたデータをピボットテーブルのような形状に変換したい場合は、set_index()unstack() が適しています。
  • より細かい制御や柔軟な操作が必要な場合は、groupby()unstack() の組み合わせが有効です。