Hoe om te gaan met SettingWithCopyWarning in Panda’s

Ik heb zojuist mijn Panda’s geüpgraded van 0.11 naar 0.13.0rc1. Nu laat de applicatie veel nieuwe waarschuwingen zien. Een van hen ziet er zo uit:

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

Antwoord 1, autoriteit 100%

De SettingWithCopyWarning is gemaakt om mogelijk verwarrende "geketende" opdrachten, zoals de volgende, die niet altijd werken zoals verwacht, vooral wanneer de eerste selectie een kopie retourneert. [zie GH5390 en GH5597 voor achtergronddiscussie.]

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

De waarschuwing biedt een suggestie om als volgt te herschrijven:

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

Dit past echter niet bij uw gebruik, wat overeenkomt met:

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

Hoewel het duidelijk is dat het u niet uitmaakt dat schrijfacties terugkeren naar het oorspronkelijke frame (aangezien u de verwijzing ernaar overschrijft), kan dit patroon helaas niet worden onderscheiden van het eerste voorbeeld van een geketende opdracht. Vandaar de (false positive) waarschuwing. Het potentieel voor valse positieven wordt behandeld in de docs over indexeren, als u verder wilt lezen. Je kunt deze nieuwe waarschuwing veilig uitschakelen met de volgende opdracht.

import pandas as pd
pd.options.mode.chained_assignment = None  # default='warn'

Andere bronnen


Antwoord 2, autoriteit 38%

Hoe om te gaan met SettingWithCopyWarning in Panda’s?

Dit bericht is bedoeld voor lezers die,

  1. Ik wil graag weten wat deze waarschuwing betekent
  2. Ik zou graag meer willen weten over verschillende manieren om deze waarschuwing te onderdrukken
  3. Zou graag willen weten hoe ze hun code kunnen verbeteren en goede praktijken willen volgen om deze waarschuwing in de toekomst te vermijden.

Instellen

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

Wat is de SettingWithCopyWarning?

Om te weten hoe met deze waarschuwing om te gaan, is het belangrijk om te begrijpen wat het betekent en waarom het in de eerste plaats wordt gesteld.

Bij het filteren van DataFrames is het mogelijk een frame te segmenteren/indexeren om ofwel een weergave of een kopie te retourneren, afhankelijk van de interne lay-out en verschillende implementatiedetails. Een "zicht" is, zoals de term suggereert, een weergave van de oorspronkelijke gegevens, dus het wijzigen van de weergave kan het oorspronkelijke object wijzigen. Aan de andere kant, een "kopie" is een replicatie van gegevens van het origineel, en het wijzigen van de kopie heeft geen effect op het origineel.

Zoals vermeld in andere antwoorden, is de SettingWithCopyWarning gemaakt om “geketende toewijzing” te markeren. activiteiten. Overweeg df in de bovenstaande setup. Stel dat u alle waarden in kolom “B” wilt selecteren. waarbij waarden in kolom “A” is > 5. Met Panda’s kun je dit op verschillende manieren doen, de ene al correcter dan de andere. Bijvoorbeeld,

df[df.A > 5]['B']
1    3
2    6
Name: B, dtype: int64

En,

df.loc[df.A > 5, 'B']
1    3
2    6
Name: B, dtype: int64

Deze geven hetzelfde resultaat, dus als u alleen deze waarden leest, maakt het geen verschil. Dus, wat is het probleem? Het probleem met geketende toewijzing is dat het over het algemeen moeilijk te voorspellen is of een weergave of een kopie wordt geretourneerd, dus dit wordt grotendeels een probleem wanneer u probeert waarden terug toe te wijzen. Om voort te bouwen op de eerdere overweeg bijvoorbeeld hoe deze code wordt uitgevoerd door de interpreter:

df.loc[df.A > 5, 'B'] = 4
# becomes
df.__setitem__((df.A > 5, 'B'), 4)

Met een enkele __setitem__ aanroep naar df. OTOH, overweeg deze code:

