본문 바로가기

Computer/Pandas

Pandas - SettingWithCopyWarning

반응형

Pandas를 사용해 데이터를 처리하다보면 생성한 데이터프레임에 대해 특정 조건에 따라 값을 재할당하는 경우가 많습니다. 예를 들어 다음과 같은 데이터프레임에 대해서 z 칼럼이 50 미만인 값을 0으로 치환하고 싶다고 합시다.

>>> data = {"x": 2**np.arange(5),
            "y": 3**np.arange(5),
            "z": np.array([45, 98, 24, 11, 64])}
>>> df = pd.DataFrame(data=data, index=["a", "b", "c", "d", "e"])
>>> df
    x   y   z
a   1   1  45
b   2   3  98
c   4   9  24
d   8  27  11
e  16  81  64

일반적인 방법으로는 df['z'] < 50 구문으로 boolean 마스크를 생성하고 필터링을 수행하겠죠. 필터링을 하면 z가 50보다 작은 a,c,d 열만 필터됩니다.

>>> mask = df["z"] < 50
>>> mask
a     True
b    False
c     True
d     True
e    False
Name: z, dtype: bool

>>> df[mask]
   x   y   z
a  1   1  45
c  4   9  24
d  8  27  11

하지만 필터링된 데이터프레임에 대해서 z 칼럼을 0으로 할당하면 SettingWithCopyWarning이 발생하고 df 데이터프레임의 z 칼럼은 변하지 않습니다. Error가 아닌 Warning이므로 코드가 멈추지는 않지만 우리가 본래 원했던 df의 z 칼럼을 특정 조건에 맞춰 값을 재할당하는 동작은 일어나지 않았습니다. 왜일까요?

>>> df[mask]['z'] = 0
<input>:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

>>> df
    x   y   z
a   1   1  45
b   2   3  98
c   4   9  24
d   8  27  11
e  16  81  64

Figure 1은 df[mask]['z'] = 0 구문의 상황을 나타냅니다. df[mask]는 mask에 해당하는 df의 복사본인 보라색 데이터프레임을 생성합니다. 따라서 df[mask]['z'] = 0은 복사본의 z 칼럼 값을 바꾸지 원본인 df의 z 칼럼 값을 바꾸지 않습니다. 따라서 pandas는 error를 굳이 발생시키지 않고 우리가 원하는 동작이 아니다라고 경고를 출력하는 것이죠.

Figure 1

해결 방법은 경고 구문에도 나와있듯이 .iloc, .loc, .at, .iat 같은 접근자를 이용하는 겁니다.

A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
>>> df.loc[mask, "z"] = 0
>>> df
    x   y   z
a   1   1   0
b   2   3  98
c   4   9   0
d   8  27   0
e  16  81  64

하지만 접근자를 이용하더라도 다음과 같이 chaining 식으로 코드를 작성하면 SettingWithCopyWarning이 발생합니다. df.loc[mask] 를 수행하면 df의 복사본이 리턴되어버리므로 경고가 발생하는 것이죠.

>>> df.loc[mask]["z"] = 0
<input>:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

SettingWithCopyWarning을 피하기 위해서는 결국 df의 복사본에 대해 assignment를 수행하지 않아야 합니다. Pandas가 어떠한 경우에 복사본에 대해 assignment를 수행하는지는 매우 복잡하나 일반적으로는 1) df['z'][mask], df.loc[mask]['z']와 같은 chained assignment를 쓰지 않으면서, 2) 접근자를 이용해 df.loc[mask, 'z']와 같은 single assignment를 수행해야 합니다.


SettingWithCopyWarning은 어디까지나 경고이기 때문에 대수롭지 않게 넘어가거나 놓치게되면 전체 코드는 동작은 할 것이나 원하는 결과는 얻지 못할겁니다. 이를 방지하려면 pandas의 set_option 기능을 이용하여 디폴트로 정해진 warning을 에러로 설정합니다.

>>> pd.get_option('mode.chained_assignment')
'warn'

>>> pd.set_option("mode.chained_assignment", "raise")

옵션을 재설정하고 chained assignment를 수행하면 에러가 발생합니다.

>>> df[mask]['z'] = 0
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "C:\Users\yukua\PycharmProjects\HongLearning\venv\lib\site-packages\pandas\core\frame.py", line 3607, in __setitem__
    self._set_item(key, value)
  File "C:\Users\yukua\PycharmProjects\HongLearning\venv\lib\site-packages\pandas\core\frame.py", line 3792, in _set_item
    self._set_item_mgr(key, value)
  File "C:\Users\yukua\PycharmProjects\HongLearning\venv\lib\site-packages\pandas\core\frame.py", line 3757, in _set_item_mgr
    self._check_setitem_copy(stacklevel=5)
  File "C:\Users\yukua\PycharmProjects\HongLearning\venv\lib\site-packages\pandas\core\generic.py", line 3930, in _check_setitem_copy
    raise com.SettingWithCopyError(t)
pandas.core.common.SettingWithCopyError: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
반응형

'Computer > Pandas' 카테고리의 다른 글

여러 데이터 행에 함수 적용하기  (0) 2022.10.13
Pandas groupby (3)  (0) 2021.08.14
Pandas groupby (2)  (0) 2021.08.07
Pandas groupby (1)  (0) 2021.08.07
Pandas Categorical Data  (0) 2021.08.01