データを集計する際は、データ構造が縦持ちか横持ちかで処理のしやすさが大きく変わってきます。
この記事では、データの縦持ち・横持ちについて、次のような内容を紹介してきます。
- そもそも縦持ち・横持ちの違いやメリット・デメリットは?
- 具体的に縦持ち・横持ちってどうやって使えばいいの?
- pythonのデータ分析ライブラリ
pandas
で縦持ち・横持ちを変換する方法は?
縦持ち・横持ちを意識して扱えれば、データ集計が一気に楽になると思います!
縦持ち・横持ちのデータとは?
そもそもデータの「縦持ち」や「横持ち」とは、何のことをいっているのでしょうか?
大まかなイメージは、次の通りです。
- 「縦持ち」、「横持ち」は、データ構造の分類の方法
- 縦持ち:ラベルが縦に並んだ、縦に長いデータ
- 横持ち:ラベルが横にも並んだ、横に長いデータ
サンプルデータで、縦持ち、横持ちのデータを比較して見ましょう
横持ちデータはExcelの集計表などでよく見る形ですね。
一方、縦持ちデータは縦に長くなっています。
縦持ち・横持ちデータのメリット・デメリット
次に縦持ち、横持ちデータのメリット・デメリットを整理してみましょう。
データ構造 | メリット | デメリット |
---|---|---|
横持ちデータ | ● 人が見やすい ● 列同士の比較ができる |
● データの追加がしにくい ● 欠損値がでやすい |
縦持ちデータ | ● プログラムで使いやすい(集計しやすい) ● データの追加がしやすい |
● 人が見て見にくい ● 行数が多くなりがち |
特にデータ分析で重要な違いは、データの追加のしやすさです。
サンプルデータに、「2023年の東京」のデータを追加してみます。
縦持ちではデータ構造は変化しませんが、横持ではデータ構造が変化してしまいます。
データ管理の観点では、データを追加してもデータ構造が変化しない縦持ちの方が便利です。
各持ち方のメリット、デメリットを参考にしっかり使い分けましょう!
pandasで縦持ち・横持ちの変換
Pythonのデータ分析ライブラリpandas
では、どのように縦持ち・横持ちを変換するのでしょうか?
実はpandas
には、縦持ち・横持ちを変換する便利なメソッドが用意されています。
- 横持ち→縦持ち:
.stack()
メソッド - 縦持ち→横持ち:
.unstack()
メソッド
stackは、「積み上げる」という意味なので、横から縦に積み上げると覚えれば、覚えやすいですね。
逆にunstackは、積み上げたものをよこに並べると覚えれば、縦→横の変換だと覚えやすいです。
以下では、各メソッドの使い方を詳しく解説していきます。
横持ち→縦持ち|stack()
横持ちのデータを縦持ちに変換する場合は、.stack()
メソッドを使用します。
.stack()
メソッドは、引数で次の内容を設定することが可能です。
設定内容 | 引数 | コメント |
---|---|---|
欠損値の除去 | dropna=True or False |
デフォルト:True |
縦にするカラムのレベル | level = カラムレベル |
デフォルト:-1 (一番内側のカラム)※マルチインデックスの場合のみ指定 |
マルチインデックスでは、level
の指定が必要な場合もありますが、通常はデフォルト設定で問題ないことが多いです。
まずは、マルチインデックスでない次のサンプルデータで横持ち→縦持ちの変換をしてみます。
import pandas as pd
df = pd.DataFrame([[100, 90, 120],
[80, 70, 80],
[50, 60, 80],
[70, 80, None]],
index=pd.Index(["Tokyo", "Chiba", "Saitama", "Kanagawa"], name="Shope"),
columns=pd.Index(["2020", "2021", "2022"], name="Year"))
print(df)
# Year 2020 2021 2022
# Shope
# Tokyo 100 90 120.0
# Chiba 80 70 80.0
# Saitama 50 60 80.0
# Kanagawa 70 80 NaN # <-あえて欠損値を準備
このデータを縦持ちに変換してみましょう。
s = df.stack()
print(s)
# Shope Year
# Tokyo 2020 100.0
# 2021 90.0
# 2022 120.0
# Chiba 2020 80.0
# 2021 70.0
# 2022 80.0
# Saitama 2020 50.0
# 2021 60.0
# 2022 80.0
# Kanagawa 2020 70.0
# 2021 80.0 # 欠損値は除去された。
# dtype: float64
無事、縦持ちのデータが生成されました。
変換後の列数が一つの場合は、Series
が返されます。
デフォルトでは、dropna=True
なので欠損値は除去されています。
また、Series
の名前が設定されていないので、必要に応じて.rename()
と組み合わせましょう。
s = df.stack().rename("Sales")
print(s)
# Shope Year
# Tokyo 2020 100.0
# 2021 90.0
# 2022 120.0
# Chiba 2020 80.0
# 2021 70.0
# 2022 80.0
# Saitama 2020 50.0
# 2021 60.0
# 2022 80.0
# Kanagawa 2020 70.0
# 2021 80.0
# Name: Sales, dtype: float64 # <-Nameが設定されている
マルチインデックスの場合
カラムがマルチインデックスの場合は、level
引数で縦持ちにするレベルを単独または複数指定できます。
level = カラムレベル番号 or カラム名
- カラムレベル → 外側から内側に向かって、
0, 1,…
の連番 - 一番内側は、
-1
でも指定可能
- カラムレベル → 外側から内側に向かって、
level = [カラムレベル番号 or カラム名のリスト]
カラムレベルのイメージ図は次の通りです。
次のマルチインデックスのサンプルデータで.stack()
の挙動を確認してみましょう。
multi_col = pd.MultiIndex.from_product([["April", "May", "June"], ["Apple", "Banana"]],
names=["Month", "Product"])
index = pd.Index(["Tokyo", "Chiba", "Saitama"], name="Shop")
df = pd.DataFrame([[10, 10, 20, 20, 40, 30],
[10, 20, 40, 30, 20, 40],
[40, 10, 20, 40, 30, 10]],
columns=multi_col, index=index)
print(df)
# Month April May June
# Product Apple Banana Apple Banana Apple Banana
# Shop
# Tokyo 10 10 20 20 40 30
# Chiba 10 20 40 30 20 40
# Saitama 40 10 20 40 30 10
まずはデフォルト設定(level=-1
)で、縦持ちにしてみます。
df_stacked = df.stack()
print(df_stacked)
# Month April June May
# Shop Product
# Tokyo Apple 10 40 20
# Banana 10 30 20
# Chiba Apple 10 20 40
# Banana 20 40 30
# Saitama Apple 40 30 20
# Banana 10 10 40
一番内側の"Product"
が、縦方向に変換されました。
変換後も複数列が残っているため、出力はSereis
でなく、DataFrame
です。
次にlevel=0
を指定して、"Month"
だけを縦方向に変換してみましょう、
df_stacked = df.stack(level=0)
print(df_stacked)
# Product Apple Banana
# Shop Month
# Tokyo April 10 10
# June 40 30
# May 20 20
# Chiba April 10 20
# June 20 40
# May 40 30
# Saitama April 40 10
# June 30 10
# May 20 40
今度は、"Product"
カラムが残されて、"Month"
カラムが縦に移動しました。
最後にlevel=[0,1]
で全てのカラムを縦に移動してみましょう。
s_stacked = df.stack(level=[0,1])
print(s_stacked)
# Shop Month Product
# Tokyo April Apple 10
# Banana 10
# June Apple 40
# Banana 30
# May Apple 20
# Banana 20
# Chiba April Apple 10
# Banana 20
# June Apple 20
# Banana 40
# May Apple 40
# Banana 30
# Saitama April Apple 40
# Banana 10
# June Apple 30
# Banana 10
# May Apple 20
# Banana 40
カラムが全て縦方向に移動しましたね。
データ列が一つだけなので、出力もSeries
になっています。
縦持ち→横持ち|unstack()
縦持ちのデータを横持ちに変換する場合は、.unstack()
メソッドを使用します。
.unstack()
メソッドは、引数で次の内容を設定することが可能です。
設定内容 | 引数 | コメント |
---|---|---|
欠損値の除去 | fill_value=数値 or 文字列 |
欠損値を埋める値 ※指定しないと欠損値のまま |
横にするインデックスのレベル | level = インデックスレベル |
デフォルト:-1 (一番内側のインデックスカラム) |
シングルインデックスでは横に移動する列がないので、縦→横の変換はマルチインデックスでのみ可能です。
.stack()
の解説で生成した、次のマルチインデックスのデータを使用して挙動を確認してみましょう。
s_stacked
# Shop Month Product
# Tokyo April Apple 10
# Banana 10
# June Apple 40
# Banana 30
# May Apple 20
# Banana 20
# Chiba April Apple 10
# Banana 20
# June Apple 20
# Banana 40
# May Apple 40
# Banana 30
# Saitama April Apple 40
# Banana 10
# June Apple 30
# Banana 10
# May Apple 20
# Banana 40
まずはデフォルト設定(level=-1
)で、縦持ちにしてみます。
df_unstacked = s_stacked.unstack()
print(df_unstacked)
# Product Apple Banana
# Shop Month
# Chiba April 10 20
# June 20 40
# May 40 30
# Saitama April 40 10
# June 30 10
# May 20 40
# Tokyo April 10 10
# June 40 30
# May 20 20
一番内側の"Product"
が、横方向に変換されました。
縦持ちから横持ちにすると、データが見やすいことがよくわかります。
次にlevel=1
を指定して、"Month"
だけを横方向に変換してみましょう、
df_unstacked = s_stacked.unstack(level=1)
print(df_unstacked)
# Month April June May
# Shop Product
# Chiba Apple 10 20 40
# Banana 20 40 30
# Saitama Apple 40 30 20
# Banana 10 10 40
# Tokyo Apple 10 40 20
# Banana 10 30 20
今度は、"Month"
が横に移動しました。
月ごとの集計を見たい場合には、この横持ちデータも見やすいですね。
最後にlevel=[1, 2]
で、"Month"
と"Product"
を横に移動してみます。
df_unstacked = s_stacked.unstack(level=[1,2])
print(df_unstacked)
# Month April June May
# Product Apple Banana Apple Banana Apple Banana
# Shop
# Chiba 10 20 20 40 40 30
# Saitama 40 10 30 10 20 40
# Tokyo 10 10 40 30 20 20
店舗ごとの集計を見たい場合には、この横持ちデータが良さそうです。
このように.unstack()
を使用すると、縦持ちから横持ちにして、人が見やすい形式に変換できます。
最後に、縦持ち・横持ちを使用したデータ集計例を紹介します。
縦持ち・横持ち変換を使用したデータ集計方法
縦持ち・横持ちの持ち方を変えて、データ集計する方法を紹介します。
-
データ処理しやすいように、横持ち→縦持ちに変換
-
.groupby()
でデータを集計 -
結果を見やすくするため、縦持ち→横持ちに変換
データが縦持ちになっていると、カテゴリごとのデータ集計がとても簡単だとわかると思います。
次のサンプルデータで、縦持ち・横持ち変換例を見てみましょう
import pandas as pd
multi_col = pd.MultiIndex.from_product([["April", "May", "June"], ["Apple", "Banana"]],
names=["Month", "Product"])
index = pd.Index(["Tokyo", "Chiba", "Saitama"], name="Shop")
df = pd.DataFrame([[10, 10, 20, 20, 40, 30],
[10, 20, 40, 30, 20, 40],
[40, 10, 20, 40, 30, 10]],
columns=multi_col, index=index)
print(df)
# Month April May June
# Product Apple Banana Apple Banana Apple Banana
# Shop
# Tokyo 10 10 20 20 40 30
# Chiba 10 20 40 30 20 40
# Saitama 40 10 20 40 30 10
横持ちデータのままだと、"Apple"
や"Banana"
の平均や合計を集計するのが少し大変です。
まずは、集計しやすいように、データを縦持ちに変換します。
s = df.stack([0,1])
print(s)
# Shop Month Product
# Tokyo April Apple 10
# Banana 10
# June Apple 40
# Banana 30
# May Apple 20
# Banana 20
# Chiba April Apple 10
# Banana 20
# June Apple 20
# Banana 40
# May Apple 40
# Banana 30
# Saitama April Apple 40
# Banana 10
# June Apple 30
# Banana 10
# May Apple 20
# Banana 40
各月の果物の合計を.groupby()
で集計してみましょう。
.groupby()
は、複数のインデックス列やデータ列を指定できるので縦持ちデータと非常に相性がいいです。
s_agg = s.groupby(["Month", "Product"]).sum()
print(s_agg)
# Month Product
# April Apple 60
# Banana 40
# June Apple 90
# Banana 80
# May Apple 80
# Banana 90
# dtype: int64
簡単に集計できましたが、縦持ちのままでは結果が見にくいですね。
最後に横持ちデータに変換して、見やすくしておきましょう。
df_res = s_agg.unstack(level=0)
print(df_res)
# Month April June May
# Product
# Apple 60 90 80
# Banana 40 80 90
今回の例では、次のようにデータ構造を順次変化させています。
- 横持ち→縦持ち→(集計)→横持ち
- データ集計のときは縦持ち
- 結果を確認するときは横持ち
目的に応じて適切なデータ構造に変化させると、データの集計がとてもスムーズです。
縦持ちデータの集計で使用した.groupby()
については、次の記事で詳しく解説しています。
縦持ちデータの集計に欠かせないメソッドの一つなので、是非チェックしてください。
オススメ|pandasとデータ分析の勉強方法
今回は、縦持ち・横持ちデータの特徴、pandas
での変換方法について解説しました。
pandas
は便利すぎて操作方法がわかりにくいことがよくあります…。
結局はコツコツ学ぶのが、pandas
マスターの近道ですよね!
データ分析初心者の方にはこちらの記事もおススメです。
私がこれまで勉強してきた経験をもとに考えたおススメの勉強本の紹介記事です。
何から始めて、どうやってレベルアップしていけばいいのか、初心者の方にぜひおススメしたい本を紹介しました。
オススメのpandas
本については、次の記事で紹介しています。