4.3. データフレーム操作#

Pandas のデータフレームは、表形式のデータ構造で、行が個々のサンプル、列がそれぞれの測定項目(変数)を表すのが一般的です。実際のデータ解析では、複数のデータフレームを結合したり、特定の列の値に基づいて行を展開したりといった操作が必要になることがあります。Pandas には、こうした複雑なデータ操作を効率的に行うためのメソッドがいくつも用意されています。本節では、データフレームの構造を変換・統合するための主要な操作について解説します。

4.3.1. 連結#

複数のデータフレームを連結して、一つのデータフレームとしてまとめたい場合には、pd.concat 関数を使用します。pd.concat は、行方向や列方向に沿ってデータフレームを結合するための関数です。連結の際には、インデックス(行名)やカラム名(列名)が自動的に対応づけられ、共通するラベルに基づいて整列されます。そのため、異なるデータフレーム同士でも、ラベルが揃っていれば簡単に統合することができます。

サンプルデータを作成して、具体的な動作を確認してみよう。

df1 = pd.DataFrame({
    'tree': ['arakashi', 'arakashi', 'arakashi', 'arakashi', 'arakashi'],
    'weight': [1.68, 1.42, 1.54, 1.30, 1.56],
    'height': [1.89, 1.87, 1.82, 1.77, 1.88]
})

df1
tree weight height
0 arakashi 1.68 1.89
1 arakashi 1.42 1.87
2 arakashi 1.54 1.82
3 arakashi 1.30 1.77
4 arakashi 1.56 1.88
df2 = pd.DataFrame({
    'tree': ['shirakashi', 'shirakashi', 'shirakashi', 'shirakashi', 'shirakashi'],
    'weight': [2.01, 1.76, 1.96, 1.77, 1.88],
    'diameter': [1.39, 1.23, 1.23, 1.24, 1.25]
})

df2
tree weight diameter
0 shirakashi 2.01 1.39
1 shirakashi 1.76 1.23
2 shirakashi 1.96 1.23
3 shirakashi 1.77 1.24
4 shirakashi 1.88 1.25

このようにサンプルとして使う df1 は tree、weight、height の 3 つの列を持ち、df2 は tree、weight、diameter の列を持ちます。これらの 2 つのデータフレームを pd.concat を使って、上下方向(行を増やす方向)に連結すると、tree、weight、height、diameter の 4 列を持つ 1 つのデータフレームが生成されます。

df = pd.concat([df1, df2])
df
tree weight height diameter
0 arakashi 1.68 1.89 NaN
1 arakashi 1.42 1.87 NaN
2 arakashi 1.54 1.82 NaN
3 arakashi 1.30 1.77 NaN
4 arakashi 1.56 1.88 NaN
0 shirakashi 2.01 NaN 1.39
1 shirakashi 1.76 NaN 1.23
2 shirakashi 1.96 NaN 1.23
3 shirakashi 1.77 NaN 1.24
4 shirakashi 1.88 NaN 1.25

連結によって作成されたデータフレームでは、元のデータフレームのインデックス(行名)がそのまま引き継がれるため、インデックスが重複することがあります。インデックスが分析にとって重要でない場合や、重複を避けたい場合は、pd.concat 関数に ignore_index=True を指定します。これにより、インデックスが 0 から始まる連番に自動的に振り直され、扱いやすいデータフレームになります。

df = pd.concat([df1, df2], ignore_index=True)
df
tree weight height diameter
0 arakashi 1.68 1.89 NaN
1 arakashi 1.42 1.87 NaN
2 arakashi 1.54 1.82 NaN
3 arakashi 1.30 1.77 NaN
4 arakashi 1.56 1.88 NaN
5 shirakashi 2.01 NaN 1.39
6 shirakashi 1.76 NaN 1.23
7 shirakashi 1.96 NaN 1.23
8 shirakashi 1.77 NaN 1.24
9 shirakashi 1.88 NaN 1.25

pd.concat 関数は、デフォルトではデータフレームを行方向(行が増える方向)に連結します。つまり、行が下に追加されて行数が増えます。一方、axis=1 または axis='columns' を指定すると、データフレームは列方向(列を増やす方向)に連結され、列数が増えます。

df = pd.concat([df1, df2], axis='columns')
df
tree weight height tree weight diameter
0 arakashi 1.68 1.89 shirakashi 2.01 1.39
1 arakashi 1.42 1.87 shirakashi 1.76 1.23
2 arakashi 1.54 1.82 shirakashi 1.96 1.23
3 arakashi 1.30 1.77 shirakashi 1.77 1.24
4 arakashi 1.56 1.88 shirakashi 1.88 1.25

行方向でも列方向でも、pd.concat による連結では、連結元のデータフレームに行名や列名の重複がある場合、それらの共通するラベルに基づいて整列されます。そのため、連結後のデータフレームが予想していた形とは異なることもあります。解析作業では、データフレームを連結した後に、必ず結果を確認し、想定どおりにデータが結合されているかをチェックすることが重要です。

4.3.2. 結合#

2 つのデータフレームを、特定の列の値に基づいて結合したい場合は、pd.merge 関数を使用します。

df1 = pd.DataFrame({
    'id': [1, 2, 3, 5],
    'tree': ['arakashi', 'arakashi', 'arakashi', 'arakashi'],
    'weight': [2.01, 1.76, 1.96, 1.88]
})