df[df.A > 5]['B'] = 4
# becomes
df.__getitem__(df.A > 5).__setitem__('B", 4)

Afhankelijk van het feit of __getitem__ een weergave of een kopie heeft geretourneerd, werkt de bewerking __setitem__ mogelijk niet.

Over het algemeen moet u loc voor op labels gebaseerde toewijzing en iloc voor toewijzing op basis van integer/positioneel, omdat de specificatie garandeert dat ze altijd op het origineel werken. Bovendien moet u voor het instellen van een enkele cel at en iat.

Meer is te vinden in de documentatie.

Opmerking
Alle booleaanse indexeringsbewerkingen gedaan met loc kunnen ook worden gedaan met iloc. Het enige verschil is dat iloc een van beide verwacht:
gehele getallen/posities voor index of een numpy array van booleaanse waarden, en
integer/positie-indexen voor de kolommen.

Bijvoorbeeld

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

Kan nas worden geschreven

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

En,

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

Kan worden geschreven als

df.iloc[1, 0] = 100

Enzovoort.


Vertel me gewoon hoe ik de waarschuwing kan onderdrukken!

Overweeg een eenvoudige handeling op de "A" kolom van df. “A” selecteren en delen door 2 verhoogt de waarschuwing, maar de bewerking zal werken.

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

Er zijn een aantal manieren om deze waarschuwing direct het zwijgen op te leggen:

  1. (aanbevolen) Gebruik loc om subsets te segmenteren:

     df2 = df.loc[:, ['A']]
     df2['A'] /= 2     # Does not raise 
    
  2. Wijzig pd.options.mode.chained_assignment
    Kan worden ingesteld op None, "warn" of "raise". "warn" is de standaardinstelling. None zal de waarschuwing volledig onderdrukken, en "raise" zal een SettingWithCopyError geven, waardoor de bewerking niet kan doorgaan.

     pd.options.mode.chained_assignment = None
     df2['A'] /= 2
    
  3. Maak een deepcopy

     df2 = df[['A']].copy(deep=True)
     df2['A'] /= 2
    

@Peter Cotton in de reacties, kwam naar voren met een leuke manier om de modus niet-intrusief te wijzigen (aangepast van deze kern) met behulp van een contextmanager , om de modus alleen in te stellen zolang als nodig is, en om deze terug te zetten naar de oorspronkelijke staat wanneer u klaar bent.

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

Het gebruik is als volgt:

# some code here
with ChainedAssignent():
    df2['A'] /= 2
# more code follows

Of, om de uitzondering op te heffen

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

Het "XY-probleem": wat doe ik verkeerd?

Vaak proberen gebruikers manieren te vinden om deze uitzondering te onderdrukken zonder volledig te begrijpen waarom deze in de eerste plaats is ontstaan. Dit is een goed voorbeeld van een XY-probleem, waarbij gebruikers proberen een probleem “Y” op te lossen. dat is eigenlijk een symptoom van een dieper geworteld probleem “X”. Er zullen vragen worden gesteld op basis van veelvoorkomende problemen die met deze waarschuwing te maken hebben, en vervolgens zullen oplossingen worden gepresenteerd.

Vraag 1
Ik heb een 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

Ik wil waarden toekennen in col "A" > 5 tot 1000. Mijn verwachte output is

      A  B  C  D  E
0     5  0  3  3  7
1  1000  3  5  2  4
2  1000  6  8  8  1

Onjuiste manier om dit te doen:

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

Juiste manier met behulp van loc:

df.loc[df.A > 5, 'A'] = 1000

Vraag 21
Ik probeer de waarde in cel (1, ‘D’) in te stellen op 12345. Mijn verwachte output is

   A  B  C      D  E
0  5  0  3      3  7
1  9  3  5  12345  4
2  7  6  8      8  1

Ik heb verschillende manieren geprobeerd om toegang te krijgen tot deze cel, zoals:
df['D'][1]. Wat is de beste manier om dit te doen?

1. Deze vraag is niet specifiek gerelateerd aan de waarschuwing, maar
het is goed om te begrijpen hoe u deze specifieke bewerking correct uitvoert
om situaties te vermijden waarin de waarschuwing mogelijk zou kunnen ontstaan ​​in
toekomst.

U kunt hiervoor een van de volgende methoden gebruiken.

df.loc[1, 'D'] = 12345
df.iloc[1, 3] = 12345
df.at[1, 'D'] = 12345
df.iat[1, 3] = 12345

Vraag 3
Ik probeer waarden in te stellen op basis van een bepaalde voorwaarde. ik heb een
DataFrame

   A  B  C  D  E
1  9  3  5  2  4
2  7  6  8  8  1

Ik wil waarden toewijzen in "D" tot 123 zodat "C" == 5. Ik
geprobeerd

df2.loc[df2.C == 5, 'D'] = 123

Dat lijkt prima, maar ik krijg nog steeds de
SettingWithCopyWarning! Hoe los ik dit op?

Dit komt waarschijnlijk door code hoger in je pijplijn. Heb je df2 gemaakt van iets groters, zoals

df2 = df[df.A > 5]

? In dit geval zal booleaanse indexering een weergave retourneren, dus df2 zal verwijzen naar het origineel. Wat u moet doen is df2 toewijzen aan een kopie:

df2 = df[df.A > 5].copy()
# Or,
# df2 = df.loc[df.A > 5, :]

Vraag 4
Ik probeer kolom "C" ter plaatse van

   A  B  C  D  E
1  9  3  5  2  4
2  7  6  8  8  1

Maar met

df2.drop('C', axis=1, inplace=True)

Gooit SettingWithCopyWarning. Waarom gebeurt dit?

Dit komt omdat df2 moet zijn gemaakt als een weergave van een andere slicing-bewerking, zoals

df2 = df[df.A > 5]

De oplossing hier is om ofwel een copy() van df te maken, of loc te gebruiken, zoals eerder.


Antwoord 3, autoriteit 14%

Over het algemeen is het doel van de SettingWithCopyWarning om gebruikers (en vooral nieuwe gebruikers) te laten zien dat ze mogelijk werken op een kopie en niet op het origineel zoals ze denken. Er zijn zijn valse positieven (IOW als je weet wat je doet, kan ok zijn). Een mogelijkheid is om simpelweg de (standaard waarschuwing) waarschuwing uit te schakelen, zoals @Garrett suggereert.

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

U kunt de vlag is_copy instellen op False, waardoor de controle voor dat object effectief wordt uitgeschakeld:

In [5]: dfa.is_copy = False
In [6]: dfa['A'] /= 2

Als u expliciet kopieert, 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, hoewel legitiem, en waarschijnlijk iets wat ik ook doe, is technisch gezien een reden voor deze waarschuwing, en niet vals positief. Een andere manier om de waarschuwing niet te krijgen, is door de selectiebewerking uit te voeren via reindex, bijvoorbeeld

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

Of,

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

Antwoord 4, autoriteit 4%

Pandas dataframe kopieerwaarschuwing

Als je zoiets gaat doen:

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

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

Waarden die u besluit te wijzigen in dit dataframe, zullen het oorspronkelijke dataframe niet veranderen.

Dit is waar panda’s je voor proberen te waarschuwen.


Waarom .ix een slecht idee is

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

Gezien 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. Als u dit wijzigt, verandert df

niet

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

Gedrag twee: dit verandert het oorspronkelijke dataframe.


Gebruik in plaats daarvan .loc

De ontwikkelaars van panda’s erkenden dat het .ix-object behoorlijk stonk [speculatief] en creëerden dus twee nieuwe objecten die helpen bij de toetreding en toewijzing van gegevens. (De andere is .iloc)

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

.loc is bedoeld om uw bestaande dataframe ter plekke aan te passen, wat meer geheugenefficiënt is.

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


De oplossing

Wat u in uw codevoorbeeld doet, is een groot bestand met veel kolommen laden en het vervolgens aanpassen om het kleiner te maken.

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

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

Hierdoor worden alleen de kolommen gelezen waarin u geïnteresseerd bent en krijgt u de juiste naam. Het is niet nodig om het kwaadaardige .ix object te gebruiken om magische dingen te doen.


Antwoord 5, autoriteit 3%

Hier beantwoord ik de vraag direct. Hoe ermee om te gaan?

Maak een .copy(deep=False) nadat je hebt gesneden. Zie pandas.DataFrame.copy.

Wacht, levert een schijfje geen kopie op? Dit is tenslotte wat het waarschuwingsbericht probeert te zeggen? Lees het lange antwoord:

import pandas as pd
df = pd.DataFrame({'x':[1,2,3]})

Dit geeft een waarschuwing:

df0 = df[df.x>2]
df0['foo'] = 'bar'

Dit doet niet:

df1 = df[df.x>2].copy(deep=False)
df1['foo'] = 'bar'

Zowel df0 als df1 zijn DataFrame-objecten, maar iets aan hen is anders waardoor panda’s de waarschuwing kunnen afdrukken. Laten we eens kijken wat het is.

import inspect
slice= df[df.x>2]
slice_copy = df[df.x>2].copy(deep=False)
inspect.getmembers(slice)
inspect.getmembers(slice_copy)

Als je je diff-tool naar keuze gebruikt, zul je zien dat naast een paar adressen het enige wezenlijke verschil dit is:

|          | slice   | slice_copy |
| _is_copy | weakref | None       |

De methode die beslist of er moet worden gewaarschuwd, is DataFrame._check_setitem_copy die _is_copy controleert. Dus hier ga je. Maak een copy zodat uw DataFrame niet _is_copy is.

De waarschuwing stelt voor om .loc te gebruiken, maar als je .loc gebruikt op een frame dat _is_copy is, krijg je nog steeds de dezelfde waarschuwing. Misleidend? Ja. Vervelend? Zeker weten. Behulpzaam? Mogelijk wanneer geketende toewijzing wordt gebruikt. Maar het kan de ketentoewijzing niet correct detecteren en de waarschuwing willekeurig afdrukken.


Antwoord 6, autoriteit 2%

Dit onderwerp is erg verwarrend met Panda’s. Gelukkig heeft het een relatief eenvoudige oplossing.

Het probleem is dat het niet altijd duidelijk is of gegevensfilterbewerkingen (bijv. loc) een kopie of een weergave van het DataFrame retourneren. Verder gebruik van een dergelijk gefilterd DataFrame kan daarom verwarrend zijn.

De eenvoudige oplossing is (tenzij u met zeer grote gegevenssets moet werken):

Als je waarden moet bijwerken, zorg er dan altijd voor dat je het DataFrame expliciet kopieert vóór de toewijzing.

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)

