Rimappa i valori nella colonna dei panda con un dict


318

Ho un dizionario che assomiglia a questo: di = {1: "A", 2: "B"}

Vorrei applicarlo alla colonna "col1" di un frame di dati simile a:

     col1   col2
0       w      a
1       1      2
2       2    NaN

ottenere:

     col1   col2
0       w      a
1       A      2
2       B    NaN

Come posso farlo al meglio? Per qualche ragione i termini googling relativi a questo mi mostrano solo collegamenti su come creare colonne da dicts e viceversa: - /

Risposte:


342

È possibile utilizzare .replace. Per esempio:

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

o direttamente sul Series, ad es df["col1"].replace(di, inplace=True).


1
Per me non funziona se col```` is tuple. The error info is non riesco a confrontare i tipi 'ndarray (dtype = object)' e 'tuple'````
Pengju Zhao

18
Sembra che questo non funziona più a tutti , che non è sorprendente dato la risposta è stata da 4 anni fa. Questa domanda ha bisogno di una nuova risposta, dato quanto sia generale l'operazione ...
PrestonH,

2
@PrestonH Funziona perfettamente per me. In esecuzione:'3.6.1 |Anaconda custom (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]'
Dan

Per me funziona. Ma se volessi sostituire i valori in TUTTE le colonne?
famargar,

2
L'unico metodo che ha funzionato per me delle risposte mostrate è stato quello di effettuare una sostituzione diretta sulla serie. Grazie!
Dirigo

243

map può essere molto più veloce di replace

Se il tuo dizionario ha più di un paio di chiavi, l'utilizzo mappuò essere molto più veloce di replace. Esistono due versioni di questo approccio, a seconda che il dizionario esegua il mapping esaustivo di tutti i possibili valori (e anche se si desidera che le non corrispondenze mantengano i loro valori o vengano convertite in NaN):

Mappatura esaustiva

In questo caso, il modulo è molto semplice:

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

Sebbene mappiù comunemente utilizzi una funzione come argomento, in alternativa può accettare un dizionario o una serie: Documentazione per Pandas.series.map

Mappatura non esaustiva

Se hai una mappatura non esaustiva e desideri conservare le variabili esistenti per le non corrispondenze, puoi aggiungere fillna:

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

come nella risposta di @ jpp qui: sostituisci i valori in una serie di panda tramite il dizionario in modo efficiente

Punti di riferimenti

Utilizzando i seguenti dati con Panda versione 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 ) })

e test con %timeit, sembra che mapsia circa 10 volte più veloce di replace.

Nota che l'accelerazione con mapvarierà con i tuoi dati. La maggiore velocità sembra essere con dizionari di grandi dimensioni e sostituzioni esaustive. Vedi la risposta @jpp (collegata sopra) per benchmark e discussioni più ampi.


17
L'ultimo blocco di codice per questa risposta non è certamente il più elegante, ma questa risposta merita un po 'di credito. È ordini di grandezza più veloci per i dizionari di grandi dimensioni e non consuma tutta la mia RAM. Rimappato un file di 10.000 righe usando un dizionario che conteneva circa 9 milioni di voci in mezzo minuto. La df.replacefunzione, sebbene ordinata e utile per piccoli dadi, si è arrestata in modo anomalo dopo aver eseguito per circa 20 minuti.
Griffinc,


@griffinc Grazie per il feedback e nota che da allora ho aggiornato questa risposta con un modo molto più semplice di fare il caso non esaustivo (grazie a @jpp)
JohnE

1
mapfunziona anche su un indice in cui non sono riuscito a trovare un modo per farloreplace
Max Ghenis,

1
@AlexSB Non posso dare una risposta completamente generale, ma penso che la mappa sarebbe molto più veloce e realizzerebbe (penso) la stessa cosa. In genere, l'unione sarà più lenta di altre opzioni che fanno la stessa cosa.
Giovanni,

59

C'è un po 'di ambiguità nella tua domanda. Esistono almeno tre due interpretazioni:

  1. le chiavi si diriferiscono ai valori di indice
  2. le chiavi si diriferiscono ai df['col1']valori
  3. le chiavi si diriferiscono alle posizioni degli indici (non alla domanda del PO, ma gettate per divertimento).

Di seguito è una soluzione per ogni caso.


Caso 1: se le chiavi di disi riferiscono a valori di indice, è possibile utilizzare il updatemetodo:

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

Per esempio,

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)

i rendimenti

  col1 col2
1    w    a
2    B   30
0    A  NaN

Ho modificato i valori del tuo post originale, quindi è più chiaro cosa updatesta facendo. Nota come le chiavi disono associate ai valori dell'indice. L'ordine dei valori dell'indice, ovvero le posizioni dell'indice , non ha importanza.


Caso 2: se le chiavi in ​​si diriferiscono a df['col1']valori, allora @DanAllan e @DSM mostrano come ottenere ciò con 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)

i rendimenti

  col1 col2
1    w    a
2    A   30
0    B  NaN

Notare come in questo caso le chiavi disono state modificate per corrispondere ai valori in df['col1'].


Caso 3: se le chiavi si diriferiscono a posizioni di indice, è possibile utilizzare

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

