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_on
と right_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 |