機械学習データ前処理:分析しやすい形へ!データを「縦持ち」「横持ち」に変換する方法【Pandas入門】
はじめに:データ形式の重要性と「縦持ち」「横持ち」とは
機械学習モデルの構築において、データ前処理はモデルの性能を大きく左右する重要なステップです。データ前処理には様々な作業がありますが、その中でもデータの「形」を整えることは分析やモデリングのために不可欠な作業の一つです。
例えば、同じ情報を含んだデータでも、その並べ方によって分析ツールが扱いやすかったり、特定の種類のモデルが処理しやすかったりします。この記事では、データ分析で頻繁に登場する「縦持ち形式(Long Format)」と「横持ち形式(Wide Format)」というデータの並べ方と、それぞれへの変換方法について解説します。
プログラミング経験がない方でも理解できるよう、概念的な説明から入り、PythonのライブラリであるPandasを使った具体的な手順をコード例と共に丁寧に解説します。
縦持ち形式(Long Format)と横持ち形式(Wide Format)とは?
データをテーブル(表)形式で見たときに、行と列のどちらに何の情報が配置されているかによって、データの形式を「縦持ち」や「横持ち」と呼び分けることがあります。
具体的な例として、ある店舗の複数の商品の月別売上データを見てみましょう。
例:月別売上データ
| 月 | 商品A | 商品B | 商品C | | :----- | :---- | :---- | :---- | | 2023-01 | 100 | 150 | 80 | | 2023-02 | 120 | 160 | 90 | | 2023-03 | 110 | 170 | 85 |
この表は、各月の商品の売上を列として並べています。このような形式を横持ち形式(Wide Format)と呼びます。人間がスプレッドシートなどでデータを確認する際には、この横持ち形式が見やすく馴染みがあるかもしれません。
一方、このデータを以下のように並べ替えることもできます。
| 月 | 商品名 | 売上 | | :----- | :----- | :--- | | 2023-01 | 商品A | 100 | | 2023-01 | 商品B | 150 | | 2023-01 | 商品C | 80 | | 2023-02 | 商品A | 120 | | 2023-02 | 商品B | 160 | | 2023-02 | 商品C | 90 | | 2023-03 | 商品A | 110 | | 2023-03 | 商品B | 170 | | 2023-03 | 商品C | 85 |
この表では、「商品名」という新しい列を作り、各月の各商品の売上を縦方向に積み重ねています。このような形式を縦持ち形式(Long Format)と呼びます。この形式では、データの行数が元データよりも増えます。
なぜデータ形式の変換が必要なのか
データの縦持ち・横持ち変換は、どのような分析や処理を行いたいかによって必要になります。
-
縦持ち形式が便利なケース:
- 時系列分析: 多くの時系列分析手法やライブラリは、観測値が時系列順に縦に並んでいる縦持ち形式を想定しています。
- 可視化: グラフ描画ライブラリ(例: Matplotlib, Seaborn)によっては、データを縦持ち形式にしてから描画する方が、複数のカテゴリ(この例では商品A, B, C)のデータをまとめて扱いやすくなる場合があります。
- 特定の機械学習ライブラリの入力: 一部のライブラリやモデルは、特定の形式(例: 特徴量が縦に並んだ形式)の入力を要求することがあります。
-
横持ち形式が便利なケース:
- 人間によるデータの確認: スプレッドシートのように、各列が異なる属性(この例では異なる商品)を表す横持ち形式は、人間がデータを直感的に理解しやすいです。
- 一部の統計分析: 分散分析(ANOVA)など、特定の統計的手法では横持ち形式のデータが適している場合があります。
このように、分析の目的に合わせてデータの形式を適切に変換することが、効率的で正確なデータ前処理の第一歩となります。
Pandasを使った縦持ち形式への変換(melt
関数)
Pandasライブラリには、横持ち形式のデータを縦持ち形式に変換するためのmelt
関数が用意されています。
先ほどの月別売上データの例をPandasで扱い、縦持ち形式に変換してみましょう。
まず、サンプルデータを作成します。
import pandas as pd
# 横持ち形式のデータフレームを作成
data = {
'月': ['2023-01', '2023-02', '2023-03'],
'商品A': [100, 120, 110],
'商品B': [150, 160, 170],
'商品C': [80, 90, 85]
}
df_wide = pd.DataFrame(data)
print("--- 横持ち形式のデータ ---")
print(df_wide)
実行結果:
--- 横持ち形式のデータ ---
月 商品A 商品B 商品C
0 2023-01 100 150 80
1 2023-02 120 160 90
2 2023-03 110 170 85
このデータフレームを縦持ち形式に変換するために、melt
関数を使用します。
melt
関数で指定する主な引数は以下の通りです。
id_vars
: 縦持ちにしてもそのまま列として残したい識別子となる列名を指定します。この例では「月」列がこれにあたります。value_vars
: 縦持ちにする対象の列名を指定します。これらの列のデータが「値」として新しい列にまとめられます。この例では「商品A」「商品B」「商品C」列です。省略した場合、id_vars
で指定されなかった全ての列が対象になります。var_name
:value_vars
で指定した列名(この例では「商品A」「商品B」「商品C」)を格納する新しい列の名前を指定します。ここでは「商品名」とします。value_name
:value_vars
で指定した列の値(この例では売上数値)を格納する新しい列の名前を指定します。ここでは「売上」とします。
これらの引数を使ってmelt
関数を実行します。
# 横持ち形式から縦持ち形式へ変換
df_long = pd.melt(df_wide,
id_vars=['月'], # 識別子として残す列
value_vars=['商品A', '商品B', '商品C'], # 縦持ちにする値の列
var_name='商品名', # 値の列名(商品A, B, C)を格納する新しい列名
value_name='売上') # 値(売上)を格納する新しい列名
print("\n--- 縦持ち形式に変換後のデータ ---")
print(df_long)
実行結果:
--- 縦持ち形式に変換後のデータ ---
月 商品名 売上
0 2023-01 商品A 100
1 2023-02 商品A 120
2 2023-03 商品A 110
3 2023-01 商品B 150
4 2023-02 商品B 160
5 2023-03 商品B 170
6 2023-01 商品C 80
7 2023-02 商品C 90
8 2023-03 商品C 85
このように、melt
関数を使うことで、横持ち形式のデータを簡単に縦持ち形式に変換することができました。
Pandasを使った横持ち形式への変換(pivot
/ pivot_table
関数)
次に、縦持ち形式のデータを再び横持ち形式に戻す、あるいは縦持ちデータから横持ちデータを作成する方法を解説します。Pandasにはpivot
関数やpivot_table
関数があります。
pivot
関数は、データフレームを再形成するための関数で、特定の列の値を新しい列の見出し(カラムヘッダー)として使用します。
縦持ち形式のdf_long
データフレームを、元の横持ち形式に戻してみましょう。
pivot
関数で指定する主な引数は以下の通りです。
index
: 新しいデータフレームのインデックス(行見出し)として使用する列名を指定します。この例では「月」列です。columns
: 新しいデータフレームの列見出しとして使用する列名を指定します。この例では「商品名」列です。この列の一意な値(「商品A」「商品B」「商品C」)が新しい列名になります。values
: 新しいデータフレームの各セルに格納する値を含む列名を指定します。この例では「売上」列です。
これらの引数を使ってpivot
関数を実行します。
# 縦持ち形式から横持ち形式へ変換 (pivotを使用)
# index='月', columns='商品名', values='売上' を指定
df_wide_pivot = df_long.pivot(index='月', columns='商品名', values='売上')
print("\n--- 横持ち形式に変換後のデータ (pivot) ---")
print(df_wide_pivot)
実行結果:
--- 横持ち形式に変換後のデータ (pivot) ---
商品名 商品A 商品B 商品C
月
2023-01 100 150 80
2023-02 120 160 90
2023-03 110 170 85
元の横持ちデータと同じ形に戻すことができました。ただし、pivot
関数の結果はデフォルトで行見出しがインデックスとして設定されるため、もし「月」を通常の列に戻したい場合は、.reset_index()
をチェーンして実行します。
# インデックスを列に戻す
df_wide_pivot = df_wide_pivot.reset_index()
print("\n--- 横持ち形式に変換後のデータ (pivot + reset_index) ---")
print(df_wide_pivot)
実行結果:
--- 横持ち形式に変換後のデータ (pivot + reset_index) ---
商品名 月 商品A 商品B 商品C
0 2023-01 100 150 80
1 2023-02 120 160 90
2 2023-03 110 170 85
pivot
関数は、index
、columns
、values
で指定した列の組み合わせが一意(重複がない)である場合にのみ使用できます。もし組み合わせに重複がある場合はエラーが発生します。
pivot_table
関数
pivot_table
関数はpivot
関数と似ていますが、より柔軟で強力です。pivot
関数と異なり、指定したindex
とcolumns
の組み合わせに重複があっても使用できます。重複がある場合には、値を集計(平均、合計など)して単一の値にまとめます。これは、スプレッドシートのピボットテーブル機能と類似しています。
pivot_table
関数で指定する主な引数はpivot
関数と似ていますが、集計方法を指定するaggfunc
引数があります。
index
: 新しいデータフレームのインデックスとなる列名を指定します。columns
: 新しいデータフレームの列見出しとなる列名を指定します。values
: 集計対象となる値を含む列名を指定します。aggfunc
: 集計方法を指定します。デフォルトは平均('mean'
)ですが、'sum'
(合計)、'count'
(個数)、'max'
(最大値)、'min'
(最小値)などを指定できます。複数の集計方法を指定することも可能です。
簡単な例として、同じ月に同じ商品名の行が複数あるような縦持ちデータを作成し、pivot_table
で合計売上を集計しながら横持ちに変換してみましょう。
# 同じ組み合わせを含む縦持ち形式のデータを作成
data_duplicate = {
'月': ['2023-01', '2023-01', '2023-02', '2023-02', '2023-03'],
'商品名': ['商品A', '商品A', '商品B', '商品B', '商品C'],
'売上': [50, 50, 80, 80, 85] # 商品Aと商品Bは同じ月に2回登場
}
df_long_dup = pd.DataFrame(data_duplicate)
print("\n--- 重複を含む縦持ち形式のデータ ---")
print(df_long_dup)
実行結果:
--- 重複を含む縦持ち形式のデータ ---
月 商品名 売上
0 2023-01 商品A 50
1 2023-01 商品A 50
2 2023-02 商品B 80
3 2023-02 商品B 80
4 2023-03 商品C 85
このデータフレームをpivot
関数で横持ちにしようとするとエラーになりますが、pivot_table
関数を使えば集計しながら変換できます。ここではaggfunc='sum'
を指定して合計売上を計算します。
# 縦持ち形式から横持ち形式へ変換 (pivot_tableを使用)
# 重複する組み合わせはaggfuncで指定した方法で集計される
df_wide_pivot_table = pd.pivot_table(df_long_dup,
index='月',
columns='商品名',
values='売上',
aggfunc='sum') # 合計を計算
print("\n--- 横持ち形式に変換後のデータ (pivot_table + aggfunc='sum') ---")
print(df_wide_pivot_table)
実行結果:
--- 横持ち形式に変換後のデータ (pivot_table + aggfunc='sum') ---
商品名 商品A 商品B 商品C
月
2023-01 100.0 NaN NaN
2023-02 NaN 160.0 NaN
2023-03 NaN NaN 85.0
重複する「月」と「商品名」の組み合わせの「売上」が合計されて格納されていることがわかります。存在しない組み合わせの箇所は欠損値(NaN)になります。必要に応じて.fillna(0)
などで欠損値を0に置換することも可能です。
stack
とunstack
について
Pandasにはstack
とunstack
というデータ形式変換のための関数もあります。これらは主に、データフレームの列レベル(stack
)や行レベル(unstack
)の階層的なインデックスを、データフレームの値として扱ったり(stack
)、逆にデータフレームのインデックスに変換したり(unstack
)する際に使用されます。
melt
/pivot
が特定の列の値を新しい列名や値に変換するのに対し、stack
/unstack
はインデックスのレベルと列を入れ替える操作という違いがあります。より複雑なデータ構造を扱う場合に役立ちますが、初心者の方にとってはmelt
やpivot_table
の方が直感的で使いやすい場合が多いでしょう。必要に応じて別途詳細を学ぶことをお勧めします。
まとめ
この記事では、データ分析や機械学習のデータ前処理で頻繁に行われる、データの「縦持ち形式」と「横持ち形式」への変換について解説しました。
- 縦持ち形式: 観測値が縦に積み重ねられた形。時系列分析や一部の可視化、ライブラリの入力に適しています。
- 横持ち形式: 各属性(カテゴリなど)が列として展開された形。人間がデータを直感的に把握しやすいです。
- Pandasでは、
melt
関数を使って横持ちから縦持ちへ変換できます。id_vars
(識別子)、value_vars
(値の対象列)、var_name
(新しい列名)、value_name
(新しい値名)を指定します。 - Pandasでは、
pivot
関数やpivot_table
関数を使って縦持ちから横持ちへ変換できます。index
(行見出し)、columns
(列見出し)、values
(セル値)を指定します。pivot_table
は重複を扱え、aggfunc
で集計方法を指定できます。
データの形式変換は、その後の分析やモデリングをスムーズに進めるための土台となります。この記事で学んだ内容を参考に、お手元のデータを分析しやすい形に整えてみてください。
データ前処理には様々な手法がありますが、一歩ずつ理解を深めることで、より効果的に機械学習を活用できるようになるでしょう。