pandas.DataFrameに列や行を追加(assign, appendなど)

Modified: | Tags: Python, pandas

pandas.DataFrameに新たな列または行を追加する方法を説明する。

新規の列名・行名を指定して追加する、pandas.DataFrameassign()またはinsert()メソッドで追加する、pandas.concat()関数で連結する、といった方法がある。

以前あったappend()メソッドはバージョン1.4.0で非推奨(Deprecated)となり、2.0.0で削除された。

本記事のサンプルコードのpandasのバージョンは以下の通り。バージョンによって仕様が異なる可能性があるので注意。

import pandas as pd

print(pd.__version__)
# 2.0.3

pandas.DataFrameに列を追加

新規列名を指定して追加

[列名]で列を選択して値を代入できる。

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のラベルindexpandas.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.Seriesvalues属性で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_duplicatesTrueとすると追加可能だが、列名が重複してしまうのでおすすめしない。

# 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.DataFramepandas.Seriesを連結できる。

pandas.DataFramepandas.Seriesを連結することで新たな列を追加できる。

これまでの例ではpandas.Seriesを追加する場合、そのname属性は無視されたが、pandas.concat()関数で引数axis=1として横方向に連結すると、pandas.Seriesnamepandas.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.Seriespandas.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が代入される。ラベルを無視したい場合はvaluesnumpy.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.concat()関数を使うことが推奨されている。

concat()関数でSeries, DataFrameを縦に連結

pandas.concat()関数で複数のpandas.DataFramepandas.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.DataFramepandas.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.Seriesto_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)

どちらの場合もまとめて追加する方が大幅に高速であることが分かる。

関連カテゴリー

関連記事