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


629

배경

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


2
당신은 df.set_value여기에서 문서 를 사용할 수 있습니다 -pandas.pydata.org/pandas-docs/stable/generated/…
leonprou

1
pandas.pydata.org/pandas-docs/stable/… 공식 문서 상세 설명
wyx

3
@leonprou df.set_value는 더 이상 사용되지 않습니다. 팬더는 이제 .at[]또는 .iat[]대신 사용하는 것이 좋습니다 . docs here pandas.pydata.org/pandas-docs/stable/generated/…
Kyle C

나는 아무도 팬더 '언급되지 놀랐어요 option_context여기 : pandas.pydata.org/pandas-docs/stable/user_guide/options.html 등의 사용을with pd.option_context("mode.chained_assignment", None): [...]
m-DZ

답변:


793

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'

34
나는 이것에 대해 전혀 경고하지 않는 것이 대부분 유리하다고 생각합니다. 체인 할당 구문으로 작업하는 경우 주어진 상황에서 예상대로 작동하기 위해 발생해야하는 인덱싱 순서를 확실히 파악할 수 있습니다. 나는 그것에 대해 철저한 편집증이 있다는 것은 지나치게 편집증 적이라고 생각합니다. '비공개'클래스 메소드 또는 속성에 대해 "모든 사람을 어른으로 키우십시오"와 같은 정신으로, 팬더가 사용자가 체인 할당에 대해 어른이되도록하는 것이 좋습니다. 하고있는 일을 알고있는 경우에만 사용하십시오.
ely

48
대안을 찾기 위해 해킹을 할 때 사람들에게 경고하려고하는 것은 약간 비 Pythonic입니다. 액세스를위한 최신 스타일의 Pandas 방법 (개선 .ix, 개선 .iloc등)은 다른 모든 방법에 대해 끊임없이 경고하지 않고 "기본 방법"으로 볼 수 있습니다. 대신 어른이되게하고 연쇄 배정을하고 싶다면 그렇게하십시오. 어쨌든 내 두 센트. 체인 할당이 문제를 해결하는 데 도움이 될 때 Pandas 개발자의 불만을 표명하는 경우가 종종 있지만 "기본"방법으로 간주되지는 않습니다.
ely December

8
@ EMS 문제는 복사 대보기가 작성되는 코드 에서 항상 명확하지 않으며이 문제로 인해 많은 버그 / 혼란이 발생한다는 것입니다. 우리는 자동으로 구성을 수행하기 위해 rc 파일 / 옵션을 넣는 것을 고려하고있었습니다. 복사 경고가있는 설정이 작동하는 방식에서 더 유용 할 수 있습니다.
Jeff Tratner

3
경고해야 할 이유는 물론 사람들이 오래된 코드를 업그레이드하기 때문입니다. 아주 추악한 코드를 다루기 때문에 확실히 경고가 필요합니다.
토마스 앤드류스

15
참고로, chained_assignment 경고를 비활성화하면 pd.options.mode.chained_assignment = None코드가 약 6 배 빠르게 실행됩니다. 다른 사람이 비슷한 결과를 경험 했습니까?
Muon

209

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


7
추신 : 귀하의 상황이 섹션 3의 질문 목록에 포함되지 않은 경우 알려주십시오. 내 게시물을 수정하겠습니다.
cs95

150

일반적으로 점은 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

정보 및 토론에 감사드립니다. 콘솔을 자동으로 끄도록 경고를 끕니다. SQL 데이터베이스의 뷰 및 테이블처럼 들립니다. '복사'개념의 도입에 따른 이점에 대해 더 알아야하지만 IMHO는 미묘한 의미 론적, 구문 적 차이를 처리하는 것이 다소 부담이됩니다.
bigbug

19
copy ()에 동의합니다. 그것은 명확하고 내 문제를 해결했습니다 (거짓 긍정적이었습니다).
rdchambers

5
업데이트 후 0.16더 많은 오 탐지가 발견되면 오 탐지 문제는 때로는 합법적이지만 무시하는 법을 배웁니다.
dashesy

3
@dashesy 당신은 요점이 없습니다. 때로는 어쩌면 대부분 의 시간 그것은 작동 할 수 있습니다. 그러나 예를 들어 프레임이 더 크거나 작거나 작동 하지 않는 다른 dtype에 대한 열을 추가하면 발생할 수 있습니다. 그게 요점입니다. 작동하지만 보장되지 않는 작업을 수행하고 있습니다 . 이것은 사용 중단 경고와는 매우 다릅니다. 계속 사용하고 싶다면 효과가 있습니다. 그러나 미리 경고하십시오.
Jeff

