5
2

More than 3 years have passed since last update.

pandas.DataFrameへSeriesを列として代入する時には注意が必要

Posted at

pandasのデータフレームにシリーズを追加しようとするとjoinに近い挙動となるので気を付けましょうという記事。

まえおき

pandasでデータ処理を行なっているとき、データフレームに列を追加する処理は頻出です。
デーフレームに列を追加する時には大きく分けて二つのやり方があります。
1. 列名を指定して列追加
2. pd.DataFrame.assignメソッドで追加

1. 列名を指定して列追加

df['new_col'] = data

2. assignメソッドを用いて列追加

df.assign(new_col=data)

いずれの場合においても、値、サイズの等しいリスト・np.arraypd.Seriesなどを渡すことができます。

シリーズを代入したときに起こる直感に反した挙動

任意のデータフレームと、同じレコード数のシリーズを用意します。

df = pd.DataFrame(
    [[1,2,3], [4,5,6], [7,8,9]],
    columns=['a', 'b', 'c'],
    index=[1,2,3]
)
sr = pd.Series([-1, -2, -3])

df
#       a   b   c
# 1     1   2   3
# 2     4   5   6
# 3     7   8   9

sr
# 0   -1
# 1   -2
# 2   -3
# dtype: int64

dfに対して、新しい列'd'としてsrのデータを追加したいとき、以下のようにすると思います。

df = df.assign(d=sr)

このようなテーブルが作られることを期待します。

a b c d
1 1 2 3 -1
2 4 5 6 -2
3 7 8 9 -3

ところが実際はこのようなデータフレームが返ってきます。

a b c d
1 1 2 3 -2
2 4 5 6 -3
3 7 8 9 NaN

何が起こっているのか

改めてデータフレームとシリーズを見比べてみると、両者のindexが一致していません。
このようなデータでは、代入の場合においても結合のような挙動になっていることがわかりますね。

回避策

np.arrayとして渡すことで回避が可能です。

df.assign(new_col=new_series.values)

補足:公式ドキュメントを見る限り、valuesメソッドよりto_numpyメソッドを使用する方が推奨されるとのことです。
pandasの0.24にて追加されたExtensionAraryをはっきりと区別するためにこのようになっているみたいですね。

なぜこうなるのか

まず、pd.DataFrame.assignメソッドは、渡した値がcallableでない場合は、冒頭に示した1の処理が内部で呼び出されているだけです。したがって、今回のような現象はいずれの方法においても起こります。

(ちなみに「渡した値がcallableである」ときというのは、lambda式などでデータフレーム自身の列を呼び出す場合などが該当します。)1

df['X'] = hogehogeのように代入しようとすると、pd.DataFrame.__setitem__()が呼び出されます。コードをたどっていくと、以下のようなdocstringを発見しました。2

        """
        Add series to DataFrame in specified column.
        If series is a numpy-array (not a Series/TimeSeries), it must be the
        same length as the DataFrames index or an error will be thrown.
        Series/TimeSeries will be conformed to the DataFrames index to
        ensure homogeneity.
        """

つまり、渡したデータが、

  • データフレームとサイズの等しいnumpy-arrayである
    • そのままの並びで追加される
  • Seriesである
    • そのindexがデータフレームのものと一貫性を保つように追加される

というように処理されると述べられています。
さらにコードを追ってみると、データが追加される前にデータフレームのindexに沿うように並び替えられていることがわかります。3

今回の現象は、シリーズを配列と同じように考えてしまっていたのが原因でした。
下記のように、そもそもシリーズのサイズは加えるデータフレームのレコードと一致させる必要すらなく、リストや配列と全く別物であることもわかりました。

df.assign(
    x=pd.Series([3], index=[2])
)
#       a   b   c   x
# 1     1   2   3   NaN
# 2     4   5   6   3.0
# 3     7   8   9   NaN

まとめ

同じサイズの1次元データだからといって、リストや配列と同じようにデータフレームに追加しないようにしましょう。
シリーズをデータフレームの列に代入する場合は、サイズが異なる場合でもエラーにならずに処理が進んでしまうため、気づかぬうちにバグを生んでしまっている危険性が高まりそうです。必ずnumpy-arrayに変換して代入処理を行おうと自戒しました。

※この検証はpandas 1.0.3で行っていますが、以前のバージョンでも同様の挙動でした。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2