Creazione condizionale di Panda di una colonna serie / dataframe


314

Ho un frame di dati sulla falsariga di quanto segue:

    Type       Set
1    A          Z
2    B          Z           
3    B          X
4    C          Y

Voglio aggiungere un'altra colonna al frame di dati (o generare una serie) della stessa lunghezza del frame di dati (= numero uguale di record / righe) che imposta un colore verde se Set = 'Z' e 'rosso' se Set = altrimenti .

Qual'è il miglior modo per farlo?

Risposte:


712

Se hai solo due opzioni tra cui scegliere:

df['color'] = np.where(df['Set']=='Z', 'green', 'red')

Per esempio,

import pandas as pd
import numpy as np

df = pd.DataFrame({'Type':list('ABBC'), 'Set':list('ZZXY')})
df['color'] = np.where(df['Set']=='Z', 'green', 'red')
print(df)

i rendimenti

  Set Type  color
0   Z    A  green
1   Z    B  green
2   X    B    red
3   Y    C    red

Se hai più di due condizioni, usanp.select . Ad esempio, se vuoi coloresserlo

  • yellow quando (df['Set'] == 'Z') & (df['Type'] == 'A')
  • altrimenti bluequando(df['Set'] == 'Z') & (df['Type'] == 'B')
  • altrimenti purplequando(df['Type'] == 'B')
  • altrimenti black,

quindi utilizzare

df = pd.DataFrame({'Type':list('ABBC'), 'Set':list('ZZXY')})
conditions = [
    (df['Set'] == 'Z') & (df['Type'] == 'A'),
    (df['Set'] == 'Z') & (df['Type'] == 'B'),
    (df['Type'] == 'B')]
choices = ['yellow', 'blue', 'purple']
df['color'] = np.select(conditions, choices, default='black')
print(df)

che cede

  Set Type   color
0   Z    A  yellow
1   Z    B    blue
2   X    B  purple
3   Y    C   black

1
non funziona se inserisco due condizioni all'interno delle quali clausola con e
Amol Sharma,

2
df ['color'] = list (np.where (df ['Set'] == 'Z', 'green', 'red')) sopprimerà l'avvertimento panda: un valore sta cercando di essere impostato su una copia di una sezione da un DataFrame. Prova a usare .loc [row_indexer, col_indexer] = value invece
denson

3
'verde' e 'rosso' possono anche essere sostituiti con l'aritmetica della colonna. ad es .df['foo'] = np.where(df['Set']=='Z', df['Set'], df['Type'].shift(1))
Alejandro il

np.where crea una nuova colonna? Ho usato questo codice e quando eseguo df.color.head () ottengo: l'oggetto 'numpy.ndarray' non ha attributo 'head'
vvv

3
È un peccato non poterlo votare più volte. Un voto non sembra abbastanza.
Harper,

120

La comprensione dell'elenco è un altro modo per creare un'altra colonna in modo condizionale. Se stai lavorando con i tipi di oggetto in colonne, come nel tuo esempio, la comprensione dell'elenco supera in genere la maggior parte degli altri metodi.

Esempio di comprensione dell'elenco:

df['color'] = ['red' if x == 'Z' else 'green' for x in df['Set']]

% timeit test:

import pandas as pd
import numpy as np

df = pd.DataFrame({'Type':list('ABBC'), 'Set':list('ZZXY')})
%timeit df['color'] = ['red' if x == 'Z' else 'green' for x in df['Set']]
%timeit df['color'] = np.where(df['Set']=='Z', 'green', 'red')
%timeit df['color'] = df.Set.map( lambda x: 'red' if x == 'Z' else 'green')

1000 loops, best of 3: 239 µs per loop
1000 loops, best of 3: 523 µs per loop
1000 loops, best of 3: 263 µs per loop

4
Si noti che, con frame di dati molto più grandi (think pd.DataFrame({'Type':list('ABBC')*100000, 'Set':list('ZZXY')*100000})-size), numpy.wheresupera map, ma la comprensione dell'elenco è re (circa il 50% più veloce di numpy.where).
nero

