0Pricing
Pandas & NumPy Academy · レッスン

MultiIndexでのstackとunstack

stack()で最も内側の列レベルを行インデックスに移し、unstack()で元に戻します。

「MultiIndexでのstackとunstack」はCoddyKit上の無料Pandas & NumPy Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPandas & NumPy Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Pandas & NumPy Academyコースには全4レッスンが含まれています。

stackとunstackの概要

stack()とunstack()は、データを行インデックスと列インデックスの間で移動させるPandasの形状変換ツールです。どちらかの軸にMultiIndexを持つDataFrameを扱う場合に特に便利です。stack()は列の最内側のレベルを行の最内側のレベルに移し、unstack()はその逆を行います。

stack(): 列から行へ

DataFrame.stack()は、列ラベルの最内側のレベルを行インデックスの最内側のレベルに展開し、より縦長で列数の少ない結果を生成します。元のDataFrameの列が単純な列(MultiIndexではない列)であれば、結果はMultiIndexを持つSeriesになります。列が複数のレベルを持つ場合、stack()によって列のレベルが1つ減ります。

import pandas as pd

df = pd.DataFrame({
    'Math': [85, 90, 78],
    'Science': [92, 88, 95]
}, index=['Alice', 'Bob', 'Carol'])

print(df)
#        Math  Science
# Alice    85       92
# Bob      90       88
# Carol    78       95

stacked = df.stack()
print(stacked)
# Alice    Math       85
#          Science    92
# Bob      Math       90
#          Science    88
# Carol    Math       78
#          Science    95
# dtype: int64

unstack(): 行から列へ

Series.unstack()(またはDataFrame.unstack())はstack()の逆の操作です。行インデックスの最内側のレベルを新しい列ラベルに移し、より横長の結果を生成します。機能的にはpivot()と似ていますが、列の値ではなくインデックスを直接操作します。

stacked = df.stack()
print(type(stacked))  # Series with MultiIndex

# Restore the original wide format
df_restored = stacked.unstack()
print(df_restored)
#        Math  Science
# Alice    85       92
# Bob      90       88
# Carol    78       95

# Identical to the original df!

stackするレベルの選択

列にMultiIndexを持つDataFrameでは、stack(level)を使って移動するレベルを選択できます。レベル番号(最外側は0、最内側は-1で、デフォルト値です)またはレベル名を渡します。同様に、unstack(level)では、列に移動する行インデックスのレベルを選択できます。この細かな制御は、複雑な階層構造のデータを扱ううえで不可欠です。

# MultiIndex columns
arrays = [['Math', 'Math', 'Science', 'Science'],
          ['Q1', 'Q2', 'Q1', 'Q2']]
multi_cols = pd.MultiIndex.from_arrays(arrays, names=['subject', 'quarter'])

df_multi = pd.DataFrame([[85, 90, 92, 88], [78, 80, 95, 91]],
                        index=['Alice', 'Bob'], columns=multi_cols)

# Stack the 'quarter' level (innermost, level=-1)
print(df_multi.stack(level='quarter').head())

特定の行レベルに対するunstack()

DataFrameの行にMultiIndexがある場合(複数の列に対してgroupby()を実行した後によく見られます)、特定の行レベルにunstack()を適用して、列方向に展開できます。これは、pivot_table()を明示的に呼び出さずにクロス集計形式のサマリーを作成する、非常に一般的なパターンです。

# GroupBy produces MultiIndex rows
df2 = pd.DataFrame({
    'region': ['East', 'East', 'West', 'West'],
    'product': ['A', 'B', 'A', 'B'],
    'sales': [100, 150, 120, 200]
})

# MultiIndex result from groupby
multi = df2.groupby(['region', 'product'])['sales'].sum()
print(multi)

# Unstack the product level into columns
wide = multi.unstack('product')
print(wide)
# product    A    B
# region
# East     100  150
# West     120  200

stack/unstackで欠損値を扱う

unstack()を呼び出したとき、すべての列レベルに対して行インデックスの組み合わせが存在するとは限らない場合、Pandasは欠損セルをNaNで埋めます。一方、stack()はデフォルトで、すべての値がNaNである行を削除します。これはdropnaパラメータで制御できます。stack(dropna=False)を渡すと、NaNの行を保持できます。

# Incomplete data: West has no product B
df3 = df2[df2['product'] != 'B'].copy()
multi3 = df3.groupby(['region', 'product'])['sales'].sum()

wide3 = multi3.unstack('product', fill_value=0)
print(wide3)
# product    A    B
# region
# East     100    0  <- B filled with 0 instead of NaN
# West     120    0

stack()して行単位で計算する

強力なパターンの1つは、ワイド形式のDataFrameにstack()を適用して列を行に変換し、フィルタリングやgroupbyなどの行単位の操作を行った後、unstack()でワイド形式に戻す方法です。これにより、列ごとのループを書く必要がなくなり、コードをベクトル化された読みやすい形に保てます。すべての列に同じ変換を同時に適用する場合に特に便利です。

