배경
방금 팬더를 0.11에서 0.13.0rc1로 업그레이드했습니다. 이제 응용 프로그램이 많은 새로운 경고를 표시합니다. 그들 중 하나는 다음과 같습니다.
E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
정확히 무슨 뜻인지 알고 싶습니까? 무언가를 바꿔야합니까?
사용을 주장하면 경고를 어떻게 중단해야 quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
합니까?
오류를주는 기능
def _decode_stock_quote(list_of_150_stk_str):
"""decode the webpage and return dataframe"""
from cStringIO import StringIO
str_of_all = "".join(list_of_150_stk_str)
quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
quote_df['TClose'] = quote_df['TPrice']
quote_df['RT'] = 100 * (quote_df['TPrice']/quote_df['TPCLOSE'] - 1)
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
quote_df['TAmt'] = quote_df['TAmt']/TAMT_SCALE
quote_df['STK_ID'] = quote_df['STK'].str.slice(13,19)
quote_df['STK_Name'] = quote_df['STK'].str.slice(21,30)#.decode('gb2312')
quote_df['TDate'] = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
return quote_df
더 많은 오류 메시지
E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE
E:\FinReporter\FM_EXT.py:450: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TAmt'] = quote_df['TAmt']/TAMT_SCALE
E:\FinReporter\FM_EXT.py:453: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
quote_df['TDate'] = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
답변
이 SettingWithCopyWarning
플래그로 생성 된 잠재적으로 첫 번째 선택은 반환 특히, 항상 예상대로 작동하지 않는 다음과 같은 “체인”과제, 혼란 사본 . [ 배경 토론 은 GH5390 및 GH5597 을 참조하십시오 .]
df[df['A'] > 2]['B'] = new_val # new_val not set in df
이 경고는 다음과 같이 다시 작성하라는 제안을 제공합니다.
df.loc[df['A'] > 2, 'B'] = new_val
그러나 이것은 용도에 맞지 않습니다. 이는 다음과 같습니다.
df = df[df['A'] > 2]
df['B'] = new_val
참조를 덮어 쓰고 있기 때문에 원래 프레임으로 다시 쓰는 쓰기에 대해서는 신경 쓰지 않지만,이 패턴을 첫 번째 체인 할당 예제와 구별 할 수는 없습니다. 따라서 (가양 성) 경고입니다. 더 자세히 읽으려면 인덱싱 문서 에서 오 탐지 가능성을 해결하십시오 . 다음 할당으로이 새로운 경고를 안전하게 비활성화 할 수 있습니다.
import pandas as pd
pd.options.mode.chained_assignment = None # default='warn'
답변
SettingWithCopyWarning
판다에서 다루는 방법 ?
이 게시물은 독자,
- 이 경고의 의미를 이해하고 싶습니다
- 이 경고를 억제하는 다른 방법을 이해하고 싶습니다
- 앞으로이 경고를 피하기 위해 코드를 개선하고 모범 사례를 따르는 방법을 이해하고 싶습니다.
설정
np.random.seed(0)
df = pd.DataFrame(np.random.choice(10, (3, 5)), columns=list('ABCDE'))
df
A B C D E
0 5 0 3 3 7
1 9 3 5 2 4
2 7 6 8 8 1
무엇입니까 SettingWithCopyWarning
?
이 경고를 처리하는 방법을 이해하려면 의미와 의미를 이해하는 것이 중요합니다.
DataFrames를 필터링 할 때 내부 레이아웃과 다양한 구현 세부 사항에 따라 프레임을 슬라이스 / 인덱싱하여 뷰 또는 복사본 을 반환 할 수 있습니다 . “보기”는 용어에서 알 수 있듯이 원래 데이터에 대한보기이므로보기를 수정하면 원래 개체가 수정 될 수 있습니다. 반면 “복사”는 원본의 데이터를 복제 한 것으로, 사본을 수정해도 원본에는 영향을 미치지 않습니다.
다른 답변에서 언급했듯이 SettingWithCopyWarning
는 “체인 할당”작업에 플래그를 지정하기 위해 만들어졌습니다. df
위의 설정을 고려하십시오 . “A”열의 값이> 5 인 “B”열의 모든 값을 선택한다고 가정합니다. Pandas를 사용하면 다른 방법보다 더 정확한 방법으로이 작업을 수행 할 수 있습니다. 예를 들어
df[df.A > 5]['B']
1 3
2 6
Name: B, dtype: int64
과,
df.loc[df.A > 5, 'B']
1 3
2 6
Name: B, dtype: int64
이들은 동일한 결과를 반환하므로이 값만 읽는 경우 아무런 차이가 없습니다. 그래서 무엇이 문제입니까? 체인 할당의 문제점은 일반적으로보기 또는 사본이 리턴되는지 여부를 예측하기 어렵 기 때문에 값을 다시 지정하려고 할 때 문제가된다는 것입니다. 이전 예제를 빌드하려면 인터프리터가이 코드를 실행하는 방법을 고려하십시오.
df.loc[df.A > 5, 'B'] = 4
# becomes
df.__setitem__((df.A > 5, 'B'), 4)
에 대한 단일 __setitem__
호출로 df
. OTOH,이 코드를 고려하십시오 :
df[df.A > 5]['B'] = 4
# becomes
df.__getitem__(df.A > 5).__setitem__('B", 4)
이제 __getitem__
보기 또는 사본을 리턴 했는지 여부에 따라 __setitem__
조작 이 작동하지 않을 수 있습니다 .
일반적으로 loc
레이블 기반 할당 및 iloc
정수 / 위치 기반 할당에는 스펙이 항상 원본에서 작동 함을 보증하므로 사용해야합니다. 또한 단일 셀을 설정하려면 at
및 을 사용해야합니다 iat
.
자세한 내용은 설명서를 참조하십시오 .
참고
모든 부울 인덱싱 작업loc
은로도 수행 할 수 있습니다iloc
. 유일한 차이점은iloc
인덱스의 정수 / 위치 또는 부울 값의 numpy 배열 및 열의 정수 / 위치 인덱스 가 필요하다는 것 입니다.예를 들어
df.loc[df.A > 5, 'B'] = 4
nas라고 쓸 수 있습니다
df.iloc[(df.A > 5).values, 1] = 4
과,
df.loc[1, 'A'] = 100
로 쓸 수 있습니다
df.iloc[1, 0] = 100
등등.
경고를 억제하는 방법을 알려주십시오!
의 “A”열에 대한 간단한 작업을 고려하십시오 df
. “A”를 선택하고 2로 나누면 경고가 발생하지만 작업은 작동합니다.
df2 = df[['A']]
df2['A'] /= 2
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/IPython/__main__.py: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
df2
A
0 2.5
1 4.5
2 3.5
이 경고를 직접하는 몇 가지 방법이 있습니다.
-
을 만들다
deepcopy
df2 = df[['A']].copy(deep=True) df2['A'] /= 2
-
변화
pd.options.mode.chained_assignment
에 설정할 수 있습니다None
,"warn"
또는"raise"
."warn"
기본값입니다.None
완전히 경고를 억제하며,"raise"
을 던질 것이다SettingWithCopyError
겪고에서 작업을 방지.pd.options.mode.chained_assignment = None df2['A'] /= 2
면 @ 피터 코멘트에 비 간섭 적 (에서 수정 모드로 변경하는 좋은 방법 해낸 이 요점을 동안 만이 필요로 모드를 설정, 컨텍스트 관리자를 사용), 다시로 리셋을 완료되면 원래 상태.
class ChainedAssignent: def __init__(self, chained=None): acceptable = [None, 'warn', 'raise'] assert chained in acceptable, "chained must be in " + str(acceptable) self.swcw = chained def __enter__(self): self.saved_swcw = pd.options.mode.chained_assignment pd.options.mode.chained_assignment = self.swcw return self def __exit__(self, *args): pd.options.mode.chained_assignment = self.saved_swcw
사용법은 다음과 같습니다.
# some code here
with ChainedAssignent():
df2['A'] /= 2
# more code follows
또는 예외를 제기하기 위해
with ChainedAssignent(chained='raise'):
df2['A'] /= 2
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
“XY 문제”: 내가 뭘 잘못하고 있니?
많은 경우에, 사용자는이 예외가 처음 발생한 이유를 완전히 이해하지 않고이 예외를 억제하는 방법을 찾으려고 시도합니다. 이것은 XY 문제 의 좋은 예입니다. 여기서 사용자는 실제로 “뿌리”문제인 “X”의 증상 인 “Y”문제를 해결하려고 시도합니다. 이 경고가 발생하는 일반적인 문제를 바탕으로 질문이 제기되고 솔루션이 제시됩니다.
질문 1
DataFrame이 있습니다df A B C D E 0 5 0 3 3 7 1 9 3 5 2 4 2 7 6 8 8 1
col “A”> 5 ~ 1000의 값을 할당하려고합니다. 예상 출력은 다음과 같습니다.
A B C D E 0 5 0 3 3 7 1 1000 3 5 2 4 2 1000 6 8 8 1
이것을하는 잘못된 방법 :
df.A[df.A > 5] = 1000 # works, because df.A returns a view
df[df.A > 5]['A'] = 1000 # does not work
df.loc[df.A 5]['A'] = 1000 # does not work
올바른 방법으로 loc
:
df.loc[df.A > 5, 'A'] = 1000
질문 2 1
셀 (1, ‘D’)의 값을 12345로 설정하려고합니다. 예상 출력은 다음과 같습니다.A B C D E 0 5 0 3 3 7 1 9 3 5 12345 4 2 7 6 8 8 1
이 셀에 액세스하는 다른 방법을 시도했습니다 (예 🙂
df['D'][1]
. 가장 좋은 방법은 무엇입니까?1.이 질문은 특별히 경고와 관련이 없지만 나중에 경고가 발생할 수있는 상황을 피하기 위해이 특정 작업을 올바르게 수행하는 방법을 이해하는 것이 좋습니다.
다음 방법 중 하나를 사용하여이를 수행 할 수 있습니다.
df.loc[1, 'D'] = 12345
df.iloc[1, 3] = 12345
df.at[1, 'D'] = 12345
df.iat[1, 3] = 12345
질문 3
일부 조건에 따라 값의 하위 집합을 설정하려고합니다. DataFrame이 있습니다A B C D E 1 9 3 5 2 4 2 7 6 8 8 1
“C”== 5가되도록 “D”의 값을 123에 할당하고 싶습니다.
df2.loc[df2.C == 5, 'D'] = 123
어느 것이 좋아 보이지만 여전히 받고 있습니다
SettingWithCopyWarning
! 이 문제를 어떻게 해결합니까?
실제로 파이프 라인에서 코드가 높기 때문일 수 있습니다. df2
더 큰 무언가로 만들었습니까?
df2 = df[df.A > 5]
? 이 경우 부울 인덱싱은 뷰를 반환하므로 df2
원본을 참조합니다. 당신이해야 할 일은 사본에 할당 df2
하는 것입니다 .
df2 = df[df.A > 5].copy()
# Or,
# df2 = df.loc[df.A > 5, :]
질문 4
열 “C”를 그 자리에서 삭제하려고합니다.A B C D E 1 9 3 5 2 4 2 7 6 8 8 1
그러나 사용
df2.drop('C', axis=1, inplace=True)
던졌습니다
SettingWithCopyWarning
. 왜 이런 일이 발생합니까?
이는 다음 df2
과 같은 다른 슬라이스 작업에서보기로 작성 되었기 때문입니다.
df2 = df[df.A > 5]
여기서 해결책은 이전과 같이 또는를 사용 하는 것 copy()
입니다 .df
loc
답변
일반적으로 점은 SettingWithCopyWarning
그들이 그 사용자 (특히 새로운 사용자) 보여주는 것입니다 수 있습니다 그들이 생각하는 원본을 복사에서 작동하지 수 있습니다. 가 있습니다 오탐 (false positive)은 (당신이 무엇을하고 있는지 알고있는 경우 IOW가 될 수 확인 ). 한 가지 가능성은 @Garrett이 제안한대로 (기본적으로 경고 ) 경고를 끄는 것입니다.
다른 옵션은 다음과 같습니다.
In [1]: df = DataFrame(np.random.randn(5, 2), columns=list('AB'))
In [2]: dfa = df.ix[:, [1, 0]]
In [3]: dfa.is_copy
Out[3]: True
In [4]: dfa['A'] /= 2
/usr/local/bin/ipython:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
#!/usr/local/bin/python
해당 객체에 대해is_copy
플래그를로 설정하면 False
검사를 효과적으로 해제 할 수 있습니다 .
In [5]: dfa.is_copy = False
In [6]: dfa['A'] /= 2
명시 적으로 복사하면 더 이상 경고가 발생하지 않습니다.
In [7]: dfa = df.ix[:, [1, 0]].copy()
In [8]: dfa['A'] /= 2
OP가 위에 보여주는 코드는 합법적이며 아마도 내가하는 일은 기술적 으로이 경고의 경우이며 오 탐지는 아닙니다. 또 다른 방법은 없는 경고가가를 통해 선택 작업을 수행하는 것 reindex
, 예를 들어,
quote_df = quote_df.reindex(columns=['STK', ...])
또는,
quote_df = quote_df.reindex(['STK', ...], axis=1) # v.0.21
답변
팬더 데이터 프레임 복사 경고
가서 이렇게 할 때 :
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
pandas.ix
이 경우 새로운 독립형 데이터 프레임을 반환합니다.
이 데이터 프레임에서 변경하기로 결정한 값은 원래 데이터 프레임을 변경하지 않습니다.
이것은 팬더가 당신에게 경고하려고하는 것입니다.
왜 .ix
나쁜 생각 입니까
.ix
객체는 하나 이상의 일을하려고하고 깨끗한 코드에 대해 아무것도 읽기 누군가를 위해, 이것은 강한 냄새입니다.
이 데이터 프레임이 주어지면 :
df = pd.DataFrame({"a": [1,2,3,4], "b": [1,1,2,2]})
두 가지 행동 :
dfcopy = df.ix[:,["a"]]
dfcopy.a.ix[0] = 2
동작 하나 : dfcopy
이제 독립형 데이터 프레임입니다. 변경해도 변경되지 않습니다df
df.ix[0, "a"] = 3
동작 2 : 원본 데이터 프레임이 변경됩니다.
사용 .loc
하는 대신
팬더 개발자들은 그 .ix
물체가 상당히 냄새가 나는 것으로 생각하여 데이터의 접근과 할당을 돕는 두 개의 새로운 물체를 만들었습니다. (다른 존재 .iloc
)
.loc
데이터 사본을 작성하지 않기 때문에 더 빠릅니다.
.loc
기존의 데이터 프레임을 수정하여 메모리 효율성이 향상되었습니다.
.loc
예측 가능하며 한 가지 동작이 있습니다.
해결책
코드 예제에서 수행하는 작업은 많은 열이 포함 된 큰 파일을로드 한 다음 더 작게 수정하는 것입니다.
이 pd.read_csv
기능을 사용하면 많은 작업을 수행 할 수 있으며 파일을 훨씬 빠르게로드 할 수 있습니다.
그래서 이것을하는 대신
quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
이 작업을 수행
columns = ['STK', 'TPrice', 'TPCLOSE', 'TOpen', 'THigh', 'TLow', 'TVol', 'TAmt', 'TDate', 'TTime']
df = pd.read_csv(StringIO(str_of_all), sep=',', usecols=[0,3,2,1,4,5,8,9,30,31])
df.columns = columns
관심있는 열만 읽고 이름을 올바르게 지정합니다. .ix
마법의 일을하기 위해 사악한 물건을 사용할 필요가 없습니다 .
답변
여기서 나는 질문에 직접 대답합니다. 그것을 다루는 방법?
.copy(deep=False)
슬라이스 한 후에 확인하십시오 . pandas.DataFrame.copy를 참조하십시오 .
잠깐만, 슬라이스가 사본을 반환하지 않습니까? 결국, 이것은 경고 메시지가 말하려는 것입니까? 긴 대답을 읽으십시오.
import pandas as pd
df = pd.DataFrame({'x':[1,2,3]})
경고가 나타납니다.
df0 = df[df.x>2]
df0['foo'] = 'bar'
이것은하지 않습니다 :
df1 = df[df.x>2].copy(deep=False)
df1['foo'] = 'bar'
모두 df0
와 것은 df1
있는 DataFrame
객체하지만 그들에 대해 어떤 경고를 인쇄 할 수 팬더를 가능하게 다르다. 그것이 무엇인지 알아 봅시다.
import inspect
slice= df[df.x>2]
slice_copy = df[df.x>2].copy(deep=False)
inspect.getmembers(slice)
inspect.getmembers(slice_copy)
선택한 diff 도구를 사용하면 몇 가지 주소를 넘어서는 중요한 차이점은 다음과 같습니다.
| | slice | slice_copy |
| _is_copy | weakref | None |
경고 여부를 결정하는 방법은 DataFrame._check_setitem_copy
어떤 검사 _is_copy
입니다. 그래서 여기 있습니다. copy
DataFrame이 아닌지 확인 하십시오 _is_copy
.
경고는을 (를) 사용하도록 제안 .loc
하지만 .loc
프레임에서 사용 하는 경우 _is_copy
에도 여전히 동일한 경고가 표시됩니다. 오해? 예. 성가신? 물론이지. 도움이 되셨습니까? 잠재적으로 체인 할당이 사용될 때. 그러나 체인 할당을 올바르게 감지 할 수 없으며 경고를 무차별 적으로 인쇄합니다.
답변
이 주제는 실제로 Pandas와 혼동됩니다. 운 좋게도 비교적 간단한 해결책이 있습니다.
문제는 데이터 필터링 작업 (예 : loc)이 DataFrame의 복사본 또는 뷰를 반환하는지 여부가 항상 명확하지 않다는 것입니다. 따라서 필터링 된 DataFrame을 추가로 사용하면 혼동 될 수 있습니다.
간단한 해결책은 다음과 같습니다 (매우 큰 데이터 세트로 작업해야하는 경우 제외).
값을 업데이트해야 할 때마다 할당 전에 항상 DataFrame을 암시 적으로 복사해야합니다.
df # Some DataFrame
df = df.loc[:, 0:2] # Some filtering (unsure whether a view or copy is returned)
df = df.copy() # Ensuring a copy is made
df[df["Name"] == "John"] = "Johny" # Assignment can be done now (no warning)
답변
의심의 여지를 없애기 위해 내 해결책은 일반 사본 대신 슬라이스의 깊은 사본을 만드는 것이 었습니다. 상황에 따라 적용되지 않을 수 있습니다 (메모리 제약 / 슬라이스 크기, 성능 저하 가능성-특히 복사가 나처럼 루프에서 발생하는 경우 등)
분명히하기 위해 내가받은 경고는 다음과 같습니다.
/opt/anaconda3/lib/python3.6/site-packages/ipykernel/__main__.py:54:
SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation:
http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
삽화
슬라이스 사본에 놓인 열로 인해 경고가 발생했다는 의심이 들었습니다. 기술적으로 슬라이스 사본에 값을 설정하려고 시도하지 않았지만 여전히 슬라이스 사본의 수정이었습니다. 다음은 의심을 확인하기 위해 취한 (간체 화 된) 단계입니다. 경고를 이해하려는 사람들에게 도움이되기를 바랍니다.
예 1 : 원본에 열을 놓으면 사본에 영향을 미칩니다
우리는 이미 알고 있었지만 이것은 건전한 알림입니다. 이것은 경고에 관한 것이 아닙니다 .
>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1
A B
0 111 121
1 112 122
2 113 123
>> df2 = df1
>> df2
A B
0 111 121
1 112 122
2 113 123
# Dropping a column on df1 affects df2
>> df1.drop('A', axis=1, inplace=True)
>> df2
B
0 121
1 122
2 123
df1의 변경 사항을 피하여 df2에 영향을 줄 수 있습니다
>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1
A B
0 111 121
1 112 122
2 113 123
>> import copy
>> df2 = copy.deepcopy(df1)
>> df2
A B
0 111 121
1 112 122
2 113 123
# Dropping a column on df1 does not affect df2
>> df1.drop('A', axis=1, inplace=True)
>> df2
A B
0 111 121
1 112 122
2 113 123
예 2 : 사본에서 열을 삭제하면 원본에 영향을 줄 수 있음
이것은 실제로 경고를 보여줍니다.
>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1
A B
0 111 121
1 112 122
2 113 123
>> df2 = df1
>> df2
A B
0 111 121
1 112 122
2 113 123
# Dropping a column on df2 can affect df1
# No slice involved here, but I believe the principle remains the same?
# Let me know if not
>> df2.drop('A', axis=1, inplace=True)
>> df1
B
0 121
1 122
2 123
df1에 영향을 미치기 위해 df2에서 변경 한 내용을 피할 수 있습니다
>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1
A B
0 111 121
1 112 122
2 113 123
>> import copy
>> df2 = copy.deepcopy(df1)
>> df2
A B
0 111 121
1 112 122
2 113 123
>> df2.drop('A', axis=1, inplace=True)
>> df1
A B
0 111 121
1 112 122
2 113 123
건배!