Antwoord 7

Ik kreeg dit probleem met .apply() bij het toewijzen van een nieuw dataframe van een reeds bestaand dataframe waarop ik de .query() heb gebruikt methode. Bijvoorbeeld:

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

Zou deze fout retourneren. De oplossing die de fout in dit geval lijkt op te lossen, is door dit te wijzigen in:

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

Dit is echter NIET efficiënt, vooral niet bij het gebruik van grote dataframes, omdat er een nieuwe kopie moet worden gemaakt.

Als u de methode .apply() gebruikt bij het genereren van een nieuwe kolom en de bijbehorende waarden, is een oplossing die de fout verhelpt en efficiënter is, door .reset_index(drop=True):

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

Antwoord 8

Om alle twijfel weg te nemen, was mijn oplossing om een ​​diepe kopie van het segment te maken in plaats van een gewone kopie.
Dit is mogelijk niet van toepassing, afhankelijk van uw context (geheugenbeperkingen / grootte van het segment, potentieel voor prestatievermindering – vooral als de kopie in een lus plaatsvindt zoals bij mij, enz…)

Voor alle duidelijkheid, hier is de waarschuwing die ik heb ontvangen:

/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

Illustratie

Ik had mijn twijfels of de waarschuwing werd gegeven vanwege een kolom die ik op een kopie van de slice liet vallen. Hoewel technisch gezien niet geprobeerd om een ​​waarde in de kopie van de slice in te stellen, was dat nog steeds een wijziging van de kopie van de slice.
Hieronder staan ​​de (vereenvoudigde) stappen die ik heb genomen om het vermoeden te bevestigen, ik hoop dat het degenen onder ons zal helpen die de waarschuwing proberen te begrijpen.