3
È possibile utilizzare il metodo di comprensione dell'elenco se la condizione richiede informazioni da più colonne? Sto cercando qualcosa del genere (questo non funziona):df['color'] = ['red' if (x['Set'] == 'Z') & (x['Type'] == 'B') else 'green' for x in df]
Mappi

2
Aggiungi le frecce al frame di dati, quindi puoi accedere a più colonne tramite la riga: ['rosso' if (riga ['Set'] == 'Z') & (riga ['Tipo'] == 'B') altrimenti 'verde 'per indice, remare in df.iterrows ()]
cheekybastard

1
Nota che questa bella soluzione non funzionerà se devi prendere valori sostitutivi da un'altra serie nel frame di dati, comedf['color_type'] = np.where(df['Set']=='Z', 'green', df['Type'])
Paul Rougieux,

@cheekybastard Oppure no, poiché .iterrows()è notoriamente lento e il DataFrame non deve essere modificato durante l'iterazione.
AMC,

21

Un altro modo in cui ciò potrebbe essere raggiunto è

df['color'] = df.Set.map( lambda x: 'red' if x == 'Z' else 'green')

Buon approccio, questo può essere memorizzato per una maggiore efficienza (in set di dati più grandi), anche se richiederebbe un passaggio aggiuntivo.
Yaakov Bressler

21

Ecco ancora un altro modo per abbellire questo gatto, usando un dizionario per mappare nuovi valori sulle chiavi dell'elenco:

def map_values(row, values_dict):
    return values_dict[row]

values_dict = {'A': 1, 'B': 2, 'C': 3, 'D': 4}

df = pd.DataFrame({'INDICATOR': ['A', 'B', 'C', 'D'], 'VALUE': [10, 9, 8, 7]})

df['NEW_VALUE'] = df['INDICATOR'].apply(map_values, args = (values_dict,))

Com'è:

df
Out[2]: 
  INDICATOR  VALUE  NEW_VALUE
0         A     10          1
1         B      9          2
2         C      8          3
3         D      7          4

Questo approccio può essere molto potente quando hai molte ifelsedichiarazioni di tipo (ad esempio molti valori univoci da sostituire).

E ovviamente puoi sempre farlo:

df['NEW_VALUE'] = df['INDICATOR'].map(values_dict)

Ma questo approccio è tre volte più lento di quello applydall'alto, sulla mia macchina.

E potresti anche farlo, usando dict.get:

df['NEW_VALUE'] = [values_dict.get(v, None) for v in df['INDICATOR']]

Mi piace questa risposta perché mostra come eseguire più sostituzioni di valori
Monica Heddneck,

Ma questo approccio è più di tre volte più lento dell'approccio dall'alto, sulla mia macchina. Come li hai confrontati? Dalle mie misurazioni rapide, la .map()soluzione è ~ 10 volte più veloce di .apply().
AMC,

Aggiornamento: su 100.000.000 di righe, 52 valori di stringa, .apply()richiede 47 secondi, contro solo 5,91 secondi per .map().
AMC,

19

Quanto segue è più lento degli approcci cronometrati qui , ma possiamo calcolare la colonna aggiuntiva in base al contenuto di più di una colonna e più di due valori possono essere calcolati per la colonna aggiuntiva.

Semplice esempio usando solo la colonna "Set":

def set_color(row):
    if row["Set"] == "Z":
        return "red"
    else:
        return "green"

df = df.assign(color=df.apply(set_color, axis=1))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C  green

Esempio con più colori e più colonne prese in considerazione:

def set_color(row):
    if row["Set"] == "Z":
        return "red"
    elif row["Type"] == "C":
        return "blue"
    else:
        return "green"

df = df.assign(color=df.apply(set_color, axis=1))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C   blue

Modifica (21/06/2019): utilizzo di plydata

