Ändern Sie einen Wert basierend auf einem anderen Wert in Pandas

106

Ich versuche, meinen Stata-Code für Geschwindigkeitsverbesserungen in Python umzuprogrammieren, und ich wurde in Richtung PANDAS gezeigt. Es fällt mir jedoch schwer, mich mit der Verarbeitung der Daten zu beschäftigen.

Angenommen, ich möchte alle Werte in der Spaltenüberschrift 'ID' durchlaufen. Wenn diese ID mit einer bestimmten Nummer übereinstimmt, möchte ich zwei entsprechende Werte ändern: Vorname und Nachname.

In Stata sieht es so aus:

replace FirstName = "Matt" if ID==103
replace LastName =  "Jones" if ID==103

Dies ersetzt also alle Werte in FirstName, die den Werten von ID == 103 bis Matt entsprechen.

In PANDAS versuche ich so etwas

df = read_csv("test.csv")
for i in df['ID']:
    if i ==103:
          ...

Ich bin mir nicht sicher, wohin ich von hier aus gehen soll. Irgendwelche Ideen?

Parselzunge
quelle

Antworten:

180

Eine Möglichkeit besteht darin, die Slicing- und Indizierungsfunktionen von Python zu verwenden, um die Stellen, an denen Ihre Bedingung gilt, logisch auszuwerten und die Daten dort zu überschreiben.

Angenommen, Sie können Ihre Daten direkt pandasmit laden, pandas.read_csvdann könnte der folgende Code für Sie hilfreich sein.

import pandas
df = pandas.read_csv("test.csv")
df.loc[df.ID == 103, 'FirstName'] = "Matt"
df.loc[df.ID == 103, 'LastName'] = "Jones"

Wie in den Kommentaren erwähnt, können Sie die Zuordnung zu beiden Spalten auch auf einmal vornehmen:

df.loc[df.ID == 103, ['FirstName', 'LastName']] = 'Matt', 'Jones'

Beachten Sie, dass Sie pandasVersion 0.11 oder höher benötigen , um locZuweisungsvorgänge überschreiben zu können.


Eine andere Möglichkeit besteht darin, eine sogenannte verkettete Zuweisung zu verwenden. Das Verhalten ist weniger stabil und wird daher nicht als die beste Lösung angesehen ( in den Dokumenten wird ausdrücklich davon abgeraten ), aber es ist nützlich zu wissen über:

import pandas
df = pandas.read_csv("test.csv")
df['FirstName'][df.ID == 103] = "Matt"
df['LastName'][df.ID == 103] = "Jones"
ely
quelle
16
Wie wäre es, auch dieses Aroma hinzuzufügen:df.loc[df.ID == 103, ['FirstName', 'LastName']] = 'Matt', 'Jones'
Boud
2
-1 "Eine andere Möglichkeit besteht darin, eine sogenannte verkettete Zuweisung zu verwenden." Nein, nachdrücklich, nein. Es ist nur nützlich zu wissen, dass eine verkettete Zuordnung nicht zuverlässig ist. Es ist nicht so, dass es eine zuverlässige, nicht optimale Lösung ist, die Situation ist viel schlimmer . Sie haben dies sogar an anderer Stelle im Stapelüberlauf bestätigt . Bitte vermeiden Sie die Illusion, dass eine verkettete Zuordnung eine praktikable Option ist. Die ersten beiden Methoden, die Sie angegeben haben, waren ausreichend und sind der bevorzugte Weg, dies zu tun.
Phillip Cloud
9
Ich bin nicht einverstanden. Ich verstehe nicht, warum Sie weiterhin pedantisch versuchen zu behaupten, dass eine verkettete Zuordnung kein praktikabler Weg ist. Ich habe anerkannt, dass dies nicht der bevorzugte Weg ist. Was willst du noch. Es ist unsinnig zu handeln , wie dies nicht ist ein Weg , es zu tun. In meinem System (Version 0.8) ist dies der richtige Weg . Ich bin nicht an Ihren Up-Votes interessiert, wenn Sie diese Position einnehmen. Fühlen Sie sich frei, Ihren Punkt mit einer Abwertung zu signalisieren, aber ich habe bereits über Ihren Punkt nachgedacht und bin damit nicht einverstanden.
Ely
11
Das Internet ist ein ernstes Geschäft. Auf jeden Fall, EMS, wusste ich zu schätzen, dass es die Option gibt.
Parseltongue
Ein Problem, auf das Sie möglicherweise stoßen, ist, dass die CSV Punkte / Punkte in den Spaltennamen enthält und die Zuweisungen durcheinander geraten. Sie können die Spalten folgendermaßen reparieren: cols = df.columns cols = cols.map (Lambda x: x.replace ('.', '_'), Wenn isinstance (x, str) else x) df.columns = cols
ski_squaw
37

