python - 計算 - Pandas でSettingWithCopyWarningを扱うには?




try using.loc row_indexer col_indexer value instead python (6)

バックグラウンド

私はパンダを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])

パンダのデータフレームのコピー警告

あなたが行ってこのようなことをするとき:

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オブジェクトがかなり匂いが.ixことを認識し、データの登録と割り当てに役立つ2つの新しいオブジェクトを作成しました。 (もう一方は.iloc

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

.locは既存のデータフレームのインプレースを変更することを目的としています。

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

ソリューション

あなたのコード例で何をしているのは、たくさんの列を持つ大きなファイルを読み込んだ後、それを小さくすることです。

pd.read_csv関数を使用すると、多くの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オブジェクトを使う必要はありません。


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

残念ながら、このパターンは最初のチェーン割り当ての例とは区別できないため、(偽陽性の)警告とは区別できません。 偽陽性の可能性は、さらに詳しくお知りになりたい場合は、 索引作成ドキュメントで取り上げています。 この新しい警告は、次の割り当てで安全に無効にすることができます。

pd.options.mode.chained_assignment = None  # default='warn'

これはうまくいくはずです:

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

スライスを変数に割り当てて、次のように変数を使用して設定する場合は、次のようにします。

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

また、条件計算df2が長くなるなどの理由で、Jeffsソリューションを使用したくない場合は、次のように使用できます。

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

df2.index.tolist()は、元のデータフレームに列Bを設定するために使用されるdf2のすべてのエントリからインデックスを返します。


疑いを取り除くために、私の解決策は、通常のコピーの代わりにスライスを深くコピーすることでした。 コンテキスト(メモリの制約/スライスのサイズ、パフォーマンスの低下の可能性があります。特に、コピーがループなどで発生した場合など)

明確にするために、ここに私が受け取った警告があります:

/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

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

>> df2.drop('A', axis=1, inplace=True)
>> df1

A   B
0   111 121
1   112 122
2   113 123

乾杯!


私の場合、この問題は以下の>単純化された<例で発生しました。 そして私はそれを解決することもできました(うまくいけば、正しい解決策で):

警告のある古いコード:

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]の警告が出力されましたold_row[field] = new_row[field]

update_rowメソッドの行は実際にはSeries型なので、私はその行を次のように置き換えました:

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

Seriesへのアクセス/参照のためのmethodです。 イベントはどちらもうまく動作し、結果は同じですが、この方法では警告を無効にする必要はありません(他のチェーンインデックスの問題を他の場所に保存する必要はありません)。

私はこれが誰かを助けるかもしれないことを望む





chained-assignment