Wie gehe ich mit SettingWithCopyWarning in Pandas um?


629

Hintergrund

Ich habe gerade meine Pandas von 0.11 auf 0.13.0rc1 aktualisiert. Jetzt gibt die Anwendung viele neue Warnungen aus. Einer von ihnen mag diesen:

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

Ich möchte wissen, was es genau bedeutet? Muss ich etwas ändern?

Wie soll ich die Warnung aussetzen, wenn ich darauf bestehe, sie zu verwenden quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE?

Die Funktion, die Fehler gibt

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

Weitere Fehlermeldungen

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
Hier ist ein Kontextmanager, um vorübergehend die Warnstufe gist.github.com/notbanker/2be3ed34539c86e22ffdd88fd95ad8bc
Peter Cotton

2
Sie können df.set_valueDokumente hier verwenden - pandas.pydata.org/pandas-docs/stable/generated/…
leonprou

1
pandas.pydata.org/pandas-docs/stable/… offizielles Dokument im Detail erklären
wyx

3
@leonprou df.set_valueist veraltet. Pandas empfiehlt jetzt die Verwendung von .at[]oder .iat[]stattdessen. Dokumente hier pandas.pydata.org/pandas-docs/stable/generated/…
Kyle C

Ich bin überrascht, dass hier niemand Pandas erwähnt option_contexthat: pandas.pydata.org/pandas-docs/stable/user_guide/options.html , verwenden alswith pd.option_context("mode.chained_assignment", None): [...]
m-dz

Antworten:


793

Das SettingWithCopyWarning wurde erstellt, um potenziell verwirrende "verkettete" Zuweisungen wie die folgenden zu kennzeichnen, die nicht immer wie erwartet funktionieren, insbesondere wenn die erste Auswahl eine Kopie zurückgibt . [ Hintergrunddiskussion siehe GH5390 und GH5597 .]

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

Die Warnung bietet einen Vorschlag zum Umschreiben wie folgt:

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

Dies passt jedoch nicht zu Ihrer Verwendung. Dies entspricht:

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

Es ist zwar klar, dass es Ihnen egal ist, ob Schreibvorgänge zum ursprünglichen Frame zurückkehren (da Sie den Verweis darauf überschreiben), aber dieses Muster kann leider nicht vom ersten verketteten Zuweisungsbeispiel unterschieden werden. Daher die (falsch positive) Warnung. Das Potenzial für Fehlalarme wird in den Dokumenten zur Indizierung angesprochen , wenn Sie weiterlesen möchten. Sie können diese neue Warnung mit der folgenden Zuordnung sicher deaktivieren.

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

34
Ich denke, ich wäre meistens dafür, überhaupt nicht davor zu warnen. Wenn Sie mit der verketteten Zuweisungssyntax arbeiten, können Sie definitiv die Reihenfolge der Indizierung ermitteln, die erforderlich ist, damit sie in einer bestimmten Situation wie erwartet funktioniert. Ich finde es zu paranoid, dass es diese erschöpfenden Vorsichtsmaßnahmen gibt. In dem gleichen Sinne wie "alle erwachsen werden lassen" über "private" Klassenmethoden oder -attribute, denke ich, ist es für Pandas besser, Benutzer Erwachsene über verkettete Aufgaben sein zu lassen. Verwenden Sie sie nur, wenn Sie wissen, was Sie tun.
ely

48
Es ist ein bisschen unpythonisch, Leute zu warnen, wenn sie nach Alternativen suchen. Die neueren Pandas-Methoden für den Zugriff (verbessert .ix, verbessert .ilocusw.) können definitiv als "der primäre Weg" angesehen werden, ohne alle ununterbrochen vor anderen Wegen zu warnen. Lass sie stattdessen Erwachsene sein und wenn sie verkettete Aufgaben erledigen wollen, dann sei es so. Meine zwei Cent sowieso. Man sieht hier oft verärgerte Kommentare von Pandas-Entwicklern, wenn verkettete Aufgaben zur Lösung eines Problems beitragen, aber nicht als "primärer" Weg dazu angesehen werden.
ely