3
@Jeff는 이제 의미가 있으므로 undefined동작입니다. 이 (볼 피하기 함정에 다음 오류가 발생한다 오히려 C) 이후 api경고의 현재 동작은 이전 버전과의 호환성을 위해 의미가 동결된다. 그리고 나는 그것들을 내 프로덕션 코드 ( warnings.filterwarnings('error', r'SettingWithCopyWarning)의 오류로 잡기 위해 던질 것 입니다. 또한 사용 제안이 .loc때로는 도움이되지 않습니다 (그룹에있는 경우).
dashesy

41

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

가서 이렇게 할 때 :

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마법의 일을하기 위해 사악한 물건을 사용할 필요가 없습니다 .


"팬더 개발자들은 .ix 객체가 냄새가 아주 심해서 두 개의 새로운 객체를 만들었 음을 인식했습니다"– 다른 하나는 무엇입니까?
jf328

3
@ jf328 생각 .iloc
브라이언 비엔

1
그렇습니다 .iloc. 팬더 데이터 구조를 인덱싱하는 두 가지 주요 방법입니다. 설명서에서 자세한 내용을 읽으십시오.
Ninjakannon

DataFrame 열을 타임 스탬프로 바꾸고 날짜 시간 객체 또는 문자열을 가진 열로 바꾸려면 어떻게해야합니까?
boldnik

@boldnik이 답변 확인 stackoverflow.com/a/37453925/3730397
firelynx

20

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

.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에도 여전히 동일한 경고가 표시됩니다. 오해? 예. 성가신? 물론이지. 도움이 되셨습니까? 잠재적으로 체인 할당이 사용될 때. 그러나 체인 할당을 올바르게 감지 할 수 없으며 경고를 무차별 적으로 인쇄합니다.


11

이 주제는 실제로 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)

오타가 있습니다 : 암시 적으로 명시 적이어야합니다
s9527

7

의심의 여지를 없애기 위해 내 해결책은 일반 사본 대신 슬라이스의 깊은 사본을 만드는 것이 었습니다. 상황에 따라 적용되지 않을 수 있습니다 (메모리 제약 / 슬라이스 크기, 성능 저하 가능성-특히 복사가 나처럼 루프에서 발생하는 경우 등)

분명히하기 위해 내가받은 경고는 다음과 같습니다.

/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

건배!


4

이것은 작동해야합니다 :

quote_df.loc[:,'TVol'] = quote_df['TVol']/TVOL_SCALE

4

일부는 단순히 경고를 표시하지 않을 수 있습니다.

class SupressSettingWithCopyWarning:
    def __enter__(self):
        pd.options.mode.chained_assignment = None

    def __exit__(self, *args):
        pd.options.mode.chained_assignment = 'warn'

with SupressSettingWithCopyWarning():
    #code that produces warning

3

슬라이스를 변수에 할당하고 다음과 같이 변수를 사용하여 설정하려는 경우 :

df2 = df[df['A'] > 2]
df2['B'] = value

조건 컴퓨팅 df2이 길거나 다른 이유로 인해 Jeffs 솔루션을 사용하지 않으려는 경우 다음을 사용할 수 있습니다.

df.loc[df2.index.tolist(), 'B'] = value

df2.index.tolist() df2의 모든 항목에서 인덱스를 반환 한 다음 원래 데이터 프레임에서 열 B를 설정하는 데 사용됩니다.


이 9 시간 후 DF 고가이며 [ "B"= 값
Claudiu Creanga

@ClaudiuCreanga에 대해 더 자세히 설명해 주시겠습니까?
gies0r

2

나 에게이 문제는 다음> 단순화 된 <예제에서 발생했습니다. 그리고 나는 그것을 해결할 수있었습니다 (올바른 해결책으로).

경고가있는 오래된 코드 :

def update_old_dataframe(old_dataframe, new_dataframe):
    for new_index, new_row in new_dataframe.iterrorws():
        old_dataframe.loc[new_index] = update_row(old_dataframe.loc[new_index], new_row)

def update_row(old_row, new_row):
    for field in [list_of_columns]:
        # line with warning because of chain indexing old_dataframe[new_index][field]
        old_row[field] = new_row[field]  
    return old_row

이것은 라인에 대한 경고를 인쇄 old_row[field] = new_row[field]

update_row 메소드의 행은 실제로 type이므로 행을 다음과 같이 바꿨 Series습니다.

old_row.at[field] = new_row.at[field]

즉에 대한 액세스 / 조회 방법Series . 이벤트가 모두 잘 작동하고 결과가 동일하지만이 방법으로 경고를 비활성화 할 필요가 없습니다 (= 다른 체인 인덱싱 문제를 위해 다른 곳에 유지하십시오).

이것이 누군가를 도울 수 있기를 바랍니다.


2

이 같은 모든 문제를 피할 수 있다고 생각합니다.

return (
    pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
    .rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
    .ix[:,[0,3,2,1,4,5,8,9,30,31]]
    .assign(
        TClose=lambda df: df['TPrice'],
        RT=lambda df: 100 * (df['TPrice']/quote_df['TPCLOSE'] - 1),
        TVol=lambda df: df['TVol']/TVOL_SCALE,
        TAmt=lambda df: df['TAmt']/TAMT_SCALE,
        STK_ID=lambda df: df['STK'].str.slice(13,19),
        STK_Name=lambda df: df['STK'].str.slice(21,30)#.decode('gb2312'),
        TDate=lambda df: df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10]),
    )
)

