Hoe om te gaan met SettingWithCopyWarning in Pandas?

Background

Ik heb zojuist mijn Pandas geupgrade van 0.11 naar 0.13.0rc1. Nu geeft de applicatie veel nieuwe waarschuwingen. Een van hen als deze:

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

Ik wil weten wat het precies betekent? Moet ik iets veranderen?

Hoe moet ik de waarschuwing opschorten als ik erop sta om quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE te gebruiken?

De functie die fouten geeft

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

Meer foutmeldingen

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

De SettingWithCopyWarning is in het leven geroepen om potentieel verwarrende "chained" toewijzingen te markeren, zoals de volgende, die niet altijd werken zoals verwacht, vooral wanneer de eerste selectie een kopie oplevert. [zie GH5390 en GH5597 voor achtergrond discussie].

df[df['A'] > 2]['B'] = new_val  # new_val not set in df

De waarschuwing geeft een suggestie om als volgt te herschrijven:

df.loc[df['A'] > 2, 'B'] = new_val

Dit past echter niet in uw gebruik, dat gelijk is aan:

df = df[df['A'] > 2]
df['B'] = new_val

Hoewel het duidelijk is dat je er niet om geeft dat schrijfsels teruggaan naar het oorspronkelijke frame (omdat je de verwijzing ernaar overschreven hebt), kan dit patroon helaas niet onderscheiden worden van het eerste geketende toewijzingsvoorbeeld, vandaar de (vals-positieve) waarschuwing. Het potentieel voor vals-positieven wordt behandeld in de docs on indexing, als je'verder wilt lezen. Je kunt deze nieuwe waarschuwing veilig uitschakelen met de volgende opdracht.

pd.options.mode.chained_assignment = None  # default='warn'
Commentaren (10)

In het algemeen is het punt van de SettingWithCopyWarning om gebruikers (en vooral nieuwe gebruikers) te laten zien dat ze misschien op een kopie werken en niet op het origineel zoals ze denken. Er zijn valse positieven (IOW als je weet wat je doet kan het goed zijn). Een mogelijkheid is om de (standaard waarschuwing) waarschuwing uit te schakelen zoals @Garrett voorstelt.

Hier is een andere optie:

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

Je kunt de is_copy vlag op False zetten, dat schakelt de controle uit, voor dat object:

In [5]: dfa.is_copy = False

In [6]: dfa['A'] /= 2

Als je expliciet kopieert dan zal er geen verdere waarschuwing plaatsvinden:

In [7]: dfa = df.ix[:, [1, 0]].copy()

In [8]: dfa['A'] /= 2

De code die de OP hierboven laat zien, is weliswaar legitiem, en waarschijnlijk iets wat ik ook doe, maar technisch gezien is het een geval voor deze waarschuwing, en geen vals-positief. Een andere manier om de waarschuwing niet te krijgen zou zijn om de selectie operatie via reindex te doen, bijv.

quote_df = quote_df.reindex(columns=['STK', ...])

Of,

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

Pandas dataframe kopieer waarschuwing

Als je zoiets als dit gaat doen:

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

pandas.ix in dit geval geeft een nieuw, op zichzelf staand dataframe.

Waarden die u in dit dataframe wijzigt, zullen het oorspronkelijke dataframe niet veranderen.

Dit is waar pandas je voor probeert te waarschuwen.


Waarom .ix een slecht idee is

Het .ix object probeert meer dan één ding te doen, en voor iedereen die iets gelezen heeft over schone code, is dit een sterke geur.

Gegeven dit dataframe:

df = pd.DataFrame({"a": [1,2,3,4], "b": [1,1,2,2]})

Twee gedragingen:

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

Gedrag één: dfcopy is nu een op zichzelf staand dataframe. Het veranderen ervan zal df niet veranderen

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

Gedrag 2: Dit verandert het originele dataframe.


Gebruik .loc in plaats

De pandas ontwikkelaars erkenden dat het .ix object nogal stonk [speculatief] en creëerden daarom twee nieuwe objecten die helpen bij het verkrijgen en toewijzen van gegevens. (De andere is .iloc)

.loc is sneller, omdat het niet probeert om een kopie van de gegevens te maken.

.loc is bedoeld om je bestaande dataframe inplace aan te passen, wat geheugen efficienter is.

.loc is voorspelbaar, het heeft één gedrag.


The solution

Wat je in je codevoorbeeld doet, is een groot bestand met veel kolommen laden, en het dan verkleinen.

De pd.read_csv functie kan je hierbij helpen en ook het laden van het bestand een stuk sneller maken.

Dus in plaats van dit te doen

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

Doe dan dit

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

Dit zal alleen de kolommen lezen waarin je geïnteresseerd bent, en ze de juiste naam geven. Het is niet nodig om het slechte .ix object te gebruiken om magische dingen te doen.

Commentaren (5)