8
@EMS Das Problem ist, dass aus dem Code nicht immer hervorgeht, wo eine Kopie oder eine Ansicht erstellt wird, und dass aus diesem Problem eine Reihe von Fehlern / Verwirrungen resultieren. Wir haben überlegt, eine RC-Datei / Optionen einzufügen, um die Konfiguration automatisch durchzuführen. Dies könnte nützlicher sein, wenn man bedenkt, wie die Einstellung mit Kopierwarnung funktioniert.
Jeff Tratner

3
Der Grund zur Warnung ist natürlich, dass Leute alten Code aktualisieren. Und ich brauche definitiv eine Warnung, weil ich es mit einem sehr hässlichen alten Code zu tun habe.
Thomas Andrews

15
Nebenbei bemerkt habe ich festgestellt, dass das Deaktivieren der Warnung chained_assignment: dazu geführt pd.options.mode.chained_assignment = Nonehat, dass mein Code ungefähr sechsmal schneller ausgeführt wurde. Hat sonst noch jemand ähnliche Ergebnisse erzielt?
Muon

206

Wie SettingWithCopyWarninggehe ich mit Pandas um?

Dieser Beitrag ist für Leser gedacht, die,

  1. Möchte verstehen, was diese Warnung bedeutet
  2. Ich möchte verschiedene Möglichkeiten zur Unterdrückung dieser Warnung verstehen
  3. Möchten Sie verstehen, wie Sie den Code verbessern und bewährte Methoden befolgen können, um diese Warnung in Zukunft zu vermeiden.

Installieren

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

Was ist der SettingWithCopyWarning ?

Um zu wissen, wie man mit dieser Warnung umgeht, ist es wichtig zu verstehen, was sie bedeutet und warum sie überhaupt ausgelöst wird.

Beim Filtern von DataFrames kann ein Frame geschnitten / indiziert werden, um entweder eine Ansicht oder eine Kopie zurückzugeben je nach internem Layout und verschiedenen Implementierungsdetails . Eine "Ansicht" ist, wie der Begriff andeutet, eine Ansicht in die Originaldaten, so dass das Ändern der Ansicht das ursprüngliche Objekt ändern kann. Andererseits ist eine "Kopie" eine Replikation von Daten aus dem Original, und das Ändern der Kopie hat keine Auswirkungen auf das Original.

Wie in anderen Antworten erwähnt, SettingWithCopyWarningwurde das erstellt, um "verkettete Zuweisungs" -Operationen zu kennzeichnen. Betrachten Sie dfim obigen Setup. Angenommen, Sie möchten alle Werte in Spalte "B" auswählen, wobei die Werte in Spalte "A"> 5 sind. Mit Pandas können Sie dies auf verschiedene Arten tun, von denen einige korrekter sind als andere. Zum Beispiel,

df[df.A > 5]['B']

1    3
2    6
Name: B, dtype: int64

Und,

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

1    3
2    6
Name: B, dtype: int64

Diese geben das gleiche Ergebnis zurück. Wenn Sie also nur diese Werte lesen, spielt dies keine Rolle. Also, was ist das Problem? Das Problem bei der verketteten Zuweisung besteht darin, dass es im Allgemeinen schwierig ist, vorherzusagen, ob eine Ansicht oder eine Kopie zurückgegeben wird. Dies wird daher größtenteils zu einem Problem, wenn Sie versuchen, Werte zurückzuweisen. Um auf dem vorherigen Beispiel aufzubauen, betrachten Sie, wie dieser Code vom Interpreter ausgeführt wird:

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

Mit einem einzigen __setitem__Anruf an df. OTOH, betrachten Sie diesen Code:

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

Abhängig davon, ob __getitem__eine Ansicht oder eine Kopie zurückgegeben wurde, __setitem__funktioniert der Vorgang möglicherweise nicht .

Im Allgemeinen sollten Sie locfür die beschriftungsbasierte Zuweisung und ilocfür die ganzzahlige / positionsbasierte Zuweisung verwenden, da die Spezifikation garantiert, dass sie immer mit dem Original arbeiten. Zum Festlegen einer einzelnen Zelle sollten Sie außerdem atund verwendeniat .

