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를 굳이 발생시키지 않고 우리가 원하는 동작이 아니다라고 경고를 출력하는 것이죠.
해결 방법은 경고 구문에도 나와있듯이 .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 |