Sie können verwenden map, es kann Werte aus einer Diktatur oder sogar einer benutzerdefinierten Funktion abbilden.

Angenommen, dies ist Ihr df:

    ID First_Name Last_Name
0  103          a         b
1  104          c         d

Erstellen Sie die Diktate:

fnames = {103: "Matt", 104: "Mr"}
lnames = {103: "Jones", 104: "X"}

Und Karte:

df['First_Name'] = df['ID'].map(fnames)
df['Last_Name'] = df['ID'].map(lnames)

Das Ergebnis wird sein:

    ID First_Name Last_Name
0  103       Matt     Jones
1  104         Mr         X

Oder verwenden Sie eine benutzerdefinierte Funktion:

names = {103: ("Matt", "Jones"), 104: ("Mr", "X")}
df['First_Name'] = df['ID'].map(lambda x: names[x][0])
Rutger Kassies
quelle
2
Wird dadurch kein KeyError generiert, wenn die Werte in Ihrem Diktat nicht vorhanden sind?
EdChum
1
Die benutzerdefinierte Funktion wird funktionieren, die anderen funktionieren trotzdem. Aber ich nahm an, dass das dictfür das Mapping erstellt wurde. Andernfalls kann eine Überprüfung / Reinigung durchgeführt werden, basierend auf:df.ID.isin(names.keys())
Rutger Kassies
Die benutzerdefinierte Funktion kann in eine beliebige (nicht anonyme) Funktion erweitert werden.
user989762
14

Die ursprüngliche Frage befasst sich mit einem bestimmten engen Anwendungsfall. Für diejenigen, die allgemeinere Antworten benötigen, sind hier einige Beispiele:

Erstellen einer neuen Spalte mit Daten aus anderen Spalten

Angesichts des folgenden Datenrahmens:

import pandas as pd
import numpy as np

df = pd.DataFrame([['dog', 'hound', 5],
                   ['cat', 'ragdoll', 1]],
                  columns=['animal', 'type', 'age'])

In[1]:
Out[1]:
  animal     type  age
----------------------
0    dog    hound    5
1    cat  ragdoll    1

Im Folgenden fügen wir eine neue descriptionSpalte als Verkettung anderer Spalten hinzu, indem wir die +Operation verwenden, die für Serien überschrieben wird. Ausgefallene String-Formatierungen, F-Strings usw. funktionieren hier nicht, da dies +für Skalare und nicht für 'primitive' Werte gilt:

df['description'] = 'A ' + df.age.astype(str) + ' years old ' \
                    + df.type + ' ' + df.animal

In [2]: df
Out[2]:
  animal     type  age                description
-------------------------------------------------
0    dog    hound    5    A 5 years old hound dog
1    cat  ragdoll    1  A 1 years old ragdoll cat

Wir bekommen 1 yearsfür die Katze (anstelle von 1 year), die wir unten unter Verwendung von Bedingungen reparieren werden.

Ändern einer vorhandenen Spalte mit Bedingungen

Hier ersetzen wir die ursprüngliche animalSpalte durch Werte aus anderen Spalten und verwenden np.where, um eine bedingte Teilzeichenfolge basierend auf dem Wert von age:

# append 's' to 'age' if it's greater than 1
df.animal = df.animal + ", " + df.type + ", " + \
    df.age.astype(str) + " year" + np.where(df.age > 1, 's', '')

In [3]: df
Out[3]:
                 animal     type  age
-------------------------------------
0   dog, hound, 5 years    hound    5
1  cat, ragdoll, 1 year  ragdoll    1

