LoginSignup
3
3

More than 3 years have passed since last update.

【pandas】データフレームの各列の型が揃っているかどうかに寄って行アクセス時の挙動が変わる話

Posted at

pandas.DataFrameに値を代入するとしばしばこういう警告が出てくる→

C:\anaconda\lib\site-packages\ipykernel_launcher.py:3: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

「(参照ではなく)コピーしたものに値を代入してるよ。元のDataFrameは変更されないけど大丈夫?」ということ。
例えば以下のように.locで1行とって代入すると、.locの時点でコピーが返るのでrowは変更されるが元のDataFrameは変更されない:


df = pd.DataFrame({
    "string": ["aaa", "bbb", "ccc"], 
    "integer": [1, 2, 3]})
row = df.loc[0]
row["string"] = "hoge"

# コピーは変更される
print(row) 
# string     hoge
# string2     ddd
# Name: 0, dtype: object

# コピー元は変更されない
print(df)
# string    string2
# 0 aaa     ddd
# 1 bbb     eee
# 2 ccc     fff

よく使うテクニックとして、これを避けたければ次のよう1行で書けば良い:


df.loc[0, "string"] = "hoge"
print(df)
#   string  integer1
# 0   hoge         1
# 1    bbb         2
# 2    ccc         3

しかしながら、次のように全ての列が同じデータ型を持つ場合挙動が異なる。すなわち、.locは参照を返すので、rowの変更がDataFrameに反映される:

import pandas as pd
df = pd.DataFrame({"string": ["aaa", "bbb", "ccc"], "string2": ["ddd", "eee", "fff"]})
row = df.loc[0]
row["string"] = "hoge"

# 参照先が変更される
print(row)
# string     hoge
# string2     ddd
# Name: 0, dtype: object

# 参照元も変更される!
print(df)
#   string string2
# 0   hoge     ddd
# 1    bbb     eee
# 2    ccc     fff

理由は分からないが、効率性を求めた結果苦肉の策でこういう仕様にしたような感じがしておもしろい。

.locだけでなく、.iterrows()や.valuesなども以下のように同様の挙動を示す。

  • 型がそろっていないとコピーが返り、DataFrameは変更されない:
df = pd.DataFrame({
    "string": ["aaa", "bbb", "ccc"], 
    "integer": [1, 2, 3]})

# iterrows: 2行目のbbbをhogeに変更
for i, row in df.iterrows():
    if i==1:
        row[0] = "hoge"

# 1行目のaaaをbarに変更
val = df.values
val[0,0] = "bar"

# -> コピー元は変更されない
print(df)
#  string  integer1
# 0    aaa         1
# 1    bbb         2
# 2    ccc         3
  • 型がそろっていれば参照が返り、DataFrameは変更される:
df = pd.DataFrame({
    "string": ["aaa", "bbb", "ccc"], 
    "string2": ["ddd", "eee", "fff"]})

# iterrows: 2行目のbbbをhogeに変更
for i, row in df.iterrows():
    if i==1:
        row[0] = "hoge"

# values: 1行目のaaaをbarに変更
val = df.values
val[0,0] = "bar"

# -> 参照元は変更される
print(df)
#  string  string2
# 0    bar     ddd
# 1   hoge     eee
# 2    ccc     fff

これくらいの小さい例だとあまり怖さはないが、データフレームにあとから列を足して型の統一が崩れたりすると、それまで参照が返ると思い込んでいたところにコピーが返ってくるようになって見つけにくいバグになる。

「基本はコピー、型が揃っているときだけ参照が返る」という話でした。

参考:
https://stackoverflow.com/questions/47972633/in-pandas-does-iloc-method-give-a-copy-or-view

3
3
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
3
3