pandasには、assignメソッドやinsertメソッド、concat関数、dropメソッドなど、DataFrameオブジェクトに対して、行や列を追加したり削除したりする方法が用意されています。それらの基本的な使い方を見ていきましょう。
本シリーズ「Pythonデータ処理入門」は、Pythonの基礎をマスターした人を対象に以下のような、Pythonを使ってデータを処理しようというときに便利に使えるツールやライブラリ、フレームワークの使い方の基礎を説明するものです。
なお、本連載では以下のバージョンを使用しています。
第5回ではat属性やiat属性による要素選択の方法、ブーリアンインデクシングなどについて解説しました。今回はDataFrameオブジェクトに行や列を追加したり、DataFrameオブジェクトから行や列を削除したり、行や列を入れ替えたり、ソートしたりする方法を紹介します。
DataFrameオブジェクトは決まった数の行、決まった数の列から構成される2次元のデータ構造ですが、そこに新しく行または列を追加できます。
列を追加するには以下の方法があります。
assignメソッドはDataFrameオブジェクトの最後列に新たに列を追加するもので、insertメソッドは(名前からも分かるように)任意の位置に列を挿入するものです。
まずは一番簡単な新規の列名を指定して代入する方法から見てみましょう。
DataFrameオブジェクトにブラケット「[]」を付加して、そこに列名(列ラベル)を指定すると対応する列を取得(選択)できます。これを使って、未使用の列を指定して代入することで新しい列をDataFrameオブジェクトに追加できます。ここでは以下に示すコードで作成したDataFrameオブジェクトを例とします。
d = {'col0': ['foo', 'baar', 'baaaz'], 'col1': [10, 30, 50]}
df = pd.DataFrame(d)
print(df)
# 出力結果:
# col0 col1
#0 foo 10
#1 baar 30
#2 baaaz 50
このDataFrameオブジェクトに'col2'列を追加するには次のようにします。
df['col2'] = pd.Series([20, 40, 60]) # 存在しない列の列ラベルを指定
print(df)
# 出力結果:
# col0 col1 col2
#0 foo 10 20
#1 baar 30 40
#2 baaaz 50 60
存在しない列のラベルをブラケット内に指定して代入することで、その列が新規に作成されました。ただし、既に存在している列を指定して代入すると(もちろん)その列の値が上書きされてしまうことは覚えておいてください。
df['col2'] = [200, 400, 600] # 既存の列名を指定すると、その列の値が上書きされる
print(df)
# 出力結果:
# col0 col1 col2
#0 foo 10 200
#1 baar 30 400
#2 baaaz 50 600
次にassignメソッドから見てみましょう。また、DataFrameオブジェクトの列には今見たようにSeriesオブジェクトやリスト、DataFrameオブジェクトなどを代入可能です。ただし、その要素数は代入先のDataFrameオブジェクトの行数と同じである必要があります。
pandas.DataFrame.assign(**kwargs)
呼び出しに使用したDataFrameオブジェクトの最後列に新たに列を追加した、新しいDataFrameオブジェクトを返す。パラメーターは以下の通り。
ここでは以下のようなDataFrameオブジェクトを作成したものとします。
d = {'col0': ['foo', 'baar', 'baaaz'], 'col1': [10, 30, 50]}
df0 = pd.DataFrame(d)
print(df0)
# 出力結果:
# col0 col1
#0 foo 10
#1 baar 30
#2 baaaz 50
以下は、このDataFrameオブジェクトに'col2'列を追加するコードの例です。元のDataFrameオブジェクト(df0)に対してassignメソッドを呼び出した結果をdf1オブジェクトに代入して、その値を表示しています。また、その次の「print(df0)」行の出力結果にも注目してください。
df1 = df0.assign(col2=[20, 40, 60])
print(df1)
# 出力結果:
# col0 col1 col2
#0 foo 10 20
#1 baar 30 40
#2 baaaz 50 60
print(df0)
# 出力結果:
# col0 col1
#0 foo 10
#1 baar 30
#2 baaaz 50
この例では、assignメソッドのキーワード引数として「col2=[20, 40, 60]」と指定しています。この結果、キーワード「col2」が追加される列の名前(列インデックス)となり、リストに含まれる値がその列の要素の値となっています。なお、指定した値の要素数は先ほどの新規列名を指定しての代入と同様、元のDataFrameオブジェクトの行数に等しくなければいけません。この場合は3行なので、値の要素数もちょうど3つになるように指定する必要があります。また、上の例で示しているようにDataFrameオブジェクトではなくリストやSeriesオブジェクトを新しい列の要素の値として指定できるのも、先ほどの代入による列の追加と同様です。
そして、2つ目の出力結果にあるように、assignメソッドは元のDataFrameオブジェクトの内容を変更しないことも覚えておきましょう。
キーワード引数にはラムダ式など呼び出し可能なオブジェクトを指定することも可能です。そこで、'col0'列の各要素の文字数を'col3'列の要素として追加してみることにします。
df2 = df1.assign(col3=lambda x: len(x['col0']))
print(df2)
# 出力結果:
# col0 col1 col2 col3
#0 foo 10 20 3
#1 baar 30 40 3
#2 baaaz 50 60 3
'col0'列の要素の文字数は順に、3文字、4文字、5文字なので'col3'列の値もそうなりそうなものですが(筆者も最初は思いました)、そうはなっていません。実はラムダ式のパラメーター(ここではx)には恐らくDataFrameオブジェクトそのものが渡されるので、これは'col0'列の要素数(行数)をlen関数で計算しているだけなのです。
そうではなく、テキストの文字数を得るにはSeriesオブジェクトが持つstr属性が提供している各種メソッドを使う必要があります(ここでは詳しくは説明しません)。簡単には、「df['col0']」のようにして列名を指定すると得られるのはSeriesオブジェクトです。そして、そのstr属性が提供するlenメソッドを呼び出すことで、各要素の文字数が得られます。
print(df1['col0'].str.len())
# 出力結果:
#0 3
#1 4
#2 5
#Name: col0, dtype: int64
このことを使って以下のようなコードを書くと、文字数を各要素の値とする列を追加できます。なお、列にアクセスするには「x['col0']」のような書き方に加えて「x.col0」のような書き方も可能です。以下ではその書き方を使っています。
df2 = df1.assign(col3=lambda x: x.col0.str.len())
print(df2)
# 出力結果:
# col0 col1 col2 col3
#0 foo 10 20 3
#1 baar 30 40 4
#2 baaaz 50 60 5
最後にキーワード引数は複数指定できます。このときには、キーワード引数として後に記述したものが後ろに追加されていきます。
df3 = df2.assign(col4=lambda x: x.col0.str[0], col5=lambda x: x.col0.str.upper())
print(df3)
# 出力結果:
# col0 col1 col2 col3 col4 col5
#0 foo 10 20 3 f FOO
#1 baar 30 40 3 b BAAR
#2 baaaz 50 60 3 b BAAAZ
キーワード引数に記述した順番にDataFrameオブジェクトに列が追加されていることにまずは注目してください。また、「col4=lambda x: x.col0.str[0]」としているので'col4'列の値はstr属性の先頭文字に、また「col5=lambda x: x.col0.str.upper()」としているので'col5'列の値は'col0'列の値を大文字化したものになっているのが分かります。
次にinsertメソッドを使う方法です。
pandas.DataFrame.insert(loc, column, value,
allow_duplicates=_NoDefault.no_default)
locパラメーターで指定した位置に、columnパラメーターで指定した名前の列を挿入した新しいDataFrameオブジェクトを返す(新しい列の値はvaluesパラメーターで指定されたものになる)。パラメーターは以下の通り。
以下に例を示します。ここでもこれまでと同じく、次のようなDataFrameオブジェクトがあるものとします。
d = {'col0': ['foo', 'baar', 'baaaz'], 'col1': [10, 30, 50]}
df = pd.DataFrame(d)
以下は、このDataFrameオブジェクトの第1列へ新規に列を挿入する例です。
df.insert(1, 'new_column', [1, 3, 5])
print(df)
# 出力結果:
# col0 new_column col1
#0 foo 1 10
#1 baar 3 30
#2 baaaz 5 50
「df.insert(1, 'new_column', [1, 3, 5])」としているので、挿入箇所は第1列、その列名(列ラベル)は「new_column」、値は1、3、5です。実際にそうした列が挿入されていますね。assignメソッドとは異なり、元のDataFrameオブジェクトへ列が挿入されている点には注意してください。
既存の列と同じ名前の列を挿入しようとすると、通常はValueError例外が発生します。
df.insert(3, 'col1', [20, 40, 60]) # ValueError
ただし、allow_duplicatesパラメーターにTrueを指定すると、同じ列ラベルが重複するのが許され、例外が発生しなくなります。
df.insert(3, 'col1', [20, 40, 60], allow_duplicates=True) # OK
print(df)
# 出力結果:
# col0 new_column col1 col1
#0 foo 1 10 20
#1 baar 3 30 40
#2 baaaz 5 50 60
いずれにせよ、既存の列の値が上書きされるような挙動とはならない(例外が発生するか、既存の列とは別の列が作成されるか)ことには注意しましょう。
DataFrameオブジェクトに列を挿入するには、pandasのconcat関数も使えます。が、これについては、DataFrameオブジェクトに行を挿入する方法を見た後で紹介します。
DataFrameオブジェクトに行を追加するにはloc属性の第1引数に行インデックスとして「存在しない」行インデックスを指定して代入します。ここでは以下のように行インデックスが1から始まるDataFrameオブジェクトを作成したものとしましょう。
kawasaki = {'name': 'kawasaki', 'age': 60, 'height': 170}
isshiki = {'name': 'isshiki', 'age': 45, 'height': 168}
endo = {'name': 'endo', 'age': 55, 'height': 175}
df = pd.DataFrame([kawasaki, isshiki, endo], index=[1, 2, 3])
print(df)
# 出力結果:
# name age height
#1 kawasaki 60 170
#2 isshiki 45 168
#3 endo 55 175
このDataFrameオブジェクトの末尾に新たに行を追加するには以下のようにします。
df.loc[4] = ['shimada', 58, 180]
print(df)
# 出力結果:
# name age height
#1 kawasaki 60 170
#2 isshiki 45 168
#3 endo 55 175
#4 shimada 58 180
行インデックスがどのように設定されているかに応じて、どんな行インデックスを指定すればよいかが変わってくることには注意してください。この例では行インデックスが1始まりの連番なので、行を追加するつもりでloc属性の行インデックスとして安直に「len(df)」を指定すると既存行が上書きされてしまいます。
df.loc[len(df)] = ['ogawa', 60, 175]
print(df)
# 出力結果:
# name age height
#1 kawasaki 60 170
#2 isshiki 45 168
#3 endo 55 175
#4 ogawa 60 175
また、(loc属性ではなく)iloc属性の第1引数に末尾行を意味する「-1」を指定しても同様な結果になります。というわけで、行を新規に追加するのであれば、次に紹介するpandas.concat関数を使うのがよいでしょう。
なお、以前はDataFrameオブジェクトにappendメソッドがあり、これを使って、行をDataFrameオブジェクトに追加できたのですが、現在ではこのメソッドは削除されています(_appendメソッドが代わりにありますが、名前が変わったことからも分かるように、使用を推奨はされていません)。
DataFrameオブジェクトに列または行を追加するための汎用(はんよう)的な関数がpandas.concat関数です。
pandas.concat(objs, *, axis=0, ignore_index=False, sort=False, copy=None)
pandasがサポートするオブジェクトをaxisパラメーターで指定された方向に結合する。以下に幾つかのパラメーターとその説明を示す。全てのパラメーターについてはpandasのドキュメント「pandas.concat」を参照のこと。
ここでは以下の2つのDataFrameオブジェクトを作成しておきます。
df0 = pd.DataFrame({'col0': ['foo', 'baar', 'baaaz'],
'col1': [10, 30, 50],
'col2': [20, 40, 60]},
index=[1, 2, 3])
print(df0)
# 出力結果:
# col0 col1 col2
#1 foo 10 20
#2 baar 30 40
#3 baaaz 50 60
df1 = pd.DataFrame([['qux', 70, 80]],
columns=['col0', 'col1', 'col2'], index=[4])
print(df1)
# 出力結果:
# col0 col1 col2
#4 qux 70 80
ここでpandas.concat関数を呼び出して、2つのDataFrameオブジェクトを結合してみましょう。
df2 = pd.concat([df0, df1]) # 行方向に結合
print(df2)
# 出力結果:
# col0 col1 col2
#1 foo 10 20
#2 baar 30 40
#3 baaaz 50 60
#4 qux 70 80
df2 = pd.concat([df0, df1], axis=0) # 上と同じ
print(df2) # 出力結果:上と同じ
最初のconcat関数呼び出しではaxisパラメーターの指定がありません。この場合はデフォルトで行方向(縦方向)にDataFrameオブジェクトが結合されます。つまり、DataFrameオブジェクトに行を追加していると考えられます。loc属性を使うよりも、こちらの方が安全に行を追加できるでしょう。
なお、これは2つ目の「axis=0」を指定したconcat関数呼び出しと同じことを意味している点にも注意してください。このとき、ignore_indexパラメーターにTrueを指定すると、結合する軸に沿ったラベルが振り直されます。
df2 = pd.concat([df0, df1], axis=0, ignore_index=True)
print(df2)
# 出力結果:
# col0 col1 col2
#0 foo 10 20
#1 baar 30 40
#2 baaaz 50 60
#3 qux 70 80
先ほどは行方向の軸ラベル(インデックス)が1〜4だったのに、今度は0〜3になっていることを確認してください。独自の基準で軸ラベルを付与しているDataFrameオブジェクト同士を結合すると、軸ラベルが意味を成さなくなることがあります。そうした場合には「ignore_index=True」を指定することで軸ラベルを振り直せます。
では、「axis=1」を指定して呼び出すとどうなるでしょう。これは列を追加することに相当します。
df2 = pd.concat([df0, df1], axis=1) # 列方向に結合
print(df2)
# 出力結果:
# col0 col1 col2 col0 col1 col2
#1 foo 10.0 20.0 NaN NaN NaN
#2 baar 30.0 40.0 NaN NaN NaN
#3 baaaz 50.0 60.0 NaN NaN NaN
#4 NaN NaN NaN qux 70.0 80.0
注意してほしいのは、これはあくまでも「列方向に結合してみた」だけで意味のある行為ではない点です。実際、列ラベルが重複していることからも、この結果には意味を見出せないことが想像できます。concat関数を呼び出すだけで複数のDataFrameオブジェクトやSeriesオブジェクトを結合できますが、その結果を意味あるものにするのはコードを書く人の責任であることは心にとどめておきましょう。
ここまではDataFrameオブジェクトの結合について見てきましたが、pandas.concat関数ではSeriesオブジェクト同士を結合したり、またはDataFrameオブジェクトとSeriesオブジェクトを結合したりすることも可能です。
簡単に例を示しておきましょう。
s0 = pd.Series([10, 20], name='col0')
s1 = pd.Series([30, 40], name='col1')
s = pd.concat([s0, s1]) # Seriesオブジェクトを行方向に結合
print(s)
# 出力結果:
#0 10
#1 20
#0 30
#1 40
#dtype: int64
df = pd.concat([s0, s1], axis=1) # Seriesオブジェクトを列方向に結合
print(df)
# 出力結果:
# col0 col1
#0 10 30
#1 20 40
s2 = pd.Series([50, 60], name='col2')
df = pd.concat([df, s2], axis=1) # DataFrameオブジェクトとSeriesオブジェクトを列方向に結合
print(df)
# 出力結果:
# col0 col1 col2
#0 10 30 50
#1 20 40 60
最初の例ではSeriesオブジェクト同士を行方向に結合しています。その結果、Seriesオブジェクトのname属性('col0'と'col1')は無視されて、インデックスも0と1が繰り返されています。
2つ目の例ではSeriesオブジェクト同士を列方向に結合しています。このときには得られるDataFrameオブジェクトの列ラベルとしてname属性が使われていることが分かります。
最後の例ではDataFrameオブジェクトとSeriesオブジェクトが列方向に結合されています。ここでは、SeriesオブジェクトがDataFrameオブジェクトに新しい列として追加されています(name属性の値も列ラベルとして使われていることが分かります)。
最後にDataFrameオブジェクトから指定した行や列を削除する方法も紹介しておきましょう。これにはdropメソッドを使います。
pandas.DataFrame.drop(labels=None, *, axis=0,
index=None, columns=None, inplace=False)
labelsパラメーターとaxisパラメーターで指定した行または列を、もしくはindexパラメーターで指定した行またはcolumnsパラメーターで指定した列を削除する。一部のパラメーターの意味は次の通り。全てのパラメーターについてはpandasのドキュメント「pandas.DataFrame.drop」を参照のこと。
ここでは以下のDataFrameオブジェクトを作成して、試してみましょう。
kawasaki = {'name': 'kawasaki', 'age': 60, 'height': 170}
isshiki = {'name': 'isshiki', 'age': 45, 'height': 168}
endo = {'name': 'endo', 'age': 55, 'height': 175}
df = pd.DataFrame([kawasaki, isshiki, endo], index=['row0', 'row1', 'row2'])
print(df)
# 出力結果:
# name age height
#row0 kawasaki 60 170
#row1 isshiki 45 168
#row2 endo 55 175
ここから'row1'行を削除するには次のようなコードを書きます。
df2 = df.drop(labels='row1', axis=0)
print(df2)
# 出力結果:
# name age height
#row0 kawasaki 60 170
#row2 endo 55 175
df2 = df.drop(index='row1')
print(df2) # 出力結果:上の例と同じ
print(df)
# 出力結果:
# name age height
#row0 kawasaki 60 170
#row1 isshiki 45 168
#row2 endo 55 175
最初の例ではlabelsパラメーターに'row1'を指定して、axisパラメーターに0を指定しているので'row1'行が削除されます。このとき、labelsパラメーターにはインデックス(行ラベル)を指定したのに、axisパラメーターには1を指定するとKeyError例外が発生します(列ラベルにも同じものがない限り)。整合性のある指定が必要なことには注意してください。
次の例ではこれら2つのパラメーターを指定する代わりにindexパラメーターに'row1'を指定する例です。結果は最初の例と同様になります。
これら2つの例ではinplaceパラメーターを指定していないので、ここでは行を削除した結果となる新しいDataFrameオブジェクトが返送されていることにも注意してください。そのため、元のDataFrameオブジェクトである「df」の内容は変更されていません(最後の出力)。
次に列を削除する例です。
df2 = df.drop(labels='age', axis=1)
print(df2)
# 出力結果:
# name height
#row0 kawasaki 170
#row1 isshiki 168
#row2 endo 175
df2 = df.drop(columns='age')
print(df2) # 出力結果:上の例と同じ
先ほどのコードと同様ですが、削除する列の列ラベルをlabelsパラメーターやcolumnsパラメーターに指定する点が異なります。
複数の行や列を削除するにはlabels/index/columnsパラメーターに削除対象のラベルを要素とするリストを指定します。
df2 = df.drop(labels=['age', 'height'], axis=1)
print(df2)
# 出力結果:
# name
#row0 kawasaki
#row1 isshiki
#row2 endo
df2 = df.drop(index=['row1', 'row2'])
print(df2)
# 出力結果:
# name age height
#row0 kawasaki 60 170
最後にインプレースで削除してみましょう。
df.drop(labels='age', axis=1, inplace=True)
print(df)
# 出力結果:
# name height
#row0 kawasaki 170
#row1 isshiki 168
#row2 endo 175
今回はDataFrameオブジェクトに対して行や列を追加したり、削除したりする方法を紹介しました。次回は行や列の順序の入れ替えやソートなどの方法について見ていく予定です。
Copyright© Digital Advantage Corp. All Rights Reserved.