Jaybanuan's Blog

どうせまた調べるハメになることをメモしていくブログ

Pandasでgroupby()した際にパターンを網羅できない不完全な表になる場合の対処

はじめに

書きたいことをタイトルで端的に表現できずに困った。 例として、バナナとミカンとイチゴの販売データを表現するDataFrameがあったとする。 そして、今回はイチゴが売れなかったので、販売データにはイチゴは含まれていないとする。

    商品  数量
0  バナナ   1
1  ミカン   4
2  バナナ   3
3  バナナ   1
4  ミカン   6
5  ミカン   1
6  バナナ   4

このDataFrameを以下のように商品ごとに集計すると、

df = df.groupby('商品').sum()
print(df)

出力結果は以下のようになる。

     数量
商品     
バナナ   9
ミカン  11

この表にはイチゴのデータがなく、レポートとして見たときには網羅性がなくてイマイチなので、改善してイチゴのデータ(販売数は0)も含めたい。

環境

$ python3 --version
Python 3.10.12

$ poetry show | grep pandas
pandas          2.2.1       Powerful data structures for data analysis, tim...

対処方法

groupby()の結果、商品がインデックスになる。 このインデックスをイチゴも含めて再構築すればよい。 具体的には、以下のようにする。

df = df.reindex(['バナナ', 'ミカン', 'イチゴ'], fill_value=0)
print(df)

出力結果は以下のようになり、イチゴの行も含まれる。

     数量
商品     
バナナ   9
ミカン  11
イチゴ   0

reindex()の引数にfill_value=0を付与しない場合は、イチゴの数量は欠損値(NaN)となる。 欠損値を細かく制御したい場合は、fill_value=0を付与せずに、後でfillna()等を実行して適切な値を埋めればよい。

マルチインデックスの場合はどうするか

以下に先程と似たような販売データを示すが、「店舗」という列を付け加えており、A店、B店、C店の3店舗あるとする。 商品は先程と同じくバナナ、ミカン、イチゴの3種類あるとする。

   店舗   商品  数量
0  A店  バナナ   1
1  C店  ミカン   4
2  C店  バナナ   3
3  A店  バナナ   1
4  C店  ミカン   6
5  A店  ミカン   1
6  A店  イチゴ   4

このDataFrameを以下のように商品ごとに集計すると、

df = df.groupby(['店舗', '商品']).sum()
print(df)

出力結果は以下のようになる。

        数量
店舗 商品     
A店 イチゴ   4
   バナナ   2
   ミカン   1
C店 バナナ   3
   ミカン  10

C店にはイチゴのデータはなく、業績不振で商品がひとつも売れなかったB店のデータはまったくない。 そこで、以下のようにして、マルチインデックスを再構築する。

shops = ['A店', 'B店', 'C店']
products = ['バナナ', 'ミカン', 'イチゴ']
df = df.reindex(pd.MultiIndex.from_product([shops, products], names=['店舗', '商品']), fill_value=0)
print(df)

出力結果は以下のように網羅性のある表になる。

        数量
店舗 商品     
A店 バナナ   2
   ミカン   1
   イチゴ   4
B店 バナナ   0
   ミカン   0
   イチゴ   0
C店 バナナ   3
   ミカン  10
   イチゴ   0

参考

stackoverflow.com

付録A シングルインデックスのテストコード

import pandas as pd

data = [
    {'商品': 'バナナ', '数量': 1},
    {'商品': 'ミカン', '数量': 4},
    {'商品': 'バナナ', '数量': 3},
    {'商品': 'バナナ', '数量': 1},
    {'商品': 'ミカン', '数量': 6},
    {'商品': 'ミカン', '数量': 1},
    {'商品': 'バナナ', '数量': 4},
]

df = pd.DataFrame(data)
print(df)

df = df.groupby('商品').sum()
print(df)

df = df.reindex(['バナナ', 'ミカン', 'イチゴ'], fill_value=0)
print(df)

付録B マルチインデックスのテストコード

import pandas as pd

data = [
    {'店舗': 'A店', '商品': 'バナナ', '数量': 1},
    {'店舗': 'C店', '商品': 'ミカン', '数量': 4},
    {'店舗': 'C店', '商品': 'バナナ', '数量': 3},
    {'店舗': 'A店', '商品': 'バナナ', '数量': 1},
    {'店舗': 'C店', '商品': 'ミカン', '数量': 6},
    {'店舗': 'A店', '商品': 'ミカン', '数量': 1},
    {'店舗': 'A店', '商品': 'イチゴ', '数量': 4},
]

df = pd.DataFrame(data)
print(df)

df = df.groupby(['店舗', '商品']).sum()
print(df)

shops = ['A店', 'B店', 'C店']
products = ['バナナ', 'ミカン', 'イチゴ']
df = df.reindex(pd.MultiIndex.from_product([shops, products], names=['店舗', '商品']), fill_value=0)
print(df)