# Normalise each column by its column mean using stack/compute/unstack
normalised = (
    df.stack()
      .groupby(level=1)
      .transform(lambda s: (s - s.mean()) / s.std())
      .unstack()
)
print(normalised.round(2))
#        Math  Science
# Alice  0.0     0.39
# Bob    1.13   -1.09
# Carol -1.13    0.71

プロットでのstack()

melt()と同様に、stack()はデータをロング形式に変換します。これはSeabornや多くのMatplotlibヘルパーが想定する形式です。主な違いは、stack()が列インデックスを操作してMultiIndex構造を保持するのに対し、melt()はフラットなロング形式のDataFrameを生成する点です。どちらを使っても、同様のプロット手順につなげられます。

# Prepare data for line plot using stack
long = df.stack().reset_index()
long.columns = ['student', 'subject', 'score']
print(long)
#   student  subject  score
# 0   Alice     Math     85
# 1   Alice  Science     92
# ...

# Ready for: sns.barplot(data=long, x='student', y='score', hue='subject')

インデックスの順序変更に使うswaplevel()

stack後は、行の最内側のレベルが元の列名になります。場合によっては、外側のレベルを変数名、内側のレベルを元の行インデックスにしたいことがあります。MultiIndexに対してswaplevel()を使うと2つのレベルの順序を入れ替えられます。その後、sort_index()を使って整った順序に戻します。

stacked = df.stack()  # MultiIndex: (student, subject)
swapped = stacked.swaplevel()  # MultiIndex: (subject, student)
swapped = swapped.sort_index()
print(swapped)
# subject  student
# Math     Alice      85
#          Bob        90
#          Carol      78
# Science  Alice      92
#          Bob        88
#          Carol      95

stack、melt、pivotの使い分け

MultiIndex列を持つDataFrameを扱い、列のレベルを1つ減らしたい場合はstack()を選びます。フラットなDataFrameをワイド形式からロング形式に変換し、どの列を行にするかを制御したい場合はmelt()を選びます。ロング形式からワイド形式に戻す場合はpivot()/pivot_table()を選びます。この3つのツールで、Pandasにおけるワイド形式とロング形式の変換をすべて扱えます。

# Decision guide (comment, not executable standalone):
# MultiIndex columns -> want fewer levels: use stack()
# Flat wide -> need long format for plotting/ML: use melt()
# Long -> need wide summary table: use pivot_table()
# Wide -> back to long: use melt() or stack()

# Example: stack when you have pivot_table output (MultiIndex cols)
tbl = df2.groupby(['region', 'product'])['sales'].sum().unstack()
long_again = tbl.stack().rename('sales').reset_index()
print(long_again)

実践的なまとめ:形状変換チートシート

簡潔なイメージとして、次のように考えてください。stack() — 列が行にまとめられ、DataFrameはより縦長になります。unstack() — 行が列に展開され、DataFrameはより横長になります。melt() — 名前付きの列がキーと値のペア(2つの新しい列)にまとめられます。pivot_table() — キーと値の列が複数の列に展開されます。これら4つはすべて可逆的で、どちらの方向に変換するかは、対象の分析でワイド形式とロング形式のどちらが適しているかを判断するだけです。

# stack/unstack cycle
df_wide = df.copy()             # wide format
df_long = df_wide.stack()       # long via stack
df_wide2 = df_long.unstack()    # back to wide

# melt/pivot cycle
df_melted = df_wide.melt(id_vars=[])    # wide -> long
# df_pivoted = df_melted.pivot(...)     # long -> wide

print('Original shape:  ', df_wide.shape)
print('After stack:     ', df_long.shape)
print('After unstack:   ', df_wide2.shape)

理解度チェック

このレッスンで学んだ、MultiIndexを使ったstackとunstackについて理解度を確認しましょう。

レッスンのまとめ

このレッスンでは、stack()が列ラベルを行インデックスに移動して、より縦長で列数の少ない結果を生成すること、unstack()がその逆に行インデックスのレベルを列に移動すること、どちらもgroupby操作で生成されるMultiIndex構造に対応していること、そしてswaplevel()でインデックスのレベルの順序を変更できることを学びました。次は、カテゴリ列間の度数表をすばやく作成するpd.crosstab()について学びます。

よくある質問

「MultiIndexでのstackとunstack」レッスンは無料ですか?

はい。「MultiIndexでのstackとunstack」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Pandas & NumPy Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Pandas & NumPy Academyコースには全4レッスンが含まれています。

「MultiIndexでのstackとunstack」で何を学びますか?

stack()で最も内側の列レベルを行インデックスに移し、unstack()で元に戻します。 ブラウザで直接実行するハンズオンコードでPandas & NumPy Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Pandas & NumPy Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのPandas & NumPy Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「MultiIndexでのstackとunstack」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このPandas & NumPy Academyレッスンでコードを書いて実行できますか?

はい。すべてのPandas & NumPy Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. pivot_table:クロス集計
  2. melt:横持ち形式から縦持ち形式へ
  3. MultiIndexでのstackとunstack
  4. 度数表に使うcrosstab
← Pandas & NumPy Academyに戻る