Pandas 오류 : SettingWithCopyWarning
오류 코드
C:\Users\@@@\PycharmProjects\back_Test\algorithm\algorithm_1.py:254: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
self.trade_trace.loc[iloc_num]['profit_rate'] = profit_rate
또는
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
iloc._setitem_with_indexer(indexer, value)
해결 방법을 사용하기 전에 앞서 일단 위의 두 오류의 차이를 한 번 살펴보아야 한다. 위의 오류는 작성했던 코드를 나타내주지만, 아래의 오류는 오류 내용을 알려준다. 두 오류의 차이는 코드가 실행된 후에 발생하는 오류인가 아니면 코드가 실행되지 않고 발생하는 오류인가의 차이이다. 실행된 후 발생하는 오류가 아래의 오류이고 실행되지 않고 발생하는 오류는 위의 오류이다.
- self.trade_trace.loc[iloc_num]['profit_rate'] = profit_rate
- iloc._setitem_with_indexer(indexer, value)
해결 방법(두 번째 방법을 권장)
해결 방법을 설명하기 전에 앞서, 각각의 변수에는 다음과 같은 데이터들이 입력되어 있다. 여기서 code 칼럼의 데이터값이 000020인 데이터열(인덱스 0번 자리)에서 buy 칼럼에 'no'라는 값을 입력해볼 예정이다.
>>> df
code date buy sell
0 000020 20200101
1 000040 2020102
2 000060 20200103
>>> dfd ## dfd = df.copy() dfd는 df의 복사본임
code date buy sell
0 000020 20200101
1 000040 2020102
2 000060 20200103
첫 번째 방법, 별도의 변수를 생성해서 데이터 입력 시의 조건을 별도의 변수에 저장하고 그를 바탕으로 인덱싱
이에 대해 부연설명을 해보자면, 일단 code 칼럼의 데이터가 000020인 데이터열의 buy 칼럼에는 no를 입력해보는 것이다. 이전에 작성했던 코드와 다른 코드를 작성할 예정이고, 그를 비교해보도록 하자.
※ 일단 이 방법으로는 문제가 해결된 부분도 있지만 해결되지 않은 부분도 있었음
## 이전에 사용한 방법
dfd.loc[0]['buy'] = 'no'
## 새로운 방법
code_data = dfd['code'].str.startswith('000020') ## dfd에 칼럼명이 'code'이고 데이터값이 '000020'인 경우 True를 반환
dfd.loc[code_df, 'buy'] = 'no' ## code_data 값이 True인 경우 'buy' 칼럼에 'no'를 입력
두 번째 방법, at을 통해 데이터를 보다 직접적으로 입력(추천)
판다스는 loc에 대응하는 at과 iloc에 대응하는 iat 메서드를 별도로 지원하고 있다. SettingWithCopy 오류는 바로 여기서의 at을 통해 데이터를 보다 직접적으로 입력하는 방식을 사용함으로써 문제를 해결할 수 있었다.
- 사용 방법 : DataFrame_Name.at(index_number, column_name) = insert_data
## 이전에 사용한 방법
dfd.loc[0]['buy'] = 'no'
## 새로운 방법 (복사본을 만들 필요도 없음)
df.at[0, 'buy'] = 'no'
해결 과정
① 파이썬 IDLE 프로그램 내에서 작업해본 결과, 문제 없이 데이터가 잘 입력되는 것을 확인.
+ 심지어 파이참 콘솔에서 동일한 작업을 수행해도 동일한 결과를 얻을 수 있었음.
import pandas as pd
## 데이터프레임 기본 틀 생성
aa = {'code':[], 'date':[], 'buy':[]}
df = pd.DataFrame(aa, columns=['code', 'date', 'buy'])
## 기본 틀에 입력할 데이터 생성
temporary_ = {'code':['000020'], 'date':['20210101']}
temporary_df = pd.DataFrame(temporary_, columns=['code', 'date'])
## 데이터를 기본 틀에 입력(append)
df = df.append(temporary_df)
## 원하는 지점에 데이터 입력하기
df.buy.loc[0] = 'no'
## 실행 결과
>>> print(df)
code date buy
0 000020 20210101 no
② 판다스 문서를 뒤져봄 : 아래는 문서 전문 번역본
Returning a view versus a copy
판다스 객체 내에서 데이터값을 설정할 때, Chained indexing 이라는 문제가 발생하지 않도록 주의해야 한다. 아래가 그 예시이다. dfmi라는 데이터프레임을 생성한 후, 두 가지 방법을 사용해서 데이터에 접근해보도록 하자.
dfmi = pd.DataFrame([list('abcd'),
list('efgh'),
list('ijkl'),
list('mnop')],
columns=pd.MultiIndex.from_product([['one', 'two'],
['first', 'second']]))
dfmi
Out[355]:
one two
first second first second
0 a b c d
1 e f g h
2 i j k l
3 m n o p
## 첫 번째 방법
dfmi['one']['second']
0 b
1 f
2 j
3 n
Name: second, dtype: object
## 두 번째 방법
dfmi.loc[:, ('one', 'second')]
0 b
1 f
2 j
3 n
Name: (one, second), dtype: object
두 가지 방법 모두 동일한 결과물을 가져오는데, 도대체 어떤 방법을 사용해야 할까? 여기서는 .loc을 사용하고 두 번째 방법이 첫 번째 방법(대괄호로 묶여 있는)에 비해 훨씬 권장되는 원리를 이해하는 것이 좋다. 아래의 내용을 살펴보자.
첫 번째 방법( dfmi['one']['second'] )의 경우, 가장 먼저 dfmi['one']['second'] 라는 코드에서 dfmi['one'] 이라는 코드를 먼저 실행한다. 즉 코드 중 먼저 입력한 칼럼의 이름에 따라 칼럼을 선택하고, 단일로 인덱싱된 데이터프레임을 반환한다. (이는 한 줄의 데이터를 반환한다는 것과 동일하다) 그 다음, 또 다른 곳에서는 dfmi_with_one['second']는 'second'에 의해 인덱싱된 데이터 열을 선택한다. 에 의해 인덱싱된 시리즈(데이터)를 선택한다. 'second'.dfmi_with_one
판다스는 이러한 동작을 서로 다른 개별적인 작업으로 보기 때문에, 이 첫 번째 작업은 dfmi_with_one 이라는 변수로 표시된다. 대표적인 예시로는 데이터를 개별적으로 호출하는 것이 있다. 다시 말해 첫 번째 방법( dfmi['one']['second'] )은 선형 작업으로 처리(순서대로 처리한다는 말)하게 되므로 어떤 작업이 완료된 후에 다른 작업이 시행되는 것이다.
이는 데이터를 개별적으로 호출하는 것과 다르게 (slice(None), ('one', 'second'))와 같이 중첩 튜플 형태(그냥 튜플이라고 이해하면 됨)로 데이터를 전달하는 두 번째 방법( df.loc[:, ('one', 'second')] )과는 완벽하게 대조된다. 이 두 번째 방법은 작업 속도가 조금 더 빨라질 수 있는 동시에, 원할 경우 두 축 모두에 인덱싱(자료에 접근)할 수 있다.
체인 인덱싱을 사용할 때 데이터 입력이 실패하는 이유는 무엇인가?
(Why does assignment fail when using chained indexing?)
앞서 살펴봤던 내용은 사실 오류와는 전혀 무관한 내용이고 작업 속도와 관련된 내용일 뿐이다. 그렇다면 무엇이 SettingWithCopy 경고를 발생시키는가? 판다스는 단순하게 작업 시간이 몇 초 정도가 더 길어진다고 이 오류를 띄우도록 설정하지 않았다. 하지만 chained indexing을 바탕으로 얻어온 데이터에 어떤 데이터를 입력하는 것이 예상하지 못했던 결과를 가져올 수 있다는 문제점이 있음을 확인했다. 아래의 두 코드를 확인해보자.
## 첫 번째 방법
dfmi['one']['second'] = value
# becomes
dfmi.__getitem__('one').__setitem__('second', value)
## 두 번째 방법
dfmi.loc[:, ('one', 'second')] = value
# becomes
dfmi.loc.__setitem__((slice(None), ('one', 'second')), value)
위의 두 코드는 서로 다른 방법으로 데이터를 다루고 있음을 확인할 수 있다. 즉, 첫 번째 방법( dfmi['one']['second'] )에서는 __getitem__ 과 __setitem__ 이 모두 등장했지만, 두 번째 방법( dfmi.loc[:, ('one', 'second')] )에서는 __getitem__ 을 찾아볼 수 없다. 이 단순한 예시가 아닌 다른 경우에도 원본의 데이터를 반환할지 아니면 복사본의 데이터를 반환할지를 예측하는 것은 상당히 어려운 일이다. 그러므로 __setitem__은 dfmi를 수정하거나 작업을 완료한 후 ㅁ임시적으로 생성된 객체를 삭제한다. 이것이 바로 SettingWithCopy가 당신에게 경고하는 것이다.
때때로 SettingWithCopy는 명백하지 않은 인덱싱 작업이 진행되고 있을 때에도 발생할 수 있는데, 이 때에도 그 문제를 찾아내도록 SettingWithCopy가 제작되었다. 판다스는 아마도 아래와 같은 코드에 대해 경고하고자 했을 것이다.
def do_something(df):
foo = df[['bar', 'baz']] ## foo라는 데이터는 복사본인가 원본인가? 그 누구도 그건 모른다.
# 많은 데이터들이 입력될 것인데,
# 우리는 원래의 df를 수정할지 말지를 전혀 알 수 없다!
foo['quux'] = value
return foo
이외에 발생하는 또 다른 오류를 사전에 조절하기
(Evaluation order matters)
chained indexing을 사용할 때, 인덱싱을 사용하기 위해 제작한 코드의 순서와 유형에 따라서 결과가 원본 개체로부터 떨어져 나온 값(slice)인지 또는 떨어져나온 값의 복사본인지가 결정된다.
판다스는 일부의 데이터 복사본에 데이터를 입력하는 것이 종종 의도적이지 않은 경우가 발생할 수 있기 때문에 SettingWithCopy 라는 오류를 발생시키도록 하고 있다. 하지만 chained indexing을 사용함으로써 발생한 실수는 slice가 예상되는 지점에서 복사본을 반환하도록 한다.
만약 chianed indexing 구조의 코드를 사용할 때 오류가 발생한다면, mode.chained_assignmen 라는 값에 아래와 같은 세 가지 값을 입력함으로써 사용할 수 있다. (이는 .loc을 사용할 때와 .iloc을 사용할 때를 구분하지 않고 적용된다.)
- 'warn' : 기본값이며, SettingWithWarning이 출력됨
- 'raise' : SettingWithCopyException이 출력됨
- 'None' : 경고가 출력되지 않음
pd.set_option('mode.chained_assignment','warn')
pd.set_option('mode.chained_assignment','raise')
pd.set_option('mode.chained_assignment', None)
아래의 코드는 다양한 데이터에 접근하고자 할 때 .loc을 사용하고자 할 경우 권장되는 방법이다.
dfc = pd.DataFrame({'a': ['one', 'one', 'two',
'three', 'two', 'one', 'six'],
'c': np.arange(7)})
## dfc를 대상으로 복사본을 생성하여 dfd 변수에 입력
dfd = dfc.copy()
# 마스크(mask)를 이용해 복수의 데이터를 설정
mask = dfd['a'].str.startswith('o')
dfd.loc[mask, 'c'] = 42
dfd
a c
0 one 42
1 one 42
2 two 2
3 three 3
4 two 4
5 one 42
6 six 6
# 개별 데이터를 설정
dfd = dfc.copy()
dfd.loc[2, 'a'] = 11
dfd
a c
0 one 0
1 one 1
2 11 2
3 three 3
4 two 4
5 one 5
6 six 6
아래의 예시는 권장되지 않는 방법이다.
## 권장되지 않는 방법
dfd = dfc.copy()
dfd['a'][2] = 111
dfd
a c
0 one 0
1 one 1
2 111 2
3 three 3
4 two 4
5 one 5
6 six 6
방법 요약
- dfc(원래의 데이터)를 대상으로 dfd(복사본)을 생성하여 데이터에 접근한다.
- ① 데이터 접근 시 .loc을 이용해 인덱스 번호를 입력한 후 칼럼명을 사용한다.
- ② 원하는 데이터를 변수로 설정해준다.
'AUTO TRADE > Error Data' 카테고리의 다른 글
당신이 좋아할만한 콘텐츠
-
[PyCharm] CreateProcess error=2, 지정된 파일을 찾을 수 없습니다. 2022.12.10
-
WorkBench 오류 : Error Code:2013. Lost connection to MySQL server during query 2022.01.05
-
WorkBench 오류 : mysql.connector.errors.DatabaseError: 1812 (HY000): Tablespace is missing for table "" 2022.01.05
-
WorkBench 오류 : ERROR CODE : 3679. Schema directory "\XXXX\" does not exist 2022.01.05
소중한 공감 감사합니다