Pandas에서 SettingWithCopyWarning을 처리하는 방법

python pandas dataframe chained-assignment


Background

방금 팬더를 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])




Answer 1 Garrett


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'



Answer 2 cs95


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

이 게시물은 독자,

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

Setup

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

And,

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 을 사용해야 합니다.

자세한 내용은 설명서를 참조하십시오 .

Note
loc 으로 수행되는 모든 부울 색인 작업 은 iloc 으로 수행 할 수도 있습니다 . 유일한 차이점은 iloc 은 인덱스의 정수 / 위치 또는 부울 값의 numpy 배열 및 열의 정수 / 위치 인덱스를 예상 한다는 것 입니다.

예를 들어

df.loc[df.A > 5, 'B'] = 4

nas라고 쓸 수 있습니다

df.iloc[(df.A > 5).values, 1] = 4

And,

df.loc[1, 'A'] = 100

로 쓸 수 있습니다

df.iloc[1, 0] = 100

등등.


경고를 억제하는 방법을 알려주십시오!

df 의 "A"열에 대한 간단한 작업을 고려하십시오 . "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]

여기서 해결책 은 dfcopy() 를 만들 거나 이전과 같이 loc 을 사용하는 것 입니다.




Answer 3 Jeff


일반적으로 지점 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', ...])

Or,

quote_df = quote_df.reindex(['STK', ...], axis=1)  # v.0.21



Answer 4 firelynx


팬더 데이터 프레임 복사 경고

가서 이렇게 할 때 :

quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]

pandas.ix 이 경우 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 객체를 사용하여 마법 같은 일을 할 필요가 없습니다 .




Answer 5 user443854


여기서 나는 질문에 직접 대답합니다. 그것을 다루는 방법?

슬라이스 후 .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'

df0df1 있습니다 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 입니다. 그래서 여기 있습니다. A는 확인 copy 하여 DataFrame가되지 않도록 _is_copy .

경고는 .loc 을 사용하도록 제안 하지만 _is_copy 프레임에서 .loc 을 사용 하면 여전히 동일한 경고가 표시됩니다. 오해? 예. 성가신? 물론이지. 도움이 되셨습니까? 잠재적으로 체인 할당이 사용될 때. 그러나 체인 할당을 올바르게 감지 할 수 없으며 경고를 무차별 적으로 인쇄합니다.