할당 사용. 로부터 문서 새로운 사람뿐만 아니라 모든 원본 열이있는 새로운 객체 (사본)을 반환하는 DataFrame에 할당 새 열 :.

팬더의 메소드 체인에 대한 Tom Augspurger의 기사를 참조하십시오 : https://tomaugspurger.github.io/method-chaining


2

후속 초보자 질문 / 비고

어쩌면 나와 같은 다른 초보자를위한 설명이있을 수 있습니다 (R에서 왔으며 후드에서 약간 다르게 작동하는 것 같습니다). 다음과 같은 무해하고 기능적인 코드는 SettingWithCopy 경고를 계속 생성했으며 그 이유를 알 수 없었습니다. 나는 "체인 인덱싱"으로 발행 된 것을 읽고 이해했지만 내 코드에는 다음이 포함되어 있지 않습니다.

def plot(pdb, df, title, **kw):
    df['target'] = (df['ogg'] + df['ugg']) / 2
    # ...

그러나 나중에 너무 늦게 plot () 함수가 호출되는 위치를 살펴 보았습니다.

    df = data[data['anz_emw'] > 0]
    pixbuf = plot(pdb, df, title)

따라서 "df"는 데이터 프레임이 아니라 데이터 프레임을 인덱싱하여 생성했음을 기억하는 객체입니다 (그래서 뷰도 마찬가지입니다).

 df['target'] = ...

에 해당

 data[data['anz_emw'] > 0]['target'] = ...

연쇄 인덱싱입니다. 내가 알았어?

어쨌든,

def plot(pdb, df, title, **kw):
    df.loc[:,'target'] = (df['ogg'] + df['ugg']) / 2

고쳤다.


1

이 질문은 이미 기존 답변에서 완전히 설명되고 논의되었으므로 pandas컨텍스트 관리자에 대한 깔끔한 접근 방식을 제공합니다 pandas.option_context( 문서예제 링크 )-모든 Dunder 메소드 및 기타 종소리로 사용자 정의 클래스를 만들 필요는 없습니다. 그리고 휘파람.

먼저 컨텍스트 관리자 코드 자체 :

from contextlib import contextmanager

@contextmanager
def SuppressPandasWarning():
    with pd.option_context("mode.chained_assignment", None):
        yield

그런 다음 예를 들면 다음과 같습니다.

import pandas as pd
from string import ascii_letters

a = pd.DataFrame({"A": list(ascii_letters[0:4]), "B": range(0,4)})

mask = a["A"].isin(["c", "d"])
# Even shallow copy below is enough to not raise the warning, but why is a mystery to me.
b = a.loc[mask]  # .copy(deep=False)

# Raises the `SettingWithCopyWarning`
b["B"] = b["B"] * 2

# Does not!
with SuppressPandasWarning():
    b["B"] = b["B"] * 2

주목할 가치는 두 접근 방식이 수정하지 않는다는 것 a입니다. 약간 놀랍습니다. 얕은 df 사본 조차도이 .copy(deep=False)경고가 발생하지 않을 것입니다 (얕은 사본도 최소한 수정해야한다는 것을 이해 a하는 한) 't. pandas마술.).


흠, 경고가 뭔가 잘못 제기되면 이해합니다. 그래서 억압과 같은 경고를 피하는 것이 좋습니다. 어떻게 생각하십니까?
jezrael

아니요, 경고는 단지 경고입니다. 여기에서와 같이, 그것은 당신에게 뭔가 잘못 되었을 수도 있다는 것을 경고 합니다 . 그러나 당신이 무엇을하고 있고 무엇을하고 있는지 알고 있다면 그것들 중 일부를 억압하는 것이 완벽합니다. 참조 재 할당 에 대해서는 stackoverflow.com/a/20627316/4272484 의 설명을 참조하십시오.
m-dz

1

메서드를 .apply()사용한 기존 데이터 프레임에서 새 데이터 프레임을 할당 할 때이 문제가 발생했습니다 .query(). 예를 들어 :

prop_df = df.query('column == "value"')
prop_df['new_column'] = prop_df.apply(function, axis=1)

이 오류를 반환합니다. 이 경우 오류를 해결하는 것으로 보이는 수정은 다음과 같이 변경하는 것입니다.

prop_df = df.copy(deep=True)
prop_df = prop_df.query('column == "value"')
prop_df['new_column'] = prop_df.apply(function, axis=1)

그러나 새 복사본을 만들어야하기 때문에 큰 데이터 프레임을 사용할 때는 특히 효율적이지 않습니다.

.apply()새 열과 해당 값을 생성하는 데 메소드를 사용하는 경우 오류를 해결하고 더 효율적인 수정은 다음을 추가하는 것입니다 .reset_index(drop=True).

prop_df = df.query('column == "value"').reset_index(drop=True)
prop_df['new_column'] = prop_df.apply(function, axis=1)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.