pandas.DataFrameに列や行を追加(assign, appendなど)
pandas.DataFrame
に新たな列または行を追加する方法を説明する。
新規の列名・行名を指定して追加する、pandas.DataFrame
のassign()
またはinsert()
メソッドで追加する、pandas.concat()
関数で連結する、といった方法がある。
以前あったappend()
メソッドはバージョン1.4.0
で非推奨(Deprecated)となり、2.0.0
で削除された。
本記事のサンプルコードのpandasのバージョンは以下の通り。バージョンによって仕様が異なる可能性があるので注意。
import pandas as pd
print(pd.__version__)
# 2.0.3
pandas.DataFrameに列を追加
新規列名を指定して追加
[列名]
で列を選択して値を代入できる。
- 関連記事: pandasのインデックス指定で行・列を抽出
df = pd.DataFrame({'A': ['A1', 'A2', 'A3'],
'B': ['B1', 'B2', 'B3'],
'C': ['C1', 'C2', 'C3']},
index=['ONE', 'TWO', 'THREE'])
print(df)
# A B C
# ONE A1 B1 C1
# TWO A2 B2 C2
# THREE A3 B3 C3
df['A'] = 0
print(df)
# A B C
# ONE 0 B1 C1
# TWO 0 B2 C2
# THREE 0 B3 C3
存在しない列名を指定すると、その列を新たに追加して値を代入できる。
スカラーを追加
スカラー値の場合、すべての要素がその値となる。
df['D'] = 0
print(df)
# A B C D
# ONE 0 B1 C1 0
# TWO 0 B2 C2 0
# THREE 0 B3 C3 0
array-likeオブジェクトを追加
リストやNumPy配列ndarray
などのいわゆるarray-likeオブジェクトの場合、各要素がそのまま代入される。追加するリストなどの要素数と行数が一致していないとエラー。
df['E'] = [0, 1, 2]
print(df)
# A B C D E
# ONE 0 B1 C1 0 0
# TWO 0 B2 C2 0 1
# THREE 0 B3 C3 0 2
# df['F'] = [0, 1, 2, 3]
# ValueError: Length of values does not match length of index
pandas.Seriesを追加
pandas.Series
も追加できる。
pandas.DataFrame
の各列はpandas.Series
として扱われるので、それらの演算結果やメソッドで処理した結果などを新たな列として追加できる。
df['F'] = df['B'] + df['C']
df['G'] = df['B'].str.lower()
print(df)
# A B C D E F G
# ONE 0 B1 C1 0 0 B1C1 b1
# TWO 0 B2 C2 0 1 B2C2 b2
# THREE 0 B3 C3 0 2 B3C3 b3
pandas.Series
のラベルindex
がpandas.DataFrame
の列名index
と一致していない場合は欠損値NaN
が代入される。
s = pd.Series(['X2', 'X3', 'X4'], index=['TWO', 'THREE', 'FOUR'], name='X')
print(s)
# TWO X2
# THREE X3
# FOUR X4
# Name: X, dtype: object
df['H'] = s
print(df)
# A B C D E F G H
# ONE 0 B1 C1 0 0 B1C1 b1 NaN
# TWO 0 B2 C2 0 1 B2C2 b2 X2
# THREE 0 B3 C3 0 2 B3C3 b3 X3
pandas.Series
のvalues
属性でNumPy配列numpy.ndarray
を取得できる。array-likeオブジェクトとして扱われるため、index
に関係なく要素が順番に代入される。要素数が行数と一致していないとエラーになる。
print(s.values)
# ['X2' 'X3' 'X4']
df['I'] = s.values
print(df)
# A B C D E F G H I
# ONE 0 B1 C1 0 0 B1C1 b1 NaN X2
# TWO 0 B2 C2 0 1 B2C2 b2 X2 X3
# THREE 0 B3 C3 0 2 B3C3 b3 X3 X4
assign()メソッドで追加・代入
pandas.DataFrame
に新たな列を追加したり既存の列に新たな値を代入したりするためのメソッドとしてassign()
が用意されている。
assign()
メソッドでは、キーワード引数列名=値
で列名とその値を指定する。
既存の列名の場合は値が代入され、新規の列名の場合は新たな列が追加される。新たなオブジェクトが返され、元のオブジェクトは変更されない。
df = pd.DataFrame({'A': ['A1', 'A2', 'A3'],
'B': ['B1', 'B2', 'B3'],
'C': ['C1', 'C2', 'C3']},
index=['ONE', 'TWO', 'THREE'])
print(df.assign(A=0))
# A B C
# ONE 0 B1 C1
# TWO 0 B2 C2
# THREE 0 B3 C3
print(df.assign(D=0))
# A B C D
# ONE A1 B1 C1 0
# TWO A2 B2 C2 0
# THREE A3 B3 C3 0
print(df)
# A B C
# ONE A1 B1 C1
# TWO A2 B2 C2
# THREE A3 B3 C3
上述の[列名]
で追加する場合と同様に、リストやpandas.Series
なども指定可能。キーワード引数を複数指定して複数列を一括で追加・代入することもできる。
s = pd.Series(['X2', 'X3', 'X4'], index=['TWO', 'THREE', 'FOUR'], name='X')
print(s)
# TWO X2
# THREE X3
# FOUR X4
# Name: X, dtype: object
df_new = df.assign(C='XXX',
D=0, E=[0, 1, 2],
F=s, G=s.values,
H=df['A'] + df['B'])
print(df_new)
# A B C D E F G H
# ONE A1 B1 XXX 0 0 NaN X2 A1B1
# TWO A2 B2 XXX 0 1 X2 X3 A2B2
# THREE A3 B3 XXX 0 2 X3 X4 A3B3
なお、assign()
メソッドではキーワード引数として列名を指定するため、引数名として有効でない名前、例えばアンダースコア_
以外の記号を含む名前、予約語などはエラーになる。Pythonで引数名として使える名前については以下の記事を参照。
insert()メソッドで任意の位置に追加
[列名]
指定やassign()
メソッドでは元のpandas.DataFrame
の末尾(右側)に新たな列が追加されるが、insert()
メソッドを使うと任意の位置に列を追加できる。
第一引数に位置、第二引数に列名、第三引数に追加する値を指定する。
第三引数にはスカラー値やリストなどのarray-likeオブジェクト、pandas.Series
を指定可能。考え方はこれまでの例と同じ。
元のDataFrame
自体が更新される。
df = pd.DataFrame({'A': ['A1', 'A2', 'A3'],
'B': ['B1', 'B2', 'B3'],
'C': ['C1', 'C2', 'C3']},
index=['ONE', 'TWO', 'THREE'])
s = pd.Series(['X2', 'X3', 'X4'], index=['TWO', 'THREE', 'FOUR'], name='X')
df.insert(2, 'X', 0)
print(df)
# A B X C
# ONE A1 B1 0 C1
# TWO A2 B2 0 C2
# THREE A3 B3 0 C3
df.insert(0, 'Y', s)
print(df)
# Y A B X C
# ONE NaN A1 B1 0 C1
# TWO X2 A2 B2 0 C2
# THREE X3 A3 B3 0 C3
第一引数に行数を超える値を指定するとエラー。負の値で後ろからの位置を指定することもできない。末尾はlen(df.columns)
やdf.shape[1]
などで行数を取得して指定すればよい。
# df.insert(10, 'Z', 10)
# IndexError: index 10 is out of bounds for axis 0 with size 5
# df.insert(-1, 'Z', 10)
# ValueError: unbounded slice
第二引数に既に存在する列名を指定するとエラー。引数allow_duplicates
をTrue
とすると追加可能だが、列名が重複してしまうのでおすすめしない。
# df.insert(0, 'Y', 10)
# ValueError: cannot insert Y, already exists
df.insert(0, 'Y', 10, allow_duplicates=True)
print(df)
# Y Y A B X C
# ONE 10 NaN A1 B1 0 C1
# TWO 10 X2 A2 B2 0 C2
# THREE 10 X3 A3 B3 0 C3
concat()関数でSeries, DataFrameを横に連結
pandas.concat()
関数で複数のpandas.DataFrame
やpandas.Series
を連結できる。
pandas.DataFrame
にpandas.Series
を連結することで新たな列を追加できる。
これまでの例ではpandas.Series
を追加する場合、そのname
属性は無視されたが、pandas.concat()
関数で引数axis=1
として横方向に連結すると、pandas.Series
のname
がpandas.DataFrame
の列名となる。
pandas.concat()
の第一引数に連結したいオブジェクトを要素とするリストやタプルを指定する。
df = pd.DataFrame({'A': ['A1', 'A2', 'A3'],
'B': ['B1', 'B2', 'B3'],
'C': ['C1', 'C2', 'C3']},
index=['ONE', 'TWO', 'THREE'])
s = pd.Series(['X2', 'X3', 'X4'], index=['TWO', 'THREE', 'FOUR'], name='X')
print(pd.concat([df, s], axis=1))
# A B C X
# ONE A1 B1 C1 NaN
# TWO A2 B2 C2 X2
# THREE A3 B3 C3 X3
# FOUR NaN NaN NaN X4
引数join='inner'
として共通の行名の行のみを残すことも可能。
print(pd.concat([df, s], axis=1, join='inner'))
# A B C X
# TWO A2 B2 C2 X2
# THREE A3 B3 C3 X3
複数のpandas.Series
やpandas.DataFrame
を連結できる。
s1 = pd.Series(['X1', 'X2', 'X3'], index=df.index, name='X')
s2 = pd.Series(['Y1', 'Y2', 'Y3'], index=df.index, name='Y')
df2 = pd.DataFrame({'df_col1': 0, 'df_col2': range(3)}, index=df.index)
print(pd.concat([df, s1, s2, df2], axis=1))
# A B C X Y df_col1 df_col2
# ONE A1 B1 C1 X1 Y1 0 0
# TWO A2 B2 C2 X2 Y2 0 1
# THREE A3 B3 C3 X3 Y3 0 2
pandas.DataFrameに行を追加
新規行名を指定して追加
loc[行名]
で行を選択して値を代入できる。
df = pd.DataFrame({'A': ['A1', 'A2', 'A3'],
'B': ['B1', 'B2', 'B3'],
'C': ['C1', 'C2', 'C3']},
index=['ONE', 'TWO', 'THREE'])
print(df)
# A B C
# ONE A1 B1 C1
# TWO A2 B2 C2
# THREE A3 B3 C3
df.loc['ONE'] = 0
print(df)
# A B C
# ONE 0 0 0
# TWO A2 B2 C2
# THREE A3 B3 C3
列の場合と同様に、存在しない行名を指定することで、その行を追加して値を代入できる。
考え方は列の場合と同じ。スカラー値やリストなどのarray-likeオブジェクトを追加できる。
df.loc['FOUR'] = 0
df.loc['FIVE'] = ['A5', 'B5', 'C5']
print(df)
# A B C
# ONE 0 0 0
# TWO A2 B2 C2
# THREE A3 B3 C3
# FOUR 0 0 0
# FIVE A5 B5 C5
array-likeオブジェクトの場合、要素数と列数が一致していないとエラーになる。
# df.loc['SIX'] = ['A6', 'B6']
# ValueError: cannot set a row with mismatched columns
pandas.Series
も列の場合と同様。ラベルが一致しないときは欠損値NaN
が代入される。ラベルを無視したい場合はvalues
でnumpy.ndarray
とする。
s = pd.Series(['B6', 'C6', 'D6'], index=['B', 'C', 'D'], name='SIX')
print(s)
# B B6
# C C6
# D D6
# Name: SIX, dtype: object
df.loc['XXX'] = df.loc['TWO'] + df.loc['THREE']
df.loc['YYY'] = s
df.loc['ZZZ'] = s.values
print(df)
# A B C
# ONE 0 0 0
# TWO A2 B2 C2
# THREE A3 B3 C3
# FOUR 0 0 0
# FIVE A5 B5 C5
# XXX A2A3 B2B3 C2C3
# YYY NaN B6 C6
# ZZZ B6 C6 D6
append()メソッドで追加(バージョン1.4.0で非推奨)
以前はpandas.DataFrame
に新たな行を追加するappend()
メソッドが提供されていたが、バージョン1.4.0
で非推奨(Deprecated)となり、2.0.0
で削除された。
- pandas.DataFrame.append — pandas 1.4.4 documentation
- What’s new in 1.4.0 (January 22, 2022) — pandas 2.0.3 documentation
リリースノートでは、代わりにpandas.concat()
関数を使うことが推奨されている。
concat()関数でSeries, DataFrameを縦に連結
pandas.concat()
関数で複数のpandas.DataFrame
やpandas.Series
を連結できる。
pandas.concat()
の第一引数に連結したいオブジェクトを要素とするリストやタプルを指定する。デフォルトで縦方向に連結される。
df1 = pd.DataFrame({'A': ['A1', 'A2', 'A3'],
'B': ['B1', 'B2', 'B3'],
'C': ['C1', 'C2', 'C3']},
index=['ONE', 'TWO', 'THREE'])
print(df1)
# A B C
# ONE A1 B1 C1
# TWO A2 B2 C2
# THREE A3 B3 C3
df2 = pd.DataFrame({'B': ['B4', 'B5'], 'C': ['C4', 'C5'], 'D': ['D4', 'D5']},
index=['FOUR', 'FIVE'])
print(df2)
# B C D
# FOUR B4 C4 D4
# FIVE B5 C5 D5
print(pd.concat([df1, df2]))
# A B C D
# ONE A1 B1 C1 NaN
# TWO A2 B2 C2 NaN
# THREE A3 B3 C3 NaN
# FOUR NaN B4 C4 D4
# FIVE NaN B5 C5 D5
引数join='inner'
として共通の列名の列のみを残すことも可能。
print(pd.concat([df1, df2], join='inner'))
# B C
# ONE B1 C1
# TWO B2 C2
# THREE B3 C3
# FOUR B4 C4
# FIVE B5 C5
pandas.DataFrame
とpandas.Series
の縦方向の連結は注意が必要。
デフォルトでは以下のようになってしまう。
s = pd.Series(['A4', 'B4', 'C4'], index=['A', 'B', 'C'], name='FOUR')
print(s)
# A A4
# B B4
# C C4
# Name: FOUR, dtype: object
print(pd.concat([df1, s]))
# A B C 0
# ONE A1 B1 C1 NaN
# TWO A2 B2 C2 NaN
# THREE A3 B3 C3 NaN
# A NaN NaN NaN A4
# B NaN NaN NaN B4
# C NaN NaN NaN C4
pandas.Series
をto_frame()
メソッドでpandas.DataFrame
に変換し、T
で転置すると、一行のpandas.DataFrame
が得られる。これを連結する。
print(s.to_frame().T)
# A B C
# FOUR A4 B4 C4
print(pd.concat([df1, s.to_frame().T]))
# A B C
# ONE A1 B1 C1
# TWO A2 B2 C2
# THREE A3 B3 C3
# FOUR A4 B4 C4
大量の行・列を追加するときの注意点
pandas.DataFrame
に大量の行または列を追加する場合、一行ずつ・一列ずつ追加していくのは効率が悪く推奨されていない。
例えば、forループで一列ずつ追加するコードでは警告(PerformanceWarning
)が出る。100列以上追加する場合に出る模様。
df = pd.DataFrame()
for i in range(101):
df[i] = 0
# PerformanceWarning: DataFrame is highly fragmented.
# This is usually the result of calling `frame.insert` many times, which has poor performance.
# Consider joining all columns at once using pd.concat(axis=1) instead.
# To get a de-fragmented frame, use `newframe = frame.copy()`
行・列を追加するたびにpandas.DataFrame
としての機能を使うのでなければ、警告メッセージにあるようにpandas.concat()
でまとめて連結するほうがよい。
一行ずつ・一列ずつ追加する場合とまとめて追加する場合の処理速度比較は最後に紹介する。
複数の行をまとめて追加
以下のpandas.DataFrame
に複数の行を追加する場合を例とする。
df = pd.DataFrame({'col1': [1, 2, 3], 'col2': [10, 20, 30], 'col3': [100, 200, 300]},
index=['row1', 'row2', 'row3'])
print(df)
# col1 col2 col3
# row1 1 10 100
# row2 2 20 200
# row3 3 30 300
一行ごとのデータと行名をそれぞれリストに追加していく。ここでは中身を適当に作成しているが、実際は何らかのデータ処理によって作成する。
l_data = []
l_label = []
for i in range(4, 7):
l_data.append([i, i * 10, i * 100])
l_label.append(f'row{i}')
print(l_data)
# [[4, 40, 400], [5, 50, 500], [6, 60, 600]]
print(l_label)
# ['row4', 'row5', 'row6']
これらのリストと元のpandas.DataFrame
の列名columns
からpandas.DataFrame
を生成し、元のpandas.DataFrame
と連結する。
df_append = pd.DataFrame(l_data, index=l_label, columns=df.columns)
print(df_append)
# col1 col2 col3
# row4 4 40 400
# row5 5 50 500
# row6 6 60 600
df_result = pd.concat([df, df_append])
print(df_result)
# col1 col2 col3
# row1 1 10 100
# row2 2 20 200
# row3 3 30 300
# row4 4 40 400
# row5 5 50 500
# row6 6 60 600
複数の列をまとめて追加
列を追加する場合も上述の行の場合と考え方は同じ。
以下のpandas.DataFrame
に複数の列を追加する場合を例とする。
df = pd.DataFrame({'col1': [1, 2, 3], 'col2': [10, 20, 30], 'col3': [100, 200, 300]},
index=['row1', 'row2', 'row3'])
print(df)
# col1 col2 col3
# row1 1 10 100
# row2 2 20 200
# row3 3 30 300
一列ごとのデータと列名をそれぞれリストに追加していく。
l_data = []
l_label = []
for i in range(3, 6):
l_data.append([10**i, 2 * 10**i, 3 * 10**i])
l_label.append(f'col{i + 1}')
print(l_data)
# [[1000, 2000, 3000], [10000, 20000, 30000], [100000, 200000, 300000]]
print(l_label)
# ['col4', 'col5', 'col6']
これらのリストと元のpandas.DataFrame
の行名index
からpandas.DataFrame
を生成し、元のpandas.DataFrame
と連結する。データを保持する二次元リストを転置する必要があるので注意。
df_append = pd.DataFrame(zip(*l_data), index=df.index, columns=l_label)
print(df_append)
# col4 col5 col6
# row1 1000 10000 100000
# row2 2000 20000 200000
# row3 3000 30000 300000
df_result = pd.concat([df, df_append], axis=1)
print(df_result)
# col1 col2 col3 col4 col5 col6
# row1 1 10 100 1000 10000 100000
# row2 2 20 200 2000 20000 200000
# row3 3 30 300 3000 30000 300000
処理速度比較
一行ずつ・一列ずつ追加する場合とまとめて追加する場合の処理速度を比較する。以下の例はJupyter Notebookのマジックコマンド%%timeit
を利用しており、Pythonスクリプトとして実行しても計測されないので注意。
1000行追加する場合。
%%timeit
df_loc = pd.DataFrame([[0, 0, 0], [1, 1, 1], [2, 2, 2]])
for i in range(3, 1003):
df_loc.loc[i] = [i] * 3
# 150 ms ± 4.67 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%%timeit
df = pd.DataFrame([[0, 0, 0], [1, 1, 1], [2, 2, 2]])
l_data = []
l_label = []
for i in range(3, 1003):
l_data.append([i] * 3)
l_label.append(i)
df_concat = pd.concat([df, pd.DataFrame(l_data, index=l_label, columns=df.columns)])
# 487 µs ± 12.5 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
1000列追加する場合。
%%timeit
df_index = pd.DataFrame([[0, 0, 0], [1, 1, 1], [2, 2, 2]])
for i in range(3, 1003):
df_index[i] = [0, 1, 2]
# 31.2 ms ± 578 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%%timeit
df = pd.DataFrame([[0, 0, 0], [1, 1, 1], [2, 2, 2]])
l_data = []
l_label = []
for i in range(3, 1003):
l_data.append([0, 1, 2])
l_label.append(i)
df_concat = pd.concat([df, pd.DataFrame(zip(*l_data), index=df.index, columns=l_label)], axis=1)
# 3.56 ms ± 54.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
どちらの場合もまとめて追加する方が大幅に高速であることが分かる。