Ordnen Sie die Werte in der Pandas-Spalte mit einem Diktat neu zu

315

Ich habe ein Wörterbuch, das so aussieht: di = {1: "A", 2: "B"}

Ich möchte es auf die Spalte "col1" eines Datenrahmens anwenden, ähnlich wie:

     col1   col2
0       w      a
1       1      2
2       2    NaN

bekommen:

     col1   col2
0       w      a
1       A      2
2       B    NaN

Wie kann ich das am besten machen? Aus irgendeinem Grund zeigt mir das Googeln von Begriffen in diesem Zusammenhang nur Links darüber, wie man Spalten aus Diktaten erstellt und umgekehrt: - /

TheChymera
quelle

Antworten:

337

Sie können verwenden .replace. Zum Beispiel:

>>> df = pd.DataFrame({'col2': {0: 'a', 1: 2, 2: np.nan}, 'col1': {0: 'w', 1: 1, 2: 2}})
>>> di = {1: "A", 2: "B"}
>>> df
  col1 col2
0    w    a
1    1    2
2    2  NaN
>>> df.replace({"col1": di})
  col1 col2
0    w    a
1    A    2
2    B  NaN

oder direkt auf dem Series, dh df["col1"].replace(di, inplace=True).

DSM
quelle
1
Es funktioniert nicht für mich, wenn col```` is tuple. The error info is die Typen 'ndarray (dtype = object)' und 'tuple'``` nicht verglichen werden können
Pengju Zhao
18
Es sieht aus wie diese nicht mehr funktioniert überhaupt , was nicht überraschend , da die Antwort von vor 4 Jahren war. Diese Frage muss neu beantwortet werden, wenn man
bedenkt
2
@ PrestonH Es funktioniert perfekt für mich. Laufen:'3.6.1 |Anaconda custom (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]'
Dan
Für mich geht das. Aber wie ist es, wenn ich Werte in ALLEN Spalten ersetzen möchte?
Famargar
2
Die einzige Methode, die für mich bei den angezeigten Antworten funktioniert hat, war ein direkter Ersatz für die Serie. Vielen Dank!
Dirigo
241

map kann viel schneller sein als replace

Wenn Ihr Wörterbuch mehr als ein paar Schlüssel enthält, mapkann die Verwendung viel schneller sein als replace. Es gibt zwei Versionen dieses Ansatzes, je nachdem, ob Ihr Wörterbuch alle möglichen Werte vollständig abbildet (und ob Sie möchten, dass Nicht-Übereinstimmungen ihre Werte beibehalten oder in NaNs konvertiert werden):

Vollständiges Mapping

In diesem Fall ist das Formular sehr einfach:

df['col1'].map(di)       # note: if the dictionary does not exhaustively map all
                         # entries then non-matched entries are changed to NaNs

Obwohl mapam häufigsten eine Funktion als Argument verwendet wird, kann alternativ ein Wörterbuch oder eine Reihe verwendet werden: Dokumentation für Pandas.series.map

Nicht erschöpfendes Mapping

Wenn Sie eine nicht vollständige Zuordnung haben und die vorhandenen Variablen für Nichtübereinstimmungen beibehalten möchten, können Sie Folgendes hinzufügen fillna:

df['col1'].map(di).fillna(df['col1'])

wie in der Antwort von @ jpp hier: Ersetzen Sie Werte in einer Pandas-Reihe effizient über ein Wörterbuch

Benchmarks

Verwenden der folgenden Daten mit Pandas Version 0.23.1:

di = {1: "A", 2: "B", 3: "C", 4: "D", 5: "E", 6: "F", 7: "G", 8: "H" }
df = pd.DataFrame({ 'col1': np.random.choice( range(1,9), 100000 ) })

und beim Testen mit %timeitscheint mapes ungefähr 10x schneller zu sein als replace.

Beachten Sie, dass Ihre Beschleunigung mit mapmit Ihren Daten variiert. Die größte Beschleunigung scheint bei großen Wörterbüchern und umfassenden Ersetzungen zu liegen. Weitere Informationen zu Benchmarks und Diskussionen finden Sie unter @ jpp-Antwort (oben verlinkt).

JohnE
quelle
17
Der letzte Codeblock für diese Antwort ist sicherlich nicht der eleganteste, aber diese Antwort verdient etwas Anerkennung. Bei großen Wörterbüchern ist es um Größenordnungen schneller und verbraucht nicht den gesamten Arbeitsspeicher. Es wurde eine Datei mit 10.000 Zeilen mithilfe eines Wörterbuchs neu zugeordnet, das in einer halben Minute etwa 9 Millionen Einträge enthielt. Die df.replaceFunktion war zwar ordentlich und nützlich für kleine Diktate, stürzte jedoch nach etwa 20 Minuten ab.
Griffin
@griffinc Vielen Dank für das Feedback und beachten Sie, dass ich diese Antwort seitdem mit einer viel einfacheren Methode aktualisiert habe, um den nicht erschöpfenden Fall zu lösen (danke an @jpp)
JohnE
1
maparbeitet auch an einem Index, bei dem ich keinen Weg finden konnte, dies zu tunreplace
Max Ghenis
1
@AlexSB Ich kann keine ganz allgemeine Antwort geben, aber ich denke, die Karte wäre viel schneller und würde (glaube ich) dasselbe erreichen. Im Allgemeinen ist das Zusammenführen langsamer als bei anderen Optionen, die dasselbe tun.
JohnE
59

Ihre Frage ist etwas mehrdeutig. Es gibt mindestens drei zwei Interpretationen:

  1. Die Tasten in dibeziehen sich auf Indexwerte
  2. Die Tasten dibeziehen sich auf df['col1']Werte
  3. Die Tasten in dibeziehen sich auf Indexpositionen (nicht die Frage des OP, sondern zum Spaß.).

Nachfolgend finden Sie eine Lösung für jeden Fall.


Fall 1: Wenn sich die Schlüssel von diauf Indexwerte beziehen sollen, können Sie die folgende updateMethode verwenden:

df['col1'].update(pd.Series(di))

Zum Beispiel,

import pandas as pd
import numpy as np

df = pd.DataFrame({'col1':['w', 10, 20],
                   'col2': ['a', 30, np.nan]},
                  index=[1,2,0])
#   col1 col2
# 1    w    a
# 2   10   30
# 0   20  NaN

di = {0: "A", 2: "B"}

# The value at the 0-index is mapped to 'A', the value at the 2-index is mapped to 'B'
df['col1'].update(pd.Series(di))
print(df)

ergibt

  col1 col2
1    w    a
2    B   30
0    A  NaN

Ich habe die Werte aus Ihrem ursprünglichen Beitrag geändert, damit klarer wird, was zu updatetun ist. Beachten Sie, wie die Schlüssel in dimit Indexwerten verknüpft sind. Die Reihenfolge der Indexwerte - , die der Index Standorte - spielt keine Rolle.


Fall 2: Wenn sich die Schlüssel in diauf df['col1']Werte beziehen, zeigen @DanAllan und @DSM, wie dies erreicht werden kann mit replace:

import pandas as pd
import numpy as np

df = pd.DataFrame({'col1':['w', 10, 20],
                   'col2': ['a', 30, np.nan]},
                  index=[1,2,0])
print(df)
#   col1 col2
# 1    w    a
# 2   10   30
# 0   20  NaN

di = {10: "A", 20: "B"}

# The values 10 and 20 are replaced by 'A' and 'B'
df['col1'].replace(di, inplace=True)
print(df)

ergibt

  col1 col2
1    w    a
2    A   30
0    B  NaN

Beachten Sie, wie in diesem Fall die Schlüssel digeändert wurden, um den Werten in zu entsprechen df['col1'].


Fall 3: Wenn sich die Schlüssel in diauf Indexpositionen beziehen, können Sie sie verwenden

df['col1'].put(di.keys(), di.values())

schon seit

df = pd.DataFrame({'col1':['w', 10, 20],
                   'col2': ['a', 30, np.nan]},
                  index=[1,2,0])
di = {0: "A", 2: "B"}

# The values at the 0 and 2 index locations are replaced by 'A' and 'B'
df['col1'].put(di.keys(), di.values())
print(df)

ergibt

  col1 col2
1    A    a
2   10   30
0    B  NaN

Hier ist die erste und dritte Reihe wurde geändert, da die in Tasten disind 0und 2, die mit Python 0-basierter Indizierung an die ersten und dritte Stelle verweisen.

unutbu
quelle
replaceist ebenso gut und vielleicht ein besseres Wort für das, was hier passiert.
Dan Allan
Beseitigt der vom OP veröffentlichte Zieldatenrahmen nicht die Mehrdeutigkeit? Trotzdem ist diese Antwort nützlich, also +1.
DSM
@DSM: Hoppla, Sie haben Recht, es gibt keine Möglichkeit für Fall3, aber ich glaube nicht, dass der Zieldatenrahmen des OP Fall1 von Fall2 unterscheidet, da die Indexwerte den Spaltenwerten entsprechen.
Unutbu
Wie eine Reihe anderer Beiträge hat die Methode von @ DSM bei mir leider nicht funktioniert, aber der Fall 1 von @ unutbu hat funktioniert. update()scheint ein wenig klobig im Vergleich zu replace(), aber zumindest funktioniert es.
Geoff
4

Hinzufügen zu dieser Frage, wenn Sie jemals mehr als eine Spalte in einem Datendatenrahmen neu zuordnen müssen:

def remap(data,dict_labels):
    """
    This function take in a dictionnary of labels : dict_labels 
    and replace the values (previously labelencode) into the string.

    ex: dict_labels = {{'col1':{1:'A',2:'B'}}

    """
    for field,values in dict_labels.items():
        print("I am remapping %s"%field)
        data.replace({field:values},inplace=True)
    print("DONE")

    return data

Hoffe, es kann für jemanden nützlich sein.

Prost

Nico Coallier
quelle
1
Diese Funktionalität wird bereits von bereitgestellt DataFrame.replace(), obwohl ich nicht weiß, wann sie hinzugefügt wurde.
AMC
3

DSM hat die akzeptierte Antwort, aber die Codierung scheint nicht für alle zu funktionieren. Hier ist eine, die mit der aktuellen Version von Pandas funktioniert (0.23.4 ab 8/2018):

import pandas as pd

df = pd.DataFrame({'col1': [1, 2, 2, 3, 1],
            'col2': ['negative', 'positive', 'neutral', 'neutral', 'positive']})

conversion_dict = {'negative': -1, 'neutral': 0, 'positive': 1}
df['converted_column'] = df['col2'].replace(conversion_dict)

print(df.head())

Sie werden sehen, es sieht aus wie:

   col1      col2  converted_column
0     1  negative                -1
1     2  positive                 1
2     2   neutral                 0
3     3   neutral                 0
4     1  positive                 1

Die Dokumente für pandas.DataFrame.replace finden Sie hier .

Worte dafür
quelle
Ich hatte nie ein Problem damit, die Antwort von DSM zum Laufen zu bringen, und ich denke, angesichts der hohen Stimmenzahl, die die meisten anderen Leute auch nicht hatten. Vielleicht möchten Sie genauer auf das Problem eingehen, das Sie haben. Vielleicht hat es mit Ihren Beispieldaten zu tun, die sich von denen von DSM unterscheiden?
JohnE
Hmm, vielleicht ein Versionsproblem. Trotzdem sind beide Antworten jetzt hier.
Worte für den
1
Die Lösung in der akzeptierten Antwort funktioniert nur bei bestimmten Typen, Series.map()scheint flexibler.
AMC
2

Oder machen Sie apply:

df['col1'].apply(lambda x: {1: "A", 2: "B"}.get(x,x))

Demo:

>>> df['col1']=df['col1'].apply(lambda x: {1: "A", 2: "B"}.get(x,x))
>>> df
  col1 col2
0    w    a
1    1    2
2    2  NaN
>>> 
U10-Vorwärts
quelle
Was passiert, wenn Ihr diDiktat ein Diktat von Listen ist? Wie können Sie nur einen Wert in der Liste zuordnen?
FaCoffee
Sie können, obwohl ich nicht verstehe, warum Sie würden.
AMC
2

Gegeben mapist schneller als Ersetzen (@ JohnEs Lösung). Sie müssen bei nicht erschöpfenden ZuordnungenNaN vorsichtig sein, denen Sie bestimmte Werte zuordnen möchten . Die richtige Methode in diesem Fall erfordert, dass Sie maskdie Serie, wenn Sie .fillna, sonst machen Sie die Zuordnung zu rückgängig NaN.

import pandas as pd
import numpy as np

d = {'m': 'Male', 'f': 'Female', 'missing': np.NaN}
df = pd.DataFrame({'gender': ['m', 'f', 'missing', 'Male', 'U']})

keep_nan = [k for k,v in d.items() if pd.isnull(v)]
s = df['gender']

df['mapped'] = s.map(d).fillna(s.mask(s.isin(keep_nan)))

    gender  mapped
0        m    Male
1        f  Female
2  missing     NaN
3     Male    Male
4        U       U
ALollz
quelle
1

Eine schöne Komplettlösung, die eine Karte Ihrer Klassenbezeichnungen enthält:

labels = features['col1'].unique()
labels_dict = dict(zip(labels, range(len(labels))))
features = features.replace({"col1": labels_dict})

Auf diese Weise können Sie jederzeit auf die ursprüngliche Klassenbezeichnung von label_dict verweisen.

dorien
quelle
0

Ein nativerer Pandas-Ansatz besteht darin, eine Ersetzungsfunktion wie folgt anzuwenden:

def multiple_replace(dict, text):
  # Create a regular expression  from the dictionary keys
  regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))

  # For each match, look-up corresponding value in dictionary
  return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text) 

Sobald Sie die Funktion definiert haben, können Sie sie auf Ihren Datenrahmen anwenden.

di = {1: "A", 2: "B"}
df['col1'] = df.apply(lambda row: multiple_replace(di, row['col1']), axis=1)
Amir Imani
quelle
Ein nativerer Pandas-Ansatz besteht darin, eine Ersetzungsfunktion wie folgt anzuwenden. Wie ist diese "native" (idiomatisch?) Als die viel einfacheren Methoden, die von Pandas bereitgestellt werden?
AMC
0

Als Erweiterung dessen, was von Nico Coallier (auf mehrere Spalten anwendbar) und U10-Forward (unter Verwendung des Methodenstils "anwenden" vorgeschlagen wurde, und als Zusammenfassung in einem Einzeiler schlage ich vor:

df.loc[:,['col1','col2']].transform(lambda x: x.map(lambda x: {1: "A", 2: "B"}.get(x,x))

Die .transform()verarbeitet jede Spalte als eine Reihe. Im Gegensatz dazu werden .apply()die in einem DataFrame aggregierten Spalten übergeben.

Folglich können Sie die Serienmethode anwenden map().

Schließlich, und ich habe dieses Verhalten dank U10 entdeckt, können Sie die gesamte Serie im Ausdruck .get () verwenden. Es sei denn, ich habe sein Verhalten falsch verstanden und es verarbeitet die Serie nacheinander anstatt bitweise.
Die .get(x,x)Konten für die Werte, die Sie in Ihrem Mapping-Wörterbuch nicht erwähnt haben und die von der .map()Methode ansonsten als Nan betrachtet würden

louisD
quelle
Die .transform()verarbeitet jede Spalte als eine Reihe. Im Gegensatz dazu werden .apply()die in einem DataFrame aggregierten Spalten übergeben. Ich habe es gerade versucht, apply()funktioniert gut. Es besteht auch keine Notwendigkeit zu verwenden loc, dies scheint zu komplex. df[["col1", "col2"]].apply(lambda col: col.map(lambda elem: my_dict.get(elem, elem)))sollte gut funktionieren. Die .get(x,x)Konten für die Werte, die Sie in Ihrem Mapping-Wörterbuch nicht erwähnt haben und die ansonsten von der .map()Methode, die Sie auch fillna()später verwenden könnten, als Nan betrachtet würden .
AMC
Schließlich, und ich habe dieses Verhalten dank U10 entdeckt, können Sie die gesamte Serie im Ausdruck .get () verwenden. Es sei denn, ich habe sein Verhalten falsch verstanden und es verarbeitet die Serie nacheinander anstatt bitweise. Ich kann das nicht reproduzieren, können Sie das näher erläutern? Die identisch benannten Variablen spielen hier wahrscheinlich eine Rolle.
AMC