Voorbeeld 1: het neerzetten van een kolom op het origineel heeft invloed op de kopie

Dat wisten we al, maar dit is een goede herinnering. Dit is NIET waar de waarschuwing over gaat.

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

Het is mogelijk om te voorkomen dat wijzigingen die in df1 zijn aangebracht, van invloed zijn op df2. Opmerking: u kunt het importeren van copy.deepcopy vermijden door in plaats daarvan df.copy() te doen.

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

Voorbeeld 2: het laten vallen van een kolom op de kopie kan het origineel beïnvloeden

Dit illustreert eigenlijk de waarschuwing.

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

Het is mogelijk om te voorkomen dat wijzigingen die op df2 zijn aangebracht, van invloed zijn op 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

Proost!


Antwoord 9

Dit zou moeten werken:

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

Antwoord 10

Sommigen willen de waarschuwing misschien gewoon onderdrukken:

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

Antwoord 11

Vervolgvraag/opmerking voor beginners

Misschien een verduidelijking voor andere beginners zoals ik (ik kom uit R, wat onder de motorkap een beetje anders lijkt te werken). De volgende onschuldig ogende en functionele code bleef de SettingWithCopy-waarschuwing produceren en ik begreep niet waarom. Ik had zowel de uitgave met “chained indexing” gelezen en begrepen, maar mijn code bevat geen:

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

