データ分析でこんなことを思ったことはありませんか?
- 複数の条件(例えば「ある月の特定の店舗での売上」)でデータを絞り込みたいけど、どうすれば効率的なんだろう?
- 店舗別、商品別、さらには「月ごとの店舗別」のように、様々な粒度でデータを集計して比較したいけど、もっと簡単な方法はないかな?
この記事では、pandas の階層型インデックス (pandas MultiIndex) を使うことで、驚くほどスマートに解決できます!
pandas の MultiIndex(階層型インデックス)は、DataFrame の行や列に複数のレベルのラベルを付けることができる機能です。これにより、スプレッドシートでいうところの「複数行の見出し」のような構造をデータに持たせることができ、複雑なデータも直感的に、そして効率的に扱えるようになります。
pandas MultiIndex を使うと、次のような分析や操作が驚くほど簡単になります!
- 特定の条件での絞り込み: 「2023 年 1 月の A 店のりんごの売上だけを見たい」「特定の期間の B 店の全商品データを取り出したい」といった、複数の条件を組み合わせたデータ抽出が直感的に行えます。
- 柔軟な集計: 「店舗ごとの合計売上」「月ごとの商品別売上」「商品ごとの全店舗での平均売上」など、様々な粒度での集計が効率的に行え、ビジネスの傾向分析に役立ちます。
- 比較分析: 異なる店舗間や商品間でのパフォーマンス比較、時系列での変化の追跡などが容易になり、データに基づいた意思決定をサポートします。
売上データを例に、pandas MultiIndex の基本的な考え方から、データの準備、選択、集計、そして操作方法までを、具体的なコードを通して学んでいきましょう!
▶️ MultiIndexの公式ドキュメントも参考にしてください:
pandas.MultiIndex Documentation
Pandas MultiIndex (階層型インデックス) の作成方法とサンプルデータ
サンプルデータの作成
import pandas as pd
sales_data = {
'月': ['2023-01', '2023-01', '2023-01', '2023-02', '2023-02', '2023-02', '2023-03', '2023-03','2023-03'],
'店舗': ['A', 'A', 'B', 'B', 'A', 'A', 'B', 'B', 'A'],
'商品': ['りんご', 'バナナ', 'りんご', 'バナナ', 'りんご', 'バナナ', 'りんご', 'バナナ', 'バナナ'],
'売上(円)': [1000, 800, 1200, 900, 1100, 850, 1300, 950, 850],
'売上原価(円)': [700, 500, 700, 600, 700, 400, 700, 550, 500]
}
sales_df = pd.DataFrame(sales_data)
display(sales_df)
| 月 | 店舗 | 商品 | 売上(円) | 売上原価(円) | |
|---|---|---|---|---|---|
| 0 | 2023-01 | A | りんご | 1000 | 700 |
| 1 | 2023-01 | A | バナナ | 800 | 500 |
| 2 | 2023-01 | B | りんご | 1200 | 700 |
| 3 | 2023-02 | B | バナナ | 900 | 600 |
| 4 | 2023-02 | A | りんご | 1100 | 700 |
| 5 | 2023-02 | A | バナナ | 850 | 400 |
| 6 | 2023-03 | B | りんご | 1300 | 700 |
| 7 | 2023-03 | B | バナナ | 950 | 550 |
| 8 | 2023-03 | A | バナナ | 850 | 500 |
行方向への階層型インデックス(pandas MultiIndex)の設定 (set_index)
階層型インデックス、すなわち pandas MultiIndex を設定することで、複数のカテゴリを組み合わせてデータを整理し、後の分析や絞り込みを効率的に行えるようになります。ここでは、’月’と’店舗’を組み合わせて行の pandas MultiIndex とします。
set_index() メソッドに、pandas MultiIndex として設定したい列名のリストを渡すことで、階層型インデックスを持つ DataFrame を作成できます。元の列はインデックスに移動し、データ本体からは削除されます。
以下のコードを実行して、sales_df に pandas MultiIndex を適用した sales_hier_df を見てみましょう。行のインデックス部分が「月」と「店舗」の 2 つのレベルで構成されているのが分かります。
# '月'と'店舗'を階層型インデックスに設定
sales_hier_df = sales_df.set_index(['月', '店舗'])
display(sales_hier_df)
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
Pandas MultiIndex DataFrame からのデータ選択・絞り込み (loc, iloc)
特定の列(カラム)の選択
単一列の選択
pandas MultiIndex を持つ DataFrame から、特定の列を選択するのは通常の DataFrame と同じように行えます。ここでは例として、’商品’列のみを絞り込んで表示してみましょう。
列を一つだけ選択する場合は、DataFrame 名の後に角括弧 [] で列名を指定します。結果は Series として返されます。
以下のコードを実行すると、元の DataFrame と比較して、「商品」列だけが抽出された Series が表示されます。インデックスは pandas MultiIndex (月, 店舗) がそのまま引き継がれていることが分かります。
print('絞り込み前')
display(sales_hier_df)
print('絞り込み後')
# グループの絞り込み
display(sales_hier_df['商品'])
絞り込み前
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
絞り込み後
| 商品 | ||
|---|---|---|
| 月 | 店舗 | |
| 2023-01 | A | りんご |
| A | バナナ | |
| B | りんご | |
| 2023-02 | B | バナナ |
| A | りんご | |
| A | バナナ | |
| 2023-03 | B | りんご |
| B | バナナ | |
| A | バナナ |
なお、
`
sales_hier_df.loc[:,’商品’]
`
にしても同じ結果が返ります。
複数列の選択
複数の列を選択したい場合は、列名のリストを角括弧 [] の中に指定します。結果は DataFrame として返されます。ここでは例として、’商品’列と’売上(円)’列を同時に絞り込んで表示してみましょう。
以下のコードを実行すると、元の DataFrame から「商品」と「売上(円)」の 2 つの列だけが抽出された DataFrame が表示されます。行の階層型インデックス (月, 店舗) は維持されています。
print('絞り込み前')
display(sales_hier_df)
print('絞り込み後')
# グループの絞り込み
display(sales_hier_df[['商品','売上(円)']])
絞り込み前
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
絞り込み後
| 商品 | 売上(円) | ||
|---|---|---|---|
| 月 | 店舗 | ||
| 2023-01 | A | りんご | 1000 |
| A | バナナ | 800 | |
| B | りんご | 1200 | |
| 2023-02 | B | バナナ | 900 |
| A | りんご | 1100 | |
| A | バナナ | 850 | |
| 2023-03 | B | りんご | 1300 |
| B | バナナ | 950 | |
| A | バナナ | 850 |
なお、
`
sales_hier_df.loc[:,[‘商品’,’売上(円)’]]
`
にしても同じ結果が返ります。
特定の行の位置による選択 (.iloc)
pandas MultiIndex を持つ DataFrame から特定の行を選択するにはいくつかの方法があります。ここでは、行の位置(0 から始まる整数)を使って絞り込む方法を見てみましょう。.iloc アクセサを使用します。
スライスを使うことで、特定の範囲の行を選択できます。例えば、最初の 1 行を選択するには iloc[0:1] と指定します。これは、0 番目の行を含み、1 番目の行を含まない範囲を指定していることになります。
位置による選択は、データの物理的な並び順に基づいて行を選択したい場合に便利です。以下のコードでは、最初の 1 行目を取得しています。
print('絞り込み前')
display(sales_hier_df)
print('絞り込み後')
# グループの絞り込み
display(sales_hier_df[0:1])
絞り込み前
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
絞り込み後
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
また、2番目の行のデータを見たいならば、
`
sales_hier_df[2:3]
`
にすると、グループの絞り込みができます。
print('絞り込み前')
display(sales_hier_df)
print('絞り込み後')
# グループの絞り込み
display(sales_hier_df[2:3])
絞り込み前
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
絞り込み後
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | B | りんご | 1200 | 700 |
階層型インデックス(pandas MultiIndex)を使ったラベルによるデータの選択・絞り込み (.loc)
特定の行(インデックスラベル)を選択する際には、.loc アクセサを使用します。pandas MultiIndex では、この .loc を使って柔軟な絞り込みが可能です。
複数レベルのインデックスラベルによる特定の組み合わせの選択
例えば、「2023 年 1 月の A 店」のように、pandas MultiIndex の複数レベル(’月’と’店舗’)の特定の組み合わせに一致する行を絞り込みたい場合、.locにタプルで指定します。
以下のコードを実行すると、('2023-01', 'A') のラベルに一致する行が抽出されます。この場合、2023 年 1 月の A 店のデータがすべて表示されます。
print('絞り込み前')
display(sales_hier_df)
print('特定の月と店舗で絞り込み (例: 2023-01 の A 店)')
display(sales_hier_df.loc[('2023-01', 'A')])
絞り込み前
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
特定の月と店舗で絞り込み (例: 2023-01 の A 店)
/tmp/ipython-input-1872768620.py:5: PerformanceWarning: indexing past lexsort depth may impact performance.
display(sales_hier_df.loc[('2023-01', 'A')])
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 |
複数レベルにまたがる要素の選択 (slice(None)の活用)
特定のレベルの全ての要素を選択しつつ、別のレベルで絞り込みたい場合は、slice(None) を使用します。例えば、全ての月の’A’店のデータを取得するには .loc[(slice(None), 'A'), :] と指定します。slice(None) はそのレベルの全ての要素を選択することを意味します。
以下のコードを実行すると、月のレベルは全て選択し、店舗レベルで ‘A’ を選択したデータが表示されます。
print('絞り込み前')
display(sales_hier_df)
print('特定の店舗で絞り込み (例: A 店の全ての月)')
display(sales_hier_df.loc[(slice(None), 'A'), :])
絞り込み前
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
特定の店舗で絞り込み (例: A 店の全ての月)
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| 2023-02 | A | りんご | 1100 | 700 |
| A | バナナ | 850 | 400 | |
| 2023-03 | A | バナナ | 850 | 500 |
単一レベルのインデックスラベルによる選択
.loc を使用すると、pandas MultiIndex の特定のレベルのラベルを指定して行を選択できます。例えば、最初のレベルである ‘月’ のラベルを指定して、特定の月のデータを全て選択することができます。
また、特定の行(インデックスラベル)と特定の列(カラム名)を組み合わせて絞り込むことも可能です。.loc[行の指定, 列の指定] の形式で記述します。
ここではたとえば、’2023-01’の全ての店舗のデータから、’商品’列だけを見たいとしましょう。行の指定には '2023-01' を、列の指定には '商品' を使います。単一レベルのラベルを指定する場合は、タプルではなく直接ラベル名を指定するか、単一要素のタプル ('2023-01',) とします。
以下のコードを実行すると、2023 年 1 月の全ての店舗のデータの中から、「商品」列だけが抽出されます。結果は Series として表示されます。
print('絞り込み前')
display(sales_hier_df)
print('絞り込み後(2023-01の全店舗の商品列を絞り込み)')
# 行の指定にタプル ('2023-01',) を使い、列の指定に '商品' を使う
display(sales_hier_df.loc[('2023-01',), '商品'])
絞り込み前
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
絞り込み後(2023-01の全店舗の商品列を絞り込み)
| 商品 | |
|---|---|
| 店舗 | |
| A | りんご |
| A | バナナ |
| B | りんご |
.loc使用時のパフォーマンスに関する注意点 (PerformanceWarningとsort_index)
pandas MultiIndex を持つ DataFrame で .loc アクセサを使ってラベルによる絞り込みを行う際に、時折 PerformanceWarning が表示されることがあります。これは、特にインデックスがソートされていない場合に発生し、データの検索効率が低下する可能性があることを示しています。
この PerformanceWarning は、pandas MultiIndex を扱う上でよく遭遇するものであり、インデックスがソートされていない状態で特定のラベルやスライスによる選択を行う際に発生します。
Pandas は、ソートされたインデックスに対しては非常に高速な検索アルゴリズムを使用できますが、ソートされていない場合は、各インデックスエントリを個別にチェックする必要があり、特に大規模なデータでは処理時間が長くなることがあります。
このパフォーマンスの問題を解決し、PerformanceWarning を回避するためには、.loc を使用して絞り込みを行う前に、DataFrame のインデックスを sort_index() メソッドでソートすることが推奨されます。sort_index() は、インデックスの各レベルを順番にソートし、効率的なデータアクセスを可能にします。
したがって、pandas MultiIndex を使った .loc によるデータ選択を行う際は、事前に sort_index() を実行することが、パフォーマンスを最適化し、警告を回避するための標準的なプラクティスとなります。
ソートされたインデックスを持つ DataFrame では、特定のラベルやスライスによる .loc での選択が高速化され、警告も表示されなくなります。
以下のコードでは、まずソート前の DataFrame で特定の絞り込みを行い、PerformanceWarning が表示される場合があることを確認します。次に、インデックスをソートした DataFrame で同じ絞り込みを行い、警告が表示されないこと、および結果が変わらないことを確認します。
print('ソート前')
display(sales_hier_df)
# インデックスをソート
sales_hier_sorted_df = sales_hier_df.sort_index()
print('インデックスをソート後')
display(sales_hier_sorted_df)
print('ソート済みDataFrameで特定の月と店舗で絞り込み (例: 2023-01 の A 店)')
display(sales_hier_sorted_df.loc[('2023-01', 'A')])
print('ソート済みDataFrameで特定の店舗で絞り込み (例: A 店の全ての月)')
display(sales_hier_sorted_df.loc[(slice(None), 'A'), :])
ソート前
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
インデックスをソート後
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | A | りんご | 1100 | 700 |
| A | バナナ | 850 | 400 | |
| B | バナナ | 900 | 600 | |
| 2023-03 | A | バナナ | 850 | 500 |
| B | りんご | 1300 | 700 | |
| B | バナナ | 950 | 550 |
ソート済みDataFrameで特定の月と店舗で絞り込み (例: 2023-01 の A 店)
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 |
ソート済みDataFrameで特定の店舗で絞り込み (例: A 店の全ての月)
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| 2023-02 | A | りんご | 1100 | 700 |
| A | バナナ | 850 | 400 | |
| 2023-03 | A | バナナ | 850 | 500 |
Pandas MultiIndex を使った高度なデータ抽出・絞り込み
pandas MultiIndex の強力な点は、単一のレベルだけでなく、複数のレベルを組み合わせてデータを柔軟に絞り込めることです。.locを使用することで、インデックスの特定の組み合わせや、特定のレベルのすべての要素を選択しながら別のレベルで絞り込むといった、より複雑な条件でのデータ抽出が容易になります。
特定の月と店舗のデータを絞り込む
例えば、「2023 年 2 月の A 店」のように、pandas MultiIndex の複数レベル(’月’と’店舗’)の特定の組み合わせに一致する行を絞り込みたい場合、.locにタプルで指定します。
以下のコードを実行すると、2023 年 2 月の A 店のデータのみが抽出されます。
print('階層型インデックスDataFrame')
display(sales_hier_df)
print("特定の月 ('2023-02') かつ特定の店舗 ('A') のデータを絞り込み:")
display(sales_hier_df.loc[('2023-02', 'A')])
print("特定の月 ('2023-03') における特定の商品のデータ ('バナナ') を全ての店舗で絞り込み:")
display(sales_hier_df.loc[(slice(None), slice(None)), :][sales_hier_df['商品'] == 'バナナ'].loc['2023-03'])
階層型インデックスDataFrame
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
特定の月 ('2023-02') かつ特定の店舗 ('A') のデータを絞り込み:
/tmp/ipython-input-3881532412.py:5: PerformanceWarning: indexing past lexsort depth may impact performance.
display(sales_hier_df.loc[('2023-02', 'A')])
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-02 | A | りんご | 1100 | 700 |
| A | バナナ | 850 | 400 |
特定の月 ('2023-03') における特定の商品のデータ ('バナナ') を全ての店舗で絞り込み:
| 商品 | 売上(円) | 売上原価(円) | |
|---|---|---|---|
| 店舗 | |||
| B | バナナ | 950 | 550 |
| A | バナナ | 850 | 500 |
Pandas MultiIndex でのデータ集計 (groupby level)
pandas MultiIndex を設定することで、特定のインデックスレベルを基準にしてデータを簡単に集計できます。groupby() メソッドと level 引数を使うことで、指定したレベルでデータをグループ化し、合計や平均などの集計関数を適用できます。
以下の例では、様々な pandas MultiIndex レベルを軸に売上と売上原価の合計を計算しています。集計対象から文字列型の’商品’列を除外するために、数値列のみを選択して集計しています。
単一インデックスレベルでの集計 (例: 店舗別)
‘店舗’を軸に売上と売上原価の合計を計算します。groupby(level='店舗') で店舗ごとにデータをグループ化し、.sum() で各グループの合計値を計算しています。集計結果からは、例えば「A 店の総売上は〇〇円、B 店の総売上は△△円である」といった情報を読み取ることができます。
print('合計前')
display(sales_hier_df)
print('店舗ごとの合計')
display(sales_hier_df.groupby(level= '店舗', axis = 0).sum())
print('店舗ごとの合計(商品を除く)')
display(sales_hier_df[['売上(円)','売上原価(円)']].groupby(level= '店舗', axis = 0).sum())
合計前
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
店舗ごとの合計
/tmp/ipython-input-3547325012.py:5: FutureWarning: The 'axis' keyword in DataFrame.groupby is deprecated and will be removed in a future version. display(sales_hier_df.groupby(level= '店舗', axis = 0).sum())
| 商品 | 売上(円) | 売上原価(円) | |
|---|---|---|---|
| 店舗 | |||
| A | りんごバナナりんごバナナバナナ | 4600 | 2800 |
| B | りんごバナナりんごバナナ | 4350 | 2550 |
店舗ごとの合計(商品を除く)
/tmp/ipython-input-3547325012.py:8: FutureWarning: The 'axis' keyword in DataFrame.groupby is deprecated and will be removed in a future version. display(sales_hier_df[['売上(円)','売上原価(円)']].groupby(level= '店舗', axis = 0).sum())
| 売上(円) | 売上原価(円) | |
|---|---|---|
| 店舗 | ||
| A | 4600 | 2800 |
| B | 4350 | 2550 |
【補足】groupbyのaxis引数に関する警告について
上記のコードを実行すると、FutureWarning: The ‘axis’ keyword in DataFrame.groupby is deprecated and will be removed in a future version. という警告が表示されることがあります。これは、将来的にgroupbyメソッドでaxisを使う方法が廃止される可能性があることを示しています。
現在(pandas の特定のバージョン)ではまだ動作しますが、将来的な互換性を考慮すると、axis=0の場合は引数を省略するか、列方向の集計(axis=1)の場合は groupby(level=’color’, axis=1) の代わりに、データの持ち方によっては先にデータを転置(.T)してからgroupbyを行うなどの代替手段を検討することもできます。
なお、groupbyメソッドでaxis=0を指定することは、デフォルトの動作である「行方向(インデックス)でのグループ化」を明示していることになります。このため、通常はaxis=0を省略しても同じ結果が得られます。警告を避けるためにも、行方向のグループ化の場合はaxis引数を省略することが推奨されます。
単一インデックスレベルでの集計 (例: 月別)
次に、’月’を軸に売上と売上原価の合計を計算してみましょう。groupby(level='月') を使用します。これにより、月ごとの全体の売上動向を把握できます。
print('合計前')
display(sales_hier_df)
print('月ごとの合計')
# 警告を避けるため、axis=0 は省略
display(sales_hier_df[['売上(円)','売上原価(円)']].groupby(level= ['月']).sum())
合計前
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
月ごとの合計
| 売上(円) | 売上原価(円) | |
|---|---|---|
| 月 | ||
| 2023-01 | 3000 | 1900 |
| 2023-02 | 2850 | 1700 |
| 2023-03 | 3100 | 1750 |
複数インデックスレベルでの集計 (例: 月と店舗別)
さらに、’月’と’店舗’の両方を軸にして集計することも可能です。groupby(level=['月', '店舗']) のようにレベル名のリストを指定します。これにより、各月における各店舗の合計売上を詳細に分析できます。
print('合計前')
display(sales_hier_df)
print('月と店舗ごとの合計')
# 警告を避けるため、axis=0 は省略
display(sales_hier_df[['売上(円)','売上原価(円)']].groupby(level= ['月', '店舗']).sum())
合計前
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
月と店舗ごとの合計
| 売上(円) | 売上原価(円) | ||
|---|---|---|---|
| 月 | 店舗 | ||
| 2023-01 | A | 1800 | 1200 |
| B | 1200 | 700 | |
| 2023-02 | A | 1950 | 1100 |
| B | 900 | 600 | |
| 2023-03 | A | 850 | 500 |
| B | 2250 | 1250 |
MultiIndex のリセットと削除
pandas MultiIndex を持つ DataFrame から特定の行を削除したい場合は、drop() メソッドを使います。drop() に削除したいインデックスのラベルを指定します。階層型インデックスの場合は、削除したいレベルのラベルを指定します。
次の例では、月レベルのインデックスである’2023-03’に対応する全ての行を削除しています。これにより、特定の期間のデータを分析から除外したい場合などに便利です。
print('削除前')
display(sales_hier_df)
print('2023-03行を削除後')
display(sales_hier_df.drop(['2023-03']))
削除前
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
2023-03行を削除後
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 |
階層型インデックス(pandas MultiIndex)のリセット (reset_index)
pandas MultiIndex を設定した DataFrame を、元のフラットなインデックスに戻したい場合があります。例えば、階層型インデックスで行った集計結果を、元の列として扱いたい場合などです。
このような場合は、reset_index() メソッドを使用します。このメソッドを呼び出すと、階層型インデックスの各レベルが通常の列として DataFrame に追加され、デフォルトの整数インデックスが新たに設定されます。
reset_index() は新しい DataFrame を返すため、元の DataFrame を変更したい場合は、結果を同じ変数に代入するか、inplace=True 引数を指定します(ただし、inplace=True の使用は推奨されない場合があります)。
ここでは、sales_hier_df に設定した pandas MultiIndex をリセットし、’月’と’店舗’を再び列に戻してみましょう。元の sales_df と同じ形式に戻ることが分かります。
print('リセット前')
display(sales_hier_df)
print('インデックスをリセット後')
sales_reset_df = sales_hier_df.reset_index()
display(sales_reset_df)
リセット前
| 商品 | 売上(円) | 売上原価(円) | ||
|---|---|---|---|---|
| 月 | 店舗 | |||
| 2023-01 | A | りんご | 1000 | 700 |
| A | バナナ | 800 | 500 | |
| B | りんご | 1200 | 700 | |
| 2023-02 | B | バナナ | 900 | 600 |
| A | りんご | 1100 | 700 | |
| A | バナナ | 850 | 400 | |
| 2023-03 | B | りんご | 1300 | 700 |
| B | バナナ | 950 | 550 | |
| A | バナナ | 850 | 500 |
インデックスをリセット後
| 月 | 店舗 | 商品 | 売上(円) | 売上原価(円) | |
|---|---|---|---|---|---|
| 0 | 2023-01 | A | りんご | 1000 | 700 |
| 1 | 2023-01 | A | バナナ | 800 | 500 |
| 2 | 2023-01 | B | りんご | 1200 | 700 |
| 3 | 2023-02 | B | バナナ | 900 | 600 |
| 4 | 2023-02 | A | りんご | 1100 | 700 |
| 5 | 2023-02 | A | バナナ | 850 | 400 |
| 6 | 2023-03 | B | りんご | 1300 | 700 |
| 7 | 2023-03 | B | バナナ | 950 | 550 |
| 8 | 2023-03 | A | バナナ | 850 | 500 |
まとめ: Pandas MultiIndex を使いこなしてデータ分析を効率化
この 記事 では、pandas の階層型インデックス(pandas MultiIndex)の基本的な使い方から、データの絞り込み、インデックスレベルを活かした集計方法までを、実際のデータとコードで具体的に学びました。
pandas MultiIndex を効果的に使うことで、複数のカテゴリを持つ複雑なデータも整理しやすくなり、特定の条件でのデータ抽出や、各階層ごとの集計が効率的に行えるようになります。 例えば、「特定の月と店舗の売上を知りたい」「月ごとの売上合計を把握したい」といった具体的な分析ニーズに対し、pandas MultiIndex は強力な解決策を提供します。これにより、より深いデータ分析が可能になります。
学んだこと:
– set_index() による pandas MultiIndex の作成
– pandas MultiIndex を持つ DataFrame から、[] や .loc を使った列の選択
– 位置による行の絞り込み(.ilocについても触れました)
– .loc を使ったインデックスラベルによる行の絞り込み(単一・複数レベル、slice(None)の活用)
– .loc 使用時の PerformanceWarning の原因と、sort_index() によるパフォーマンス改善策
– groupby(level=...) を使った様々な pandas MultiIndex レベルでの集計(店舗別、月別、月・店舗別)
– drop() を使ったインデックス要素の削除
– reset_index() を使って pandas MultiIndex を通常の列に戻す方法
ぜひ、あなたのデータでも pandas MultiIndex を活用して、データ分析を効率化してみてください。この Notebook で紹介した様々なテクニックが、日々のデータ操作や分析の強力な味方となるはずです。
次のステップ
- 今回使用したサンプルデータを変更して、色々な絞り込みや集計を試してみましょう。
- あなたの持っている実際のデータに pandas MultiIndex を適用して、どのように分析が効率化されるか体験してみましょう。
groupby()の他の集計関数(.mean(),.count(),.max(),.min()など)を使って、様々な観点からデータを分析してみましょう。
MultiIndexとは何ですか?
pandasの階層型インデックス機能で、行や列に複数の階層を持たせることができます。
MultiIndexを作成する方法は?
pd.MultiIndex.from_tuples() や set_index() で階層を定義できます。
stack/unstackの役割は?
行と列の階層を入れ替える操作で、MultiIndexを利用した集計に便利です。
コメント