Weitere finden Sie in der Dokumentation .

Hinweis
Alle booleschen Indizierungsvorgänge, mit denen ausgeführt wird, lockönnen auch ausgeführt werden iloc. Der einzige Unterschied besteht darin, ilocdass entweder Ganzzahlen / Positionen für den Index oder ein numpy-Array von Booleschen Werten und Ganzzahl- / Positionsindizes für die Spalten erwartet werden.

Zum Beispiel,

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

Kann nas geschrieben werden

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

Und,

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

Kann geschrieben werden als

df.iloc[1, 0] = 100

Und so weiter.


Sagen Sie mir einfach, wie ich die Warnung unterdrücken kann!

Betrachten Sie eine einfache Operation in der Spalte "A" von df. Wenn Sie "A" auswählen und durch 2 teilen, wird die Warnung ausgelöst, aber der Vorgang funktioniert.

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

Es gibt verschiedene Möglichkeiten, diese Warnung direkt zum Schweigen zu bringen:

  1. Mach ein deepcopy

    df2 = df[['A']].copy(deep=True)
    df2['A'] /= 2
  2. Ändernpd.options.mode.chained_assignment
    kann eingestellt werden None, "warn"oder "raise". "warn"ist die Standardeinstellung. Noneunterdrückt die Warnung vollständig und "raise"wirft ein SettingWithCopyError, wodurch verhindert wird , dass die Operation ausgeführt wird.

    pd.options.mode.chained_assignment = None
    df2['A'] /= 2

@Peter Cotton hat in den Kommentaren eine nette Möglichkeit gefunden, den Modus (geändert von diesem Kern ) mit einem Kontextmanager nicht aufdringlich zu ändern , um den Modus nur so lange einzustellen, wie es erforderlich ist, und ihn auf den zurückzusetzen Originalzustand, wenn fertig.

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

Die Verwendung ist wie folgt:

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

Oder um die Ausnahme auszulösen

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

Das "XY-Problem": Was mache ich falsch?

In den meisten Fällen versuchen Benutzer, nach Möglichkeiten zu suchen, um diese Ausnahme zu unterdrücken, ohne vollständig zu verstehen, warum sie überhaupt ausgelöst wurde. Dies ist ein gutes Beispiel für ein XY-Problem , bei dem Benutzer versuchen, ein Problem "Y" zu lösen, das tatsächlich ein Symptom für ein tiefer verwurzeltes Problem "X" ist. Auf der Grundlage häufiger Probleme, auf die diese Warnung stößt, werden Fragen gestellt und anschließend Lösungen vorgestellt.

Frage 1
Ich habe einen 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

Ich möchte Werte in Spalte "A"> 5 bis 1000 zuweisen. Meine erwartete Ausgabe ist

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

Falscher Weg, dies zu tun:

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

Richtiger Weg mit loc:

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


Frage 2 1
Ich versuche, den Wert in Zelle (1, 'D') auf 12345 zu setzen. Meine erwartete Ausgabe ist

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

Ich habe verschiedene Möglichkeiten für den Zugriff auf diese Zelle ausprobiert, z df['D'][1] . Was ist der beste Weg, dies zu tun?

1. Diese Frage bezieht sich nicht speziell auf die Warnung, aber es ist gut zu verstehen, wie dieser bestimmte Vorgang korrekt ausgeführt wird, um Situationen zu vermeiden, in denen die Warnung möglicherweise in Zukunft auftreten könnte.

Sie können dazu eine der folgenden Methoden verwenden.

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


Frage 3
Ich versuche, Werte basierend auf einer bestimmten Bedingung zu unterteilen. Ich habe einen DataFrame

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

Ich möchte 123 Werte in "D" zuweisen, so dass "C" == 5. Ich habe es versucht

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

Was scheint in Ordnung, aber ich bekomme immer noch die SettingWithCopyWarning! Wie behebe ich das?

Dies liegt wahrscheinlich wahrscheinlich an Code, der sich weiter oben in Ihrer Pipeline befindet. Hast du df2aus etwas Größerem erschaffen , wie