Ändern mehrerer Spalten mit Bedingungen

Ein flexiblerer Ansatz besteht darin, .apply()einen gesamten Datenrahmen anstelle einer einzelnen Spalte aufzurufen:

def transform_row(r):
    r.animal = 'wild ' + r.type
    r.type = r.animal + ' creature'
    r.age = "{} year{}".format(r.age, r.age > 1 and 's' or '')
    return r

df.apply(transform_row, axis=1)

In[4]:
Out[4]:
         animal            type      age
----------------------------------------
0    wild hound    dog creature  5 years
1  wild ragdoll    cat creature   1 year

Im obigen Code transform_row(r)nimmt die Funktion ein SeriesObjekt, das eine bestimmte Zeile darstellt (angezeigt durch axis=1, der Standardwert von axis=0liefert ein SeriesObjekt für jede Spalte). Dies vereinfacht die Verarbeitung, da wir mithilfe der Spaltennamen auf die tatsächlichen 'primitiven' Werte in der Zeile zugreifen können und andere Zellen in der angegebenen Zeile / Spalte sichtbar sind.

ccpizza
quelle
1
Vielen Dank, dass Sie sich die Zeit genommen haben, eine so umfassende Antwort zu verfassen. Sehr geschätzt.
Parseltongue
Vielen Dank für diese äußerst hilfreiche Antwort. Ein Follow-up - Was ist, wenn wir eine Spalte ändern möchten, indem wir die Spalte berechnen, anstatt eine Zeichenfolge zu ändern? Was ist zum Beispiel im obigen Beispiel, wenn wir die Spalte df.age mit 7 multiplizieren möchten, wenn df.animal == 'dog'? Danke dir!
GbG
1
@GbG: np.whereist wahrscheinlich das, wonach Sie suchen, siehe z. B. stackoverflow.com/a/42540310/191246, aber es ist auch möglich, dass Sie die Logik nicht in eine skalare Operation einpassen können, dann müssten Sie explizit transformieren Die Zelle ähnelt numerisch der transform_row
Vorgehensweise
Danke @ccpizza! Genau das, wonach ich gesucht habe.
GbG
13

Diese Frage wird möglicherweise immer noch so oft gestellt, dass es sich lohnt, einen Nachtrag zur Antwort von Herrn Kassies anzubieten. Die dictintegrierte Klasse kann in Unterklassen unterteilt werden, sodass ein Standardwert für fehlende Schlüssel zurückgegeben wird. Dieser Mechanismus funktioniert gut für Pandas. Aber siehe unten.

Auf diese Weise können wichtige Fehler vermieden werden.

>>> import pandas as pd
>>> data = { 'ID': [ 101, 201, 301, 401 ] }
>>> df = pd.DataFrame(data)
>>> class SurnameMap(dict):
...     def __missing__(self, key):
...         return ''
...     
>>> surnamemap = SurnameMap()
>>> surnamemap[101] = 'Mohanty'
>>> surnamemap[301] = 'Drake'
>>> df['Surname'] = df['ID'].apply(lambda x: surnamemap[x])
>>> df
    ID  Surname
0  101  Mohanty
1  201         
2  301    Drake
3  401         

Das Gleiche kann auf folgende Weise einfacher gemacht werden. Die Verwendung des 'Standard'-Arguments für die getMethode eines Diktierobjekts macht es unnötig, ein Diktat in Unterklassen zu unterteilen.

>>> import pandas as pd
>>> data = { 'ID': [ 101, 201, 301, 401 ] }
>>> df = pd.DataFrame(data)
>>> surnamemap = {}
>>> surnamemap[101] = 'Mohanty'
>>> surnamemap[301] = 'Drake'
>>> df['Surname'] = df['ID'].apply(lambda x: surnamemap.get(x, ''))
>>> df
    ID  Surname
0  101  Mohanty
1  201         
2  301    Drake
3  401         
Bill Bell
quelle
1
Dies ist bei weitem die beste und einfachste Antwort, die ich je gesehen habe, mit ausgezeichneter Standardbehandlung. Danke dir.
Brendan
@Brendan: Oh! Vielen Dank.
Bill Bell