È anche possibile usare plydata per fare questo tipo di cose (questo sembra ancora più lento dell'uso assigne apply, comunque).

from plydata import define, if_else

Semplice if_else:

df = define(df, color=if_else('Set=="Z"', '"red"', '"green"'))

print(df)
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B  green
3   Y    C  green

Nidificato if_else:

df = define(df, color=if_else(
    'Set=="Z"',
    '"red"',
    if_else('Type=="C"', '"green"', '"blue"')))

print(df)                            
  Set Type  color
0   Z    A    red
1   Z    B    red
2   X    B   blue
3   Y    C  green

10

Forse questo è stato possibile con gli aggiornamenti più recenti di Panda, ma penso che la seguente sia la risposta più breve e forse la migliore per la domanda, finora. È possibile utilizzare il .locmetodo e utilizzare una o più condizioni in base alle proprie esigenze.

Riepilogo codice:

df=pd.DataFrame(dict(Type='A B B C'.split(), Set='Z Z X Y'.split()))
df['Color'] = "red"
df.loc[(df['Set']=="Z"), 'Color'] = "green"

#practice!
df.loc[(df['Set']=="Z")&(df['Type']=="B")|(df['Type']=="C"), 'Color'] = "purple"

Spiegazione:

df=pd.DataFrame(dict(Type='A B B C'.split(), Set='Z Z X Y'.split()))

# df so far: 
  Type Set  
0    A   Z 
1    B   Z 
2    B   X 
3    C   Y

aggiungi una colonna "color" e imposta tutti i valori su "rosso"

df['Color'] = "red"

Applica la tua singola condizione:

df.loc[(df['Set']=="Z"), 'Color'] = "green"


# df: 
  Type Set  Color
0    A   Z  green
1    B   Z  green
2    B   X    red
3    C   Y    red

o più condizioni se si desidera:

df.loc[(df['Set']=="Z")&(df['Type']=="B")|(df['Type']=="C"), 'Color'] = "purple"

Puoi leggere gli operatori logici di Panda e la selezione condizionale qui: Operatori logici per l'indicizzazione booleana in Panda


2
Il migliore finora. Probabilmente potresti aggiungere altre condizioni che potrebbero essere il codicedf.loc[(df['Set']=="Z") & (df['Type']=="A"), 'Color'] = "green"
Salvador Vigo,

2
Questa dovrebbe essere la risposta accettata. In realtà idiomatico ed estensibile.
AMC

1

Una fodera con .apply()metodo è la seguente:

df['color'] = df['Set'].apply(lambda set_: 'green' if set_=='Z' else 'red')

Successivamente, il dfframe di dati è simile al seguente:

>>> print(df)
  Type Set  color
0    A   Z  green
1    B   Z  green
2    B   X    red
3    C   Y    red

0

Se stai lavorando con dati di massa, un approccio memoized sarebbe il migliore:

# First create a dictionary of manually stored values
color_dict = {'Z':'red'}

# Second, build a dictionary of "other" values
color_dict_other = {x:'green' for x in df['Set'].unique() if x not in color_dict.keys()}

# Next, merge the two
color_dict.update(color_dict_other)

# Finally, map it to your column
df['color'] = df['Set'].map(color_dict)

Questo approccio sarà più veloce quando hai molti valori ripetuti. La mia regola generale è quella di memorizzare quando: data_size> 10**4& n_distinct<data_size/4

Ex Memoize in un caso 10.000 righe con 2.500 o meno valori distinti.


Bene, quindi con solo 2 valori distinti da mappare, 100.000.000 di righe, sono necessari 6,67 secondi per l'esecuzione senza "memoization" e 9,86 secondi con.
AMC,

100.000.000 di righe, 52 valori distinti, in cui 1 di tali mappe corrisponde al primo valore di output e gli altri 51 corrispondono tutti all'altro: 7,99 secondi senza memoization, 11,1 secondi con.
AMC,

I tuoi valori sono in ordine casuale? O sono schiena contro schiena? L'alta velocità dei panda potrebbe essere dovuta alla memorizzazione nella cache @AMC
Yaakov Bressler

1
I tuoi valori sono in ordine casuale? O sono schiena contro schiena? I valori sono casuali, selezionati usando random.choices().
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.