df2 = df[df.A > 5]

? In diesem Fall gibt die boolesche Indizierung eine Ansicht zurück und df2verweist auf das Original. Was Sie tun müssen, ist df2einer Kopie zuzuweisen :

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


Frage 4
Ich versuche, die Spalte "C" an Ort und Stelle zu löschen

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

Aber mit

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

Würfe SettingWithCopyWarning. Warum passiert dies?

Dies liegt daran, df2dass eine Ansicht aus einer anderen Schnittoperation erstellt worden sein muss, z

df2 = df[df.A > 5]

Die Lösung besteht darin, entweder wie zuvor eine copy()zu verwenden dfoder zu verwenden loc.


6
PS: Lassen Sie mich wissen, wenn Ihre Situation nicht in der Fragenliste von Abschnitt 3 aufgeführt ist. Ich werde meinen Beitrag ändern.
CS95

150

Im Allgemeinen SettingWithCopyWarninggeht es darum, Benutzern (und insbesondere neuen Benutzern) zu zeigen, dass sie möglicherweise eine Kopie bearbeiten und nicht das Original, wie sie denken. Es gibt Fehlalarme (IOW, wenn Sie wissen, was Sie tun, könnte es in Ordnung sein ). Eine Möglichkeit besteht darin, die Warnung (standardmäßig warnen ) einfach zu deaktivieren, wie von @Garrett vorgeschlagen.

Hier ist eine weitere Option:

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

Sie können das is_copyFlag für dieses Objekt auf setzen False, wodurch die Prüfung effektiv deaktiviert wird :

In [5]: dfa.is_copy = False

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

Wenn Sie explizit kopieren, erfolgt keine weitere Warnung:

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

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

Der Code, den das OP oben anzeigt, ist zwar legitim und wahrscheinlich auch etwas, das ich tue, aber technisch gesehen ein Fall für diese Warnung und kein falsches Positiv. Eine weitere Möglichkeit, nicht die Warnung haben würde , die Auswahloperation zu tun über reindex, zB

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

Oder,

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

Vielen Dank für die Informationen und die Diskussion. Ich schalte einfach die Warnung aus, damit die Konsole darüber stumm bleibt. Es klingt wie die Ansicht und Tabelle in der SQL-Datenbank. Ich muss mehr über die Vorteile der Einführung des "Kopier" -Konzepts wissen, aber meiner Meinung nach ist es eine gewisse Belastung, sich um den subtilen Unterschied der semantischen Syntax zu kümmern.
Bigbug

19
Ich bin mit der Kopie einverstanden (); Es ist klar und es hat mein Problem behoben (was falsch positiv war).
rdchambers

5
Nach dem Update auf 0.16sehe ich viel mehr False Positives. Das Problem mit False Positives ist, dass man lernt, es zu ignorieren, obwohl es manchmal legitim ist.
Bindestrich

3
@dashesy Sie verpassen den Punkt. manchmal vielleicht sogar die meiste Zeit könnte es funktionieren. Dies kann jedoch beispielsweise vorkommen, wenn der Frame größer / kleiner ist oder Sie eine Spalte mit einem anderen Typ hinzufügen, der nicht funktioniert. Das ist der Punkt. Sie tun etwas, das möglicherweise funktioniert, aber nicht garantiert ist. Dies unterscheidet sich stark von Verfallswarnungen. Wenn Sie es weiterhin verwenden möchten und es funktioniert, großartig. Aber seien Sie gewarnt.
Jeff

