pandasのappendができない? もとのDataFrameは変更されないので、返り値を使う

pandasのappendができない? もとのDataFrameは変更されないので、返り値を使う

pandasのappend関数を使うときに、たまに間違えて失敗するのでメモ。

pandasでDataFrameにappendするとき、連結後のDataFrameは返り値になっている。もとのDataFrameは変更されない。

以下、詳細。

準備

import pandas as pd
import numpy as np
pd.options.display.notebook_repr_html = False  # jupyter notebook上での出力形式を制御するために書いています。無くても動きます。
# 動作環境の確認
print(pd.__version__)
print(np.__version__)

# --------------------

1.1.2
1.19.1

Python標準のappend

pythonの普通のリストはappendがインプレースに行われる。

公式ドキュメント:5. データ構造 — Python 3.10.0b2 ドキュメント

my_list = [1, 3, 5]
my_list

# --------------------

[1, 3, 5]
my_list.append(7)
my_list

# --------------------

[1, 3, 5, 7]

このとき、append関数の返り値はNoneである。

ret = my_list.append(9)
print(ret)

# --------------------

None

pandasのappend

pandasではリストはappendがインプレースに行われない。返り値を取ってこなきゃいけない。
これはPython標準のappendとは違う動きなので、混同しないように注意する必要がある。

公式ドキュメント:pandas.DataFrame.append — pandas 1.4.1 documentation
仕様はあまりハッキリ書いていないが、 Returns(返り値)が新しいDataFrameであることに注意。

df = pd.DataFrame({'col_A': [1,2,3], 'col_B': ['p','q','r']})
df

# --------------------

   col_A col_B
0      1     p
1      2     q
2      3     r
df2 = pd.DataFrame({'col_A': [100, 200], 'col_B': ['x', 'y']})
df2

# --------------------

   col_A col_B
0    100     x
1    200     y
# 返り値は結合後のDataFrameとなる
df.append(df2)

# --------------------

   col_A col_B
0      1     p
1      2     q
2      3     r
0    100     x
1    200     y
# もとのDataFrameであるdfは変わっていない
df

# --------------------

   col_A col_B
0      1     p
1      2     q
2      3     r

appendにはinplace引数もない。
ので、dfを新しくしようと思ったら、結果を代入する必要がある。

df = df.append(df2)
df

# --------------------

   col_A col_B
0      1     p
1      2     q
2      3     r
0    100     x
1    200     y

特に、ループの中でうっかり間違えてappendを書くと、ループを抜けても何も追加されていないということが起きる。
(以下の書き方はDataFrameを1行ずつ追加していくものであり、動作が遅くなるのであまり良い方法ではない。説明用の例である)

# ループの中でappendする例
# 
df = pd.DataFrame()
for i in range(5):
    temp = {
        'num': i,
        'square': i**2,
        'cubic': i**3
    }
    df.append(temp, ignore_index=True)
    # appendの結果を代入していないので、dfはループの中で変わらない
# dfは空のDataFrameである
df

# --------------------

Empty DataFrame
Columns: []
Index: []
df = pd.DataFrame()
for i in range(5):
    temp = {
        'num': i,
        'square': i**2,
        'cubic': i**3
    }
    df = df.append(temp, ignore_index=True)
df

# --------------------

   cubic  num  square
0    0.0  0.0     0.0
1    1.0  1.0     1.0
2    8.0  2.0     4.0
3   27.0  3.0     9.0
4   64.0  4.0    16.0

おまけ:numpyのappend関数

NumPyは公式ドキュメントにこの動作が明示的に説明してあるから良いですね。

Note that append does not occur in-place: a new array is allocated and filled.
(拙訳: appendはインプレースではないことに注意してください。新しいNumPy配列が確保されて、そこに値が埋められます。)
https://numpy.org/doc/stable/reference/generated/numpy.append.html

NumPyのappend関数の動きはpandasと同様だ。すなわち、appendの動作はインプレースではなく、追加したあとのNumPy配列を使うには返り値を見る必要がある。

numpy_array = np.array([1, 3, 5])
numpy_array

# --------------------

array([1, 3, 5])
# 返り値は結合後のNumPy配列となる
other = np.array([2, 4])
np.append(numpy_array, other)

# --------------------

array([1, 3, 5, 2, 4])
# もとのNumPy配列であるnumpy_arrayは変わっていない
numpy_array

# --------------------

array([1, 3, 5])

それでは。