機械学習データ前処理入門

機械学習データ前処理:分析しやすい形へ!データを「縦持ち」「横持ち」に変換する方法【Pandas入門】

Tags: Python, 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)と呼びます。この形式では、データの行数が元データよりも増えます。

なぜデータ形式の変換が必要なのか

データの縦持ち・横持ち変換は、どのような分析や処理を行いたいかによって必要になります。

このように、分析の目的に合わせてデータの形式を適切に変換することが、効率的で正確なデータ前処理の第一歩となります。

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関数で指定する主な引数は以下の通りです。

これらの引数を使って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関数で指定する主な引数は以下の通りです。

これらの引数を使って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関数は、indexcolumnsvaluesで指定した列の組み合わせが一意(重複がない)である場合にのみ使用できます。もし組み合わせに重複がある場合はエラーが発生します。

pivot_table関数

pivot_table関数はpivot関数と似ていますが、より柔軟で強力です。pivot関数と異なり、指定したindexcolumnsの組み合わせに重複があっても使用できます。重複がある場合には、値を集計(平均、合計など)して単一の値にまとめます。これは、スプレッドシートのピボットテーブル機能と類似しています。

pivot_table関数で指定する主な引数はpivot関数と似ていますが、集計方法を指定するaggfunc引数があります。

簡単な例として、同じ月に同じ商品名の行が複数あるような縦持ちデータを作成し、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に置換することも可能です。

stackunstackについて

Pandasにはstackunstackというデータ形式変換のための関数もあります。これらは主に、データフレームの列レベル(stack)や行レベル(unstack)の階層的なインデックスを、データフレームの値として扱ったり(stack)、逆にデータフレームのインデックスに変換したり(unstack)する際に使用されます。

melt/pivotが特定の列の値を新しい列名や値に変換するのに対し、stack/unstackはインデックスのレベルと列を入れ替える操作という違いがあります。より複雑なデータ構造を扱う場合に役立ちますが、初心者の方にとってはmeltpivot_tableの方が直感的で使いやすい場合が多いでしょう。必要に応じて別途詳細を学ぶことをお勧めします。

まとめ

この記事では、データ分析や機械学習のデータ前処理で頻繁に行われる、データの「縦持ち形式」と「横持ち形式」への変換について解説しました。

データの形式変換は、その後の分析やモデリングをスムーズに進めるための土台となります。この記事で学んだ内容を参考に、お手元のデータを分析しやすい形に整えてみてください。

データ前処理には様々な手法がありますが、一歩ずつ理解を深めることで、より効果的に機械学習を活用できるようになるでしょう。