da

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)

i rendimenti

  col1 col2
1    A    a
2   10   30
0    B  NaN

Qui, la prima e la terza riga sono state modificate, perché le chiavi disono 0e 2, che con l'indicizzazione basata su 0 di Python si riferiscono alla prima e alla terza posizione.


replaceè ugualmente buono, e forse una parola migliore per quello che sta succedendo qui.
Dan Allan,

Il frame di dati di destinazione postato dall'OP non elimina l'ambiguità? Tuttavia, questa risposta è utile, quindi +1.
DSM,

@DSM: Oops, hai ragione non c'è possibilità di Case3, ma non credo che il frame di dati di destinazione dell'OP distingue Case1 da Case2 poiché i valori dell'indice sono uguali ai valori della colonna.
unutbu,

Come molti altri pubblicati, purtroppo il metodo di @ DSM non ha funzionato per me, ma il caso 1 di @ unutbu ha funzionato. update()sembra un po 'kludgy rispetto a replace(), ma almeno funziona.
Geoff,

4

Aggiungendo a questa domanda se hai mai più di una colonna da rimappare in un data frame:

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

Spero che possa essere utile a qualcuno.

Saluti


1
Questa funzionalità è già fornita da DataFrame.replace(), anche se non so quando è stata aggiunta.
AMC

3

DSM ha la risposta accettata, ma la codifica non sembra funzionare per tutti. Eccone uno che funziona con l'attuale versione di Panda (0.23.4 dell'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())

Vedrai che assomiglia a:

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

I documenti per pandas.DataFrame.replace sono qui .


Non ho mai avuto problemi a far funzionare la risposta di DSM e immagino che, dato l'elevato voto totale, nemmeno la maggior parte delle altre persone. Potresti voler essere più specifico sul problema che stai riscontrando. Forse ha a che fare con i tuoi dati di esempio che sono diversi da quelli del DSM?
Giovanni

Hmm, forse un problema di versione. Tuttavia, entrambe le risposte sono qui ora.
parole anche il

1
La soluzione nella risposta accettata funziona solo su determinati tipi, Series.map()sembra più flessibile.
AMC

2

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

Cosa succede quando il tuo didict è un dict di liste? Come si può mappare un solo valore nell'elenco?
FaCoffee

Puoi, anche se non vedo perché lo faresti.
AMC

2

Dato mapè più veloce di sostituire (la soluzione di @ JohnE) è necessario fare attenzione con i mapping non esaustivi in ​​cui si intende mappare valori specificiNaN . Il metodo corretto in questo caso richiede che tu maskla Serie quando tu .fillna, altrimenti si annulla la mappatura 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

1

Una bella soluzione completa che mantiene una mappa delle etichette della tua classe:

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

In questo modo, puoi in qualsiasi momento fare riferimento all'etichetta di classe originale di labels_dict.


1

Come estensione a ciò che è stato proposto da Nico Coallier (si applicano a più colonne) e U10-Forward (usando lo stile di applicazione dei metodi), e riassumendolo in una riga, propongo:

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

La .transform()elabora ogni colonna come serie. Contrariamente al .apply()quale passa le colonne aggregate in un DataFrame.

Di conseguenza è possibile applicare il metodo Serie map().

Alla fine, e ho scoperto questo comportamento grazie a U10, puoi usare l'intera serie nell'espressione .get (). A meno che non abbia frainteso il suo comportamento e che elabori sequenzialmente la serie anziché in modo bitwisely.
I .get(x,x)conti per i valori che non hai menzionato nel dizionario di mappatura che altrimenti verrebbero considerati Nan dal .map()metodo


La .transform()elabora ogni colonna come serie. Contrariamente al .apply()quale passa le colonne aggregate in un DataFrame. Ho appena provato, apply()funziona benissimo. Non è necessario locnemmeno usare , questo sembra eccessivamente complesso. df[["col1", "col2"]].apply(lambda col: col.map(lambda elem: my_dict.get(elem, elem)))dovrebbe funzionare bene. I .get(x,x)conti per i valori che non hai menzionato nel tuo dizionario di mappatura che verrebbero considerati come Nan altrimenti dal .map()metodo che potresti anche usare in fillna()seguito.
AMC

Alla fine, e ho scoperto questo comportamento grazie a U10, puoi usare l'intera serie nell'espressione .get (). A meno che non abbia frainteso il suo comportamento e che elabori in sequenza la serie anziché in modo bitwisely. Non riesco a riprodurre questo, puoi elaborare? Le variabili con nomi identici probabilmente giocano un ruolo qui.
AMC

0

Un approccio panda più nativo è applicare una funzione di sostituzione come di seguito:

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) 

Una volta definita la funzione, è possibile applicarla al proprio frame di dati.

di = {1: "A", 2: "B"}
df['col1'] = df.apply(lambda row: multiple_replace(di, row['col1']), axis=1)

Un approccio panda più nativo è quello di applicare una funzione di sostituzione come di seguito. Come è più "nativo" (idiomatico?) Rispetto ai metodi molto più semplici forniti da Pandas?
AMC
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.