[python] Pandas에서 SettingWithCopyWarning을 처리하는 방법은 무엇입니까?

배경

방금 팬더를 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플래그로 생성 된 잠재적으로 첫 번째 선택은 반환 특히, 항상 예상대로 작동하지 않는 다음과 같은 “체인”과제, 혼란 사본 . [ 배경 토론 은 GH5390GH5597 을 참조하십시오 .]

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판다에서 다루는 방법 ?

이 게시물은 독자,

  1. 이 경고의 의미를 이해하고 싶습니다
  2. 이 경고를 억제하는 다른 방법을 이해하고 싶습니다
  3. 앞으로이 경고를 피하기 위해 코드를 개선하고 모범 사례를 따르는 방법을 이해하고 싶습니다.

설정

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

이 경고를 직접하는 몇 가지 방법이 있습니다.

  1. 을 만들다 deepcopy

    df2 = df[['A']].copy(deep=True)
    df2['A'] /= 2
    
  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()입니다 .dfloc


답변

일반적으로 점은 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입니다. 그래서 여기 있습니다. copyDataFrame이 아닌지 확인 하십시오 _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

건배!