3
@ Jeff macht jetzt Sinn, also ist es ein undefinedVerhalten. Wenn überhaupt, sollte es einen Fehler auslösen (um Fallstricke zu vermeiden C), da apidas aktuelle Warnverhalten für die Abwärtskompatibilität sinnvoll ist , da es eingefroren ist. Und ich werde sie werfen lassen, um sie als Fehler in meinem Produktionscode ( warnings.filterwarnings('error', r'SettingWithCopyWarning) zu fangen . Auch der Vorschlag, .locmanchmal zu verwenden, hilft auch nicht (wenn es in einer Gruppe ist).
Bindestrich

41

Warnung zum Kopieren von Pandas-Datenrahmen

Wenn Sie so etwas tun:

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

pandas.ix In diesem Fall wird ein neuer, eigenständiger Datenrahmen zurückgegeben.

Alle Werte, die Sie in diesem Datenrahmen ändern möchten, ändern den ursprünglichen Datenrahmen nicht.

Darum versucht Pandas Sie zu warnen.


Warum .ix ist eine schlechte Idee

Das .ixObjekt versucht mehr als eine Sache zu tun, und für jeden, der etwas über sauberen Code gelesen hat, ist dies ein starker Geruch.

Angesichts dieses Datenrahmens:

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

Zwei Verhaltensweisen:

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

Verhalten eins: dfcopyist jetzt ein eigenständiger Datenrahmen. Das Ändern wird nicht geändertdf

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

Verhalten zwei: Dies ändert den ursprünglichen Datenrahmen.


Verwenden .loc stattdessen

Die Pandas-Entwickler erkannten, dass das .ixObjekt [spekulativ] ziemlich stinkend war, und erstellten daher zwei neue Objekte, die beim Zugriff und bei der Zuweisung von Daten helfen. (Das andere Wesen .iloc)

.loc ist schneller, weil nicht versucht wird, eine Kopie der Daten zu erstellen.

.loc soll Ihren vorhandenen Datenrahmen an Ort und Stelle ändern, was speichereffizienter ist.

.loc ist vorhersehbar, es hat ein Verhalten.


Die Lösung

In Ihrem Codebeispiel laden Sie eine große Datei mit vielen Spalten und ändern sie dann so, dass sie kleiner ist.

Die pd.read_csvFunktion kann Ihnen dabei helfen und das Laden der Datei erheblich beschleunigen.

Also anstatt dies zu tun

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

Mach das

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

Dadurch werden nur die Spalten gelesen, an denen Sie interessiert sind, und sie werden ordnungsgemäß benannt. Keine Notwendigkeit, das böse .ixObjekt zu benutzen, um magische Dinge zu tun.


"Die Pandas-Entwickler haben erkannt, dass das .ix-Objekt [spekulativ] ziemlich stinkt, und haben daher zwei neue Objekte erstellt" - was ist das andere?
jf328

3
@ jf328 .iloc Ich denke
Brian Bien

1
Ja, das ist es .iloc. Dies sind die beiden Hauptmethoden zum Indizieren von Pandas-Datenstrukturen. Lesen Sie mehr in der Dokumentation.
Ninjakannon

Wie sollte man eine DataFrame-Spalte durch Zeitstempel in eine Spalte mit einem Datums- / Uhrzeitobjekt oder einer Zeichenfolge ersetzen?
Boldnik

@boldnik Überprüfen Sie diese Antwort stackoverflow.com/a/37453925/3730397
firelynx

20

Hier beantworte ich die Frage direkt. Wie man damit umgeht?

Machen Sie eine, .copy(deep=False)nachdem Sie schneiden. Siehe pandas.DataFrame.copy .

Warten Sie, gibt ein Slice keine Kopie zurück? Immerhin versucht dies die Warnmeldung zu sagen? Lesen Sie die lange Antwort:

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

Dies gibt eine Warnung:

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

Das tut nicht:

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

Beide df0und df1sind DataFrameObjekte, aber etwas an ihnen ist anders, sodass Pandas die Warnung drucken können. Lassen Sie uns herausfinden, was es ist.

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

Wenn Sie das Diff-Tool Ihrer Wahl verwenden, werden Sie feststellen, dass der einzige wesentliche Unterschied über einige Adressen hinaus der folgende ist:

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

Die Methode, die entscheidet, ob gewarnt werden soll, ist DataFrame._check_setitem_copydie Prüfung _is_copy. Also los geht's. Machen Sie einen copyso, dass Ihr DataFrame nicht ist_is_copy .

Die Warnung schlägt vor, sie zu verwenden .loc. Wenn Sie sie jedoch .locfür einen Frame verwenden _is_copy, wird immer noch dieselbe Warnung angezeigt. Irreführend? Ja. Nervig? Sie wetten. Hilfreich? Möglicherweise, wenn eine verkettete Zuordnung verwendet wird. Die Kettenzuordnung kann jedoch nicht korrekt erkannt werden, und die Warnung wird wahllos gedruckt.


11

Dieses Thema ist wirklich verwirrend mit Pandas. Zum Glück hat es eine relativ einfache Lösung.

Das Problem ist, dass nicht immer klar ist, ob Datenfiltervorgänge (z. B. loc) eine Kopie oder eine Ansicht des DataFrame zurückgeben. Die weitere Verwendung eines solchen gefilterten DataFrame könnte daher verwirrend sein.

Die einfache Lösung lautet (es sei denn, Sie müssen mit sehr großen Datenmengen arbeiten):

Stellen Sie immer sicher, dass Sie den DataFrame vor der Zuweisung implizit kopieren, wenn Sie Werte aktualisieren müssen.

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)

Es gibt einen Tippfehler: sollte implizit explizit sein
s9527

7

Um jeden Zweifel auszuräumen, bestand meine Lösung darin, eine tiefe Kopie des Slice anstelle einer regulären Kopie zu erstellen. Dies ist je nach Kontext möglicherweise nicht anwendbar (Speicherbeschränkungen / Größe des Slice, potenzielle Leistungseinbußen - insbesondere, wenn die Kopie in einer Schleife wie bei mir usw. erfolgt).

Um klar zu sein, hier ist die Warnung, die ich erhalten habe:

/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

Illustration

Ich hatte Zweifel, dass die Warnung wegen einer Spalte ausgelöst wurde, die ich auf eine Kopie des Slice fallen ließ. Obwohl technisch nicht versucht wurde, einen Wert in der Kopie des Slice festzulegen, war dies dennoch eine Modifikation der Kopie des Slice. Im Folgenden sind die (vereinfachten) Schritte aufgeführt, die ich unternommen habe, um den Verdacht zu bestätigen. Ich hoffe, dass dies denjenigen von uns hilft, die versuchen, die Warnung zu verstehen.

Beispiel 1: Das Löschen einer Spalte auf dem Original wirkt sich auf die Kopie aus

Das wussten wir schon, aber das ist eine gesunde Erinnerung. Das ist nicht , was die Warnung ist.

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

Es ist möglich, Änderungen an df1 zu vermeiden, die sich auf df2 auswirken

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

Beispiel 2: Das Löschen einer Spalte auf der Kopie kann sich auf das Original auswirken

Dies veranschaulicht tatsächlich die Warnung.

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

Es ist möglich, Änderungen an df2 zu vermeiden, die sich auf df1 auswirken

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

Prost!


4

Das sollte funktionieren:

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

4

Einige möchten die Warnung möglicherweise einfach unterdrücken:

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

Wenn Sie das Slice einer Variablen zugewiesen haben und die Variable wie folgt festlegen möchten:

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

Und Sie möchten Jeffs Lösung nicht verwenden, weil Ihr Condition Computing df2zu lang ist oder aus einem anderen Grund, dann können Sie Folgendes verwenden:

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

df2.index.tolist() Gibt die Indizes aller Einträge in df2 zurück, die dann verwendet werden, um Spalte B im ursprünglichen Datenrahmen festzulegen.


Dies ist 9 Mal teurer als df ["B"] = Wert
Claudiu Creanga

Können Sie das @ClaudiuCreanga genauer erklären?
Gies0r

2

Für mich trat dieses Problem in einem folgenden> vereinfachten <Beispiel auf. Und ich konnte es auch lösen (hoffentlich mit einer richtigen Lösung):

alter Code mit Warnung:

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

Dies druckte die Warnung für die Zeile old_row[field] = new_row[field]

Da die Zeilen in der update_row-Methode tatsächlich vom Typ sind Series, habe ich die Zeile durch Folgendes ersetzt:

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

dh Methode für den Zugriff / die Suche nach a Series. Obwohl beide einwandfrei funktionieren und das Ergebnis gleich ist, muss ich die Warnungen auf diese Weise nicht deaktivieren (= sie für andere Probleme bei der Kettenindizierung an einem anderen Ort aufbewahren).

Ich hoffe das kann jemandem helfen.


2

Sie könnten das ganze Problem so vermeiden, glaube ich:

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

Verwenden von Zuweisen. Aus der Dokumentation : Weisen Sie einem DataFrame neue Spalten zu und geben Sie ein neues Objekt (eine Kopie) mit allen ursprünglichen Spalten zusätzlich zu den neuen zurück.

Siehe Tom Augspurgers Artikel über die Verkettung von Methoden in Pandas: https://tomaugspurger.github.io/method-chaining


2

Follow-up Anfänger Frage / Bemerkung

Vielleicht eine Klarstellung für andere Anfänger wie mich (ich komme aus R, was unter der Haube etwas anders zu funktionieren scheint). Der folgende harmlos aussehende und funktionale Code erzeugte weiterhin die SettingWithCopy-Warnung, und ich konnte nicht herausfinden, warum. Ich hatte die mit "Chained Indexing" ausgegebene Ausgabe gelesen und verstanden, aber mein Code enthält keine:

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

Aber später, viel zu spät, habe ich mir angesehen, wo die Funktion plot () heißt:

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

"Df" ist also kein Datenrahmen, sondern ein Objekt, das sich irgendwie daran erinnert, dass es durch Indizieren eines Datenrahmens erstellt wurde (ist das also eine Ansicht?), Der die Linie in plot () bilden würde.

 df['target'] = ...

gleichwertig

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

Das ist eine verkettete Indizierung. Habe ich das richtig verstanden?

Wie auch immer,

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

repariert.


1

Da diese Frage in den vorhandenen Antworten bereits vollständig erklärt und diskutiert wurde, werde ich pandasdem Kontextmanager nur einen übersichtlichen Ansatz geben, indem er pandas.option_context(Links zu Dokumenten und Beispiel) verwendet ). Es ist absolut nicht erforderlich, eine benutzerdefinierte Klasse mit allen Dunder-Methoden und anderen Glocken zu erstellen und pfeift.