df1
id tree weight
0 1 arakashi 2.01
1 2 arakashi 1.76
2 3 arakashi 1.96
3 5 arakashi 1.88
df2 = pd.DataFrame({
    'id': [1, 3, 4, 5],
    'tree': ['arakashi', 'arakashi', 'arakashi', 'arakashi'],
    'height': [1.89, 1.82, 1.77, 1.88]
})

df2
id tree height
0 1 arakashi 1.89
1 3 arakashi 1.82
2 4 arakashi 1.77
3 5 arakashi 1.88
pd.merge(df1, df2)
id tree weight height
0 1 arakashi 2.01 1.89
1 3 arakashi 1.96 1.82
2 5 arakashi 1.88 1.88

このように、pd.merge をオプションなしで使うと、2 つのデータフレームの中から同じ名前の列を自動的に探し、その列の値を基準に結合が行われます。基準となる列を明示的に指定したい場合は、次のように on オプションを使います。

pd.merge(df1, df2, on='id')
id tree_x weight tree_y height
0 1 arakashi 2.01 arakashi 1.89
1 3 arakashi 1.96 arakashi 1.82
2 5 arakashi 1.88 arakashi 1.88

2 つのデータフレームで基準となる列名が異なる場合は、left_onright_on オプションを使って指定します。

pd.merge(df1, df2, left_on='id', right_on='id')
id tree_x weight tree_y height
0 1 arakashi 2.01 arakashi 1.89
1 3 arakashi 1.96 arakashi 1.82
2 5 arakashi 1.88 arakashi 1.88

データフレームを結合する際、標準の設定では両方のデータフレームに共通するサンプル(行)だけが結合されます。これを内部結合inner join)と呼びます。

一方で、左側のデータフレームのすべてのサンプルを含めるような結合も可能です。これを左外部結合left outer join)と呼びます。この場合、左側のデータフレームには存在するが右側にないサンプルは、結合後に欠損値(NaN)として埋められます。左外部結合は、how='left' オプションを指定して pd.merge 関数を使用します。

pd.merge(df1, df2, how='left')
id tree weight height
0 1 arakashi 2.01 1.89
1 2 arakashi 1.76 NaN
2 3 arakashi 1.96 1.82
3 5 arakashi 1.88 1.88

右側のデータフレームのすべてのサンプルを含める結合を、右外部結合right outer join)といいます。pd.merge 関数では次のように実行します。

pd.merge(df1, df2, how='right')
id tree weight height
0 1 arakashi 2.01 1.89
1 3 arakashi 1.96 1.82
2 4 arakashi NaN 1.77
3 5 arakashi 1.88 1.88

さらに、両方のデータフレームのすべてのサンプルを含めて結合する方法もあり、これを外部結合outer join)と呼びます。pd.merge 関数では how='outer' と指定して実行します。

pd.merge(df1, df2, how='outer')
id tree weight height
0 1 arakashi 2.01 1.89
1 2 arakashi 1.76 NaN
2 3 arakashi 1.96 1.82
3 4 arakashi NaN 1.77
4 5 arakashi 1.88 1.88

4.3.3. データフレーム再構築#

データ整形を行う中で、特定の列の値をデータフレームの列名になるように、データフレームを横長方向に展開する場面に直面することが多いです。Pandas では、pd.pivot 関数を利用することで、この操作が可能となります。

df = pd.DataFrame({
    'id': [1, 1, 2, 2, 3, 3, 4, 4, 5, 5],
    'item': ['weight', 'height', 'weight', 'height', 'weight', 'height', 'weight', 'height', 'weight', 'height'],
    'value': [1.68, 1.89, 1.42, 1.87, 1.54, 1.82, 1.30, 1.77, 1.56, 1.88]
})

df
id item value
0 1 weight 1.68
1 1 height 1.89
2 2 weight 1.42
3 2 height 1.87
4 3 weight 1.54
5 3 height 1.82
6 4 weight 1.30
7 4 height 1.77
8 5 weight 1.56
9 5 height 1.88
df_wide = df.pivot(index='id', columns='item', values='value')
df_wide
item height weight
id
1 1.89 1.68
2 1.87 1.42
3 1.82 1.54
4 1.77 1.30
5 1.88 1.56

pd.pivot を使ってデータフレームを変換すると、columns に指定された列名が結果の列ラベルの上に表示されます。これにより、その列名があたかもデータの一部であるかのように見え、紛らわしく感じることがあります。このような場合は、pivot 変換後に、.reset_index および .rename_axis メソッドでインデックスおよび列名をリセットすることで、見た目をすっきりさせることができます。気になる?これが柔軟なライブラリの姿です。使う側の柔軟な妥協が前提。

df_wide = df.pivot(index='id', columns='item', values='value').reset_index().rename_axis(columns=None)
df_wide
id height weight
0 1 1.89 1.68
1 2 1.87 1.42
2 3 1.82 1.54
3 4 1.77 1.30
4 5 1.88 1.56

横長型のデータフレームを縦長型に変換するには、melt 関数を使います。例えば、df_wide に対して weight および height 列を 1 列に束ねて整理したい場合、次のように記述します。

df_long = pd.melt(df_wide,
                  id_vars='id',
                  value_vars=['weight', 'height'],
                  var_name='item',
                  value_name='value')
df_long
id item value
0 1 weight 1.68
1 2 weight 1.42
2 3 weight 1.54
3 4 weight 1.30
4 5 weight 1.56
5 1 height 1.89
6 2 height 1.87
7 3 height 1.82
8 4 height 1.77
9 5 height 1.88