Maar later, veel te laat, heb ik gekeken waar de functie plot() wordt aangeroepen:

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

Dus “df” is geen dataframe maar een object dat zich op de een of andere manier herinnert dat het is gemaakt door een dataframe te indexeren (dus is dat een weergave?) die de regel in plot() zou maken

 df['target'] = ...

gelijk aan

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

wat een geketende indexering is. Heb ik dat goed begrepen?

Hoe dan ook,

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

het is opgelost.


Antwoord 12

Je zou het hele probleem zo kunnen vermijden, denk ik:

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

Toewijzen gebruiken. Uit de documentatie: nieuwe kolommen toewijzen aan een DataFrame, waarbij een nieuw object (een kopie) wordt geretourneerd met alle originele kolommen naast de nieuwe.

Zie het artikel van Tom Augspurger over method chaining bij panda’s: https://tomaugspurger.github.io/method -ketenen


Antwoord 13

Als u het segment aan een variabele hebt toegewezen en u wilt de variabele als volgt instellen:

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

En u wilt Jeffs oplossing niet gebruiken omdat uw conditieberekening df2 te lang is of om een ​​andere reden, dan kunt u het volgende gebruiken:

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

df2.index.tolist() retourneert de indices van alle items in df2, die vervolgens worden gebruikt om kolom B in het oorspronkelijke dataframe in te stellen.


Antwoord 14

Omdat deze vraag al volledig is uitgelegd en besproken in bestaande antwoorden, zal ik een nette pandas-benadering bieden aan de contextmanager met behulp van pandas.option_context (links naar docs en voorbeeld) – het is absoluut niet nodig om een ​​aangepaste klasse te maken met alle dunder-methoden en andere toeters en bellen.

Eerst de code van de contextmanager zelf:

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

Dan een voorbeeld:

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

Opmerkelijk is dat beide benaderingen a niet wijzigen, wat voor mij een beetje verrassend is, en zelfs een ondiepe df-kopie met .copy(deep=False) zou voorkomen dat deze waarschuwing wordt weergegeven (voor zover ik begrijp zou een oppervlakkige kopie op zijn minst a moeten wijzigen, maar dat doet het niet. pandas magie.).


Antwoord 15

Voor mij deed dit probleem zich voor in een volgende >vereenvoudigde< voorbeeld. En ik heb het ook kunnen oplossen (hopelijk met een juiste oplossing):

oude code met waarschuwing:

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

Hiermee werd de waarschuwing afgedrukt voor de regel old_row[field] = new_row[field]

Aangezien de rijen in de update_row-methode eigenlijk van het type Series zijn, heb ik de regel vervangen door:

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

d.w.z. methode voor toegang tot/opzoeken voor een voor toegang/opzoeken voor Series. Hoewel beide prima werken en het resultaat hetzelfde is, hoef ik op deze manier de waarschuwingen niet uit te schakelen (=bewaar ze voor andere problemen met ketenindexering ergens anders).

Ik hoop dat dit iemand kan helpen.


Antwoord 16

Ik kreeg dezelfde waarschuwing te zien, terwijl ik dit deel van mijn code uitvoerde:

    def scaler(self, numericals):
        scaler = MinMaxScaler()
        self.data.loc[:, numericals[0]] = scaler.fit_transform(self.data.loc[:, numericals[0]])
        self.data.loc[:, numericals[1]] = scaler.fit_transform(self.data.loc[:, numericals[1]])

welke scaler een MinMaxScaler is en numericals[0] de namen bevat van 3 van mijn numerieke kolommen.
de waarschuwing werd verwijderd toen ik de code veranderde in:

    def scaler(self, numericals):
        scaler = MinMaxScaler()
        self.data.loc[:][numericals[0]] = scaler.fit_transform(self.data.loc[:][numericals[0]])
        self.data.loc[:][numericals[1]] = scaler.fit_transform(self.data.loc[:][numericals[1]])

Dus, verander gewoon [:, ~] in [:][~]

LEAVE A REPLY

Please enter your comment!
Please enter your name here

nineteen + fifteen =

Other episodes