Zuerst der Kontextmanager-Code selbst:

from contextlib import contextmanager

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

Dann ein Beispiel:

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

Bemerkenswert ist, dass beide Ansätze nicht geändert werden a, was für mich etwas überraschend ist, und sogar eine flache df-Kopie mit .copy(deep=False)würde verhindern, dass diese Warnung aausgelöst wird (soweit ich verstehe, sollte sich auch eine flache Kopie zumindest ändern , aber dies ist nicht der Fall 't. pandasMagie.).


hmmm, ich verstehe es, wenn eine Warnung offensichtlich falsch ist. Vermeiden Sie also besser eine Warnung, wie sie zu unterdrücken. Was denken Sie?
Jezrael

Nein, Warnung ist nur eine Warnung. Wie hier, es warnt Sie etwas könnte falsch sein , das ist toll zu wissen, aber wenn Sie wissen , was und warum Sie tun , es ist völlig in Ordnung , einige von ihnen zu unterdrücken. Weitere Informationen zum Neuzuweisen von Referenzen finden Sie in der Erklärung unter stackoverflow.com/a/20627316/4272484 .
m-dz

1

Ich hatte dieses Problem .apply()beim Zuweisen eines neuen Datenrahmens aus einem bereits vorhandenen Datenrahmen, für den ich die .query()Methode verwendet habe. Zum Beispiel:

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

Würde diesen Fehler zurückgeben. Das Update, das den Fehler in diesem Fall zu beheben scheint, besteht darin, dies zu ändern in:

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

Dies ist jedoch NICHT effizient, insbesondere wenn große Datenrahmen verwendet werden, da eine neue Kopie erstellt werden muss.

Wenn Sie die .apply()Methode zum Generieren einer neuen Spalte und ihrer Werte verwenden, können Sie den Fehler beheben, indem Sie Folgendes hinzufügen .reset_index(drop=True):

prop_df = df.query('column == "value"').reset_index(drop=True)
prop_df['new_column'] = prop_df.apply(function, axis=1)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.