パンダでSettingWithCopyWarningを処理する方法?


629

バックグラウンド

パンダを0.11から0.13.0rc1にアップグレードしました。現在、アプリケーションは多くの新しい警告を表示しています。それらの1つはこのような:

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 '30

3
@leonprou df.set_valueは非推奨になりました。Pandasは現在、.at[]または.iat[]代わりに使用することを推奨しています。ドキュメントはここpandas.pydata.org/pandas-docs/stable/generated/...
カイルC

option_contextここでパンダについて言及されている人がいないことに驚いています:pandas.pydata.org/pandas-docs/stable/user_guide/options.htmlとして使用with pd.option_context("mode.chained_assignment", None): [...]
m-dz

回答:


795

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'

34
私はほとんどこれについて警告しないことを支持したいと思います。連鎖代入構文を使用すると、特定の状況で期待どおりに機能するために必要なインデックス付けの順序を明確に理解できます。私はそれについてこれらの徹底的な予防策があることは過度に偏執的だと思います。「プライベート」クラスのメソッドまたは属性について「すべての人を大人にする」と同じ精神で、パンダはユーザーにチェーンされた割り当てについて大人になってもらうほうがよいと思います。何をしているのかわかっている場合にのみ使用してください。
2013

48
人々が代替策を探してハッキングしようとするときに人々に警告することを試みることは少し不正確です。アクセスのための新しいスタイルのPandasメソッド(改良.ix、改良.ilocなど)は、他の方法について絶え間なく警告することなく、「主要な方法」と見なすことができます。代わりに彼らを大人にさせて、彼らが連鎖割り当てをしたいなら、そうしてください。とにかく私の2セント。連鎖割り当てが問題を解決するために機能するが、「主要な」方法とは見なされない場合、Pandas開発者からの不満なコメントがここに頻繁に表示されます。
2013

8
@EMS問題は、コピー対ビューが作成されているコードから常に明確であるとは限らず、この問題から多くのバグ/混乱が発生することです。設定を自動的に行うためにrcファイル/オプションを入れることを検討していましたが、これはコピー警告を伴う設定がどのように機能しているかを考えると、より便利かもしれません。
Jeff Tratner、2013

3
警告する理由は、もちろん古いコードをアップグレードする人々のためです。そして、私はいくつかの非常に醜い古いコードを扱っているので、私は間違いなく警告が必要です。
Thomas Andrews

16
余談ですpd.options.mode.chained_assignment = Noneが、chained_assignment警告を無効にすると、コードの実行が約6倍速くなることがわかりました。他の誰かが同様の結果を経験しましたか?
ムオン

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

この警告への対処方法を知るには、警告の意味と、そもそもなぜ警告が出されるのかを理解することが重要です。

DataFrameをフィルタリングする場合、内部レイアウトとさまざまな実装の詳細に応じて、フレームをスライスまたはインデックス付けして、ビューまたはコピーを返すことができます。「ビュー」は、用語が示すように、元のデータへのビューです。したがって、ビューを変更すると、元のオブジェクトが変更される場合があります。一方、「コピー」はオリジナルからのデータの複製であり、コピーを変更してもオリジナルには影響しません。

他の回答で述べたように、SettingWithCopyWarningは「連鎖割り当て」操作にフラグを立てるために作成されました。df上記の設定を検討してください。列 "A"の値が5より大きい列 "B"のすべての値を選択するとします。パンダでは、これをさまざまな方法で実行できます。例えば、

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__への1回の呼び出しで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
    

コメントの@Peter Cottonは、コンテキストマネージャーを使用してモード(この要旨から変更)を非侵入的に変更し、必要な場合にのみモードを設定し、それをリセットして、終了時の元の状態。

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

「D」の値を「C」== 5となるように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です。


7
PS:セクション3の質問リストであなたの状況がカバーされていない場合はお知らせください。投稿を修正します。
cs95

150

一般に、のポイントは、SettingWithCopyWarningユーザー(特に新規ユーザー)が、オリジナルではなくコピーで操作している可能性があることを示すことです。そこです(あなたはそれができる何をしているかを知っていればIOW偽陽性はOK)。1つの可能性は、@ Garrettが示唆するように(デフォルトではwarn)警告をオフにすることです。

ここに別のオプションがあります:

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データベースのビューとテーブルのように聞こえます。「コピー」概念の導入の利点についてもっと知る必要がありますが、私見では微妙な意味、構文の違いを処理するのはやや負担です..
bigbug

19
私はcopy()に同意します。それは明らかで、私の問題を修正しました(これは誤検知でした)。
rdchambers 2015

5
更新後0.16、さらに多くの誤検知が表示されますが、誤検知の問題は、それが正当である場合でも、それを無視することを学ぶことです。
2015

3
@dashesyポイントを逃しています。時々 、多分ほとんどの時間、それがうまくいくかもしれません。しかし、たとえば、フレームが大きい/小さい場合、または機能しない別のdtypeの列を追加した場合に発生する可能性があります。それがポイントです。動作する可能性があるが、保証されていないことを実行しています。これは非推奨の警告とは大きく異なります。あなたがそれを使い続けたい、そしてそれがうまくいくなら、素晴らしい。ただし、事前に注意してください。
ジェフ

3
@Jeffは今では理にかなっているので、これはundefined動作です。何らかのエラーが発生すると、エラーがスローされます(で見られる落とし穴を回避するためC)。api凍結されているため、警告の現在の動作は下位互換性に意味があります。そして、プロダクションコード(warnings.filterwarnings('error', r'SettingWithCopyWarning)でエラーとしてキャッチするようにスローします。また、使用する提案.locは、(それがグループ内にある場合は)いずれも役に立たないことがあります。
2015

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]})

2つの動作:

dfcopy = df.ix[:,["a"]]
dfcopy.a.ix[0] = 2

動作1:dfcopyスタンドアロンデータフレームになりました。変更しても変わりませんdf

df.ix[0, "a"] = 3

動作2:元のデータフレームを変更します。


.loc代わりに使用

パンダの開発者は、.ixオブジェクトが非常に臭い[推測的]であることを認識し、データのアクセスと割り当てに役立つ2つの新しいオブジェクトを作成しました。(もう一方.iloc

.loc データのコピーを作成しようとしないため、より高速です。

.loc 既存のデータフレームをインプレースで変更することを目的としています。これにより、メモリ効率が向上します。

.loc 予測可能であり、1つの動作があります。


ソリューション

あなたのコード例であなたがしていることは、たくさんの列を持つ大きなファイルをロードして、それをより小さく変更することです。

この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オブジェクトがかなり臭い[推測的]であり、2つの新しいオブジェクトを作成したことを認識しました」-他のオブジェクトは何ですか?
jf328

3
@ jf328 .iloc I think
Brian Bien

1
はい、そうです.iloc。これらは、パンダのデータ構造にインデックスを付けるための2つの主要な方法です。詳細については、ドキュメントをご覧ください。
忍者観音

タイムスタンプを持つ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'

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です。だからここに行きます。作るcopyあなたのデータフレームではないように_is_copy

警告は、使用に示唆され.locていますが、使用している場合.loc、フレームの上にそれ_is_copy、あなたはまだ同じ警告が表示されます。誤解を招く?はい。迷惑ですか?あなたは賭けます。役に立ちましたか?潜在的に、連鎖割り当てが使用される場合。ただし、チェーンの割り当てを正しく検出できず、警告が無差別に出力されます。


11

このトピックはパンダと本当に混乱しています。幸いなことに、それは比較的簡単な解決策を持っています。

問題は、データフィルタリング操作(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

df2に影響を与えるためにdf1に加えられた変更を回避することが可能です。

>> 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

警告を抑制したい人もいるでしょう。

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を設定します。


これはdf ["B"]の9倍のコストです=値
Claudiu Creanga

これを@ClaudiuCreangaについてより深く説明できますか?
gies0r 2018

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も少なくとも変更する必要があることを理解している限り、' pandast。magic。)。


うーん、警告が何かを引き起こしたのが明らかに間違っているなら私はそれを理解します、それで、それを抑制するような警告を避けた方が良いと思いますか?
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.