i panda creano una nuova colonna basata sui valori di altre colonne / applicano una funzione di più colonne, per quanto riguarda le righe


316

Voglio applicare la mia funzione personalizzata (che utilizza una scala if-else) per questi sei colonne ( ERI_Hispanic, ERI_AmerInd_AKNatv, ERI_Asian, ERI_Black_Afr.Amer, ERI_HI_PacIsl, ERI_White) in ogni riga della mia dataframe.

Ho provato metodi diversi da altre domande ma non riesco ancora a trovare la risposta giusta al mio problema. Il pezzo critico di questo è che se la persona viene considerata come ispanica non può essere considerata come qualsiasi altra cosa. Anche se hanno un "1" in un'altra colonna di etnia, vengono comunque considerati come ispanici non due o più razze. Allo stesso modo, se la somma di tutte le colonne ERI è maggiore di 1, vengono conteggiate come due o più razze e non possono essere considerate come un'etnia unica (tranne che per l'ispanico). Speriamo che abbia senso. Qualsiasi aiuto sarà molto apprezzato.

È quasi come fare un ciclo for attraverso ogni riga e se ogni record soddisfa un criterio vengono aggiunti a un elenco ed eliminati dall'originale.

Dal frame di dati in basso ho bisogno di calcolare una nuova colonna in base alle seguenti specifiche in SQL:

CRITERI ========================== ========================= =======

IF [ERI_Hispanic] = 1 THEN RETURN Hispanic
ELSE IF SUM([ERI_AmerInd_AKNatv] + [ERI_Asian] + [ERI_Black_Afr.Amer] + [ERI_HI_PacIsl] + [ERI_White]) > 1 THEN RETURN Two or More
ELSE IF [ERI_AmerInd_AKNatv] = 1 THEN RETURN A/I AK Native
ELSE IF [ERI_Asian] = 1 THEN RETURN Asian
ELSE IF [ERI_Black_Afr.Amer] = 1 THEN RETURN Black/AA
ELSE IF [ERI_HI_PacIsl] = 1 THEN RETURN Haw/Pac Isl.”
ELSE IF [ERI_White] = 1 THEN RETURN White

Commento: se la bandiera ERI per ispanica è vera (1), il dipendente è classificato come "ispanico"

Commento: se è vero più di 1 flag ERI non ispanico, restituire "Due o più"

====================== DATAFRAME ===========================

     lname          fname       rno_cd  eri_afr_amer    eri_asian   eri_hawaiian    eri_hispanic    eri_nat_amer    eri_white   rno_defined
0    MOST           JEFF        E       0               0           0               0               0               1           White
1    CRUISE         TOM         E       0               0           0               1               0               0           White
2    DEPP           JOHNNY              0               0           0               0               0               1           Unknown
3    DICAP          LEO                 0               0           0               0               0               1           Unknown
4    BRANDO         MARLON      E       0               0           0               0               0               0           White
5    HANKS          TOM         0                       0           0               0               0               1           Unknown
6    DENIRO         ROBERT      E       0               1           0               0               0               1           White
7    PACINO         AL          E       0               0           0               0               0               1           White
8    WILLIAMS       ROBIN       E       0               0           1               0               0               0           White
9    EASTWOOD       CLINT       E       0               0           0               0               0               1           White

La tua particolare funzione è solo una lunga scala if-else in cui i valori di alcune variabili hanno la priorità su altri. Sarebbe chiamato un decodificatore di priorità nel linguaggio dell'ingegneria hardware.
smci,

Risposte:


408

OK, due passaggi per questo - il primo è scrivere una funzione che fa la traduzione desiderata - Ho messo insieme un esempio basato sul tuo pseudo-codice:

def label_race (row):
   if row['eri_hispanic'] == 1 :
      return 'Hispanic'
   if row['eri_afr_amer'] + row['eri_asian'] + row['eri_hawaiian'] + row['eri_nat_amer'] + row['eri_white'] > 1 :
      return 'Two Or More'
   if row['eri_nat_amer'] == 1 :
      return 'A/I AK Native'
   if row['eri_asian'] == 1:
      return 'Asian'
   if row['eri_afr_amer']  == 1:
      return 'Black/AA'
   if row['eri_hawaiian'] == 1:
      return 'Haw/Pac Isl.'
   if row['eri_white'] == 1:
      return 'White'
   return 'Other'

Potresti voler andare oltre questo, ma sembra fare il trucco - nota che il parametro che entra nella funzione è considerato un oggetto Serie etichettato "riga".

Quindi, utilizzare la funzione apply in panda per applicare la funzione - ad es

df.apply (lambda row: label_race(row), axis=1)

Notare l'identificatore axis = 1, ciò significa che l'applicazione viene eseguita su una riga anziché a livello di colonna. I risultati sono qui:

0           White
1        Hispanic
2           White
3           White
4           Other
5           White
6     Two Or More
7           White
8    Haw/Pac Isl.
9           White

Se sei soddisfatto di questi risultati, eseguilo di nuovo, salvando i risultati in una nuova colonna nel tuo frame di dati originale.

df['race_label'] = df.apply (lambda row: label_race(row), axis=1)

Il frame di dati risultante è simile al seguente (scorrere verso destra per vedere la nuova colonna):

      lname   fname rno_cd  eri_afr_amer  eri_asian  eri_hawaiian   eri_hispanic  eri_nat_amer  eri_white rno_defined    race_label
0      MOST    JEFF      E             0          0             0              0             0          1       White         White
1    CRUISE     TOM      E             0          0             0              1             0          0       White      Hispanic
2      DEPP  JOHNNY    NaN             0          0             0              0             0          1     Unknown         White
3     DICAP     LEO    NaN             0          0             0              0             0          1     Unknown         White
4    BRANDO  MARLON      E             0          0             0              0             0          0       White         Other
5     HANKS     TOM    NaN             0          0             0              0             0          1     Unknown         White
6    DENIRO  ROBERT      E             0          1             0              0             0          1       White   Two Or More
7    PACINO      AL      E             0          0             0              0             0          1       White         White
8  WILLIAMS   ROBIN      E             0          0             1              0             0          0       White  Haw/Pac Isl.
9  EASTWOOD   CLINT      E             0          0             0              0             0          1       White         White

69
solo una nota: se stai solo inserendo la riga nella tua funzione, puoi semplicemente fare:df.apply(label_race, axis=1)
Paul H,

1
Se volessi fare qualcosa di simile con un'altra riga, potrei usare la stessa funzione? Ad esempio, dai risultati, se ['race_label'] == "White" restituisce 'White' e così via. Ma se ['race_label'] == 'Unknown' restituisce i valori dalla colonna ['rno_defined']. Suppongo che la stessa funzione funzionerebbe, ma non riesco a capire come ottenere i valori dall'altra colonna.
Dave,

2
Potresti scrivere una nuova funzione, che guarda il campo "race_label", e inviare i risultati in un nuovo campo, oppure - e penso che in questo caso potrebbe essere meglio, modificare la funzione originale, cambiando la return 'Other'riga finale a return row['rno_defined']cui dovrebbe sostituisci il valore di quella colonna nei casi in cui l'insieme delle istruzioni if ​​/ then non trova una corrispondenza (ovvero dove attualmente vedi "Altro").
Thomas Kimber,

9
Puoi semplificare: df.apply(lambda row: label_race (row),axis=1)adf.apply(label_race, axis=1)
user48956 il

5
Nelle versioni più recenti, se ottieni "SettingWithCopyWarning", dovresti guardare il metodo "Assegna". Vedi: stackoverflow.com/a/12555510/3015186
NP8

218

Poiché questo è il primo risultato di Google per "nuova colonna di Panda da altri", ecco un semplice esempio:

import pandas as pd

# make a simple dataframe
df = pd.DataFrame({'a':[1,2], 'b':[3,4]})
df
#    a  b
# 0  1  3
# 1  2  4

# create an unattached column with an index
df.apply(lambda row: row.a + row.b, axis=1)
# 0    4
# 1    6

# do same but attach it to the dataframe
df['c'] = df.apply(lambda row: row.a + row.b, axis=1)
df
#    a  b  c
# 0  1  3  4
# 1  2  4  6

Se lo ottieni SettingWithCopyWarningpuoi farlo anche in questo modo:

fn = lambda row: row.a + row.b # define a function for the new column
col = df.apply(fn, axis=1) # get column data with an index
df = df.assign(c=col.values) # assign values to column 'c'

Fonte: https://stackoverflow.com/a/12555510/243392

E se il nome della tua colonna include spazi puoi usare la sintassi in questo modo:

df = df.assign(**{'some column name': col.values})

Ed ecco la documentazione per applicare e assegnare .


1
Risposta breve, distillata fino all'essenziale!
Frode Akselsen,

1
Ricevo SettingWithCopyWarningquando lo faccio df['c'] = df.apply(lambda row: row.a + row.b, axis=1) È un vero problema qui o non dovrei preoccuparmene?
Nate,

2
@Nate Non ho mai ricevuto quell'avvertimento - forse dipende dai dati nel frame di dati? Ma ho modificato la risposta basandomi su un'altra risposta del 2017.
Brian Burns,

57

Le risposte sopra sono perfettamente valide, ma esiste una soluzione vettoriale, sotto forma di numpy.select. Ciò consente di definire le condizioni, quindi definire gli output per tali condizioni, in modo molto più efficiente rispetto all'utilizzo di apply:


Innanzitutto, definire le condizioni:

conditions = [
    df['eri_hispanic'] == 1,
    df[['eri_afr_amer', 'eri_asian', 'eri_hawaiian', 'eri_nat_amer', 'eri_white']].sum(1).gt(1),
    df['eri_nat_amer'] == 1,
    df['eri_asian'] == 1,
    df['eri_afr_amer'] == 1,
    df['eri_hawaiian'] == 1,
    df['eri_white'] == 1,
]

Ora, definisci gli output corrispondenti:

outputs = [
    'Hispanic', 'Two Or More', 'A/I AK Native', 'Asian', 'Black/AA', 'Haw/Pac Isl.', 'White'
]

Infine, usando numpy.select:

res = np.select(conditions, outputs, 'Other')
pd.Series(res)

0           White
1        Hispanic
2           White
3           White
4           Other
5           White
6     Two Or More
7           White
8    Haw/Pac Isl.
9           White
dtype: object

Perché dovrebbe numpy.selectessere usato apply? Ecco alcuni controlli delle prestazioni:

df = pd.concat([df]*1000)

In [42]: %timeit df.apply(lambda row: label_race(row), axis=1)
1.07 s ± 4.16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [44]: %%timeit
    ...: conditions = [
    ...:     df['eri_hispanic'] == 1,
    ...:     df[['eri_afr_amer', 'eri_asian', 'eri_hawaiian', 'eri_nat_amer', 'eri_white']].sum(1).gt(1),
    ...:     df['eri_nat_amer'] == 1,
    ...:     df['eri_asian'] == 1,
    ...:     df['eri_afr_amer'] == 1,
    ...:     df['eri_hawaiian'] == 1,
    ...:     df['eri_white'] == 1,
    ...: ]
    ...:
    ...: outputs = [
    ...:     'Hispanic', 'Two Or More', 'A/I AK Native', 'Asian', 'Black/AA', 'Haw/Pac Isl.', 'White'
    ...: ]
    ...:
    ...: np.select(conditions, outputs, 'Other')
    ...:
    ...:
3.09 ms ± 17 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

L'uso numpy.selectci offre prestazioni notevolmente migliorate e la discrepanza aumenterà solo con l'aumentare dei dati.


8
Questa soluzione è così sottovalutata. Sapevo che avrei potuto fare qualcosa di simile con apply, ma cercavo un'alternativa in quanto devo fare quell'operazione per migliaia di file. Sono contento di aver trovato il tuo post.
mlx

Ho problemi con la creazione di qualcosa di simile. Ottengo il messaggio di errore "il valore di verità di una serie è ambiguo ...". Il mio codice è Kansas_City = ['ND', 'SD', 'NE', 'KS', 'MN', 'IA', 'MO'] condizioni = [df_merge ['state_alpha'] in Kansas_City] output = [' Kansas City '] df_merge [' Region '] = np.select (condizioni, output,' Altro ') Qualche aiuto?
Shawn Schreier,

3
Questa dovrebbe essere la risposta accettata. Gli altri vanno bene, ma una volta che stai lavorando su dati più grandi, questo è l'unico che funziona e funziona incredibilmente veloce.
TheProletariat

29

.apply()accetta una funzione come primo parametro; passare la label_racefunzione in questo modo:

df['race_label'] = df.apply(label_race, axis=1)

Non è necessario creare una funzione lambda per passare una funzione.


12

prova questo,

df.loc[df['eri_white']==1,'race_label'] = 'White'
df.loc[df['eri_hawaiian']==1,'race_label'] = 'Haw/Pac Isl.'
df.loc[df['eri_afr_amer']==1,'race_label'] = 'Black/AA'
df.loc[df['eri_asian']==1,'race_label'] = 'Asian'
df.loc[df['eri_nat_amer']==1,'race_label'] = 'A/I AK Native'
df.loc[(df['eri_afr_amer'] + df['eri_asian'] + df['eri_hawaiian'] + df['eri_nat_amer'] + df['eri_white']) > 1,'race_label'] = 'Two Or More'
df.loc[df['eri_hispanic']==1,'race_label'] = 'Hispanic'
df['race_label'].fillna('Other', inplace=True)

OPERAZIONE:

     lname   fname rno_cd  eri_afr_amer  eri_asian  eri_hawaiian  \
0      MOST    JEFF      E             0          0             0   
1    CRUISE     TOM      E             0          0             0   
2      DEPP  JOHNNY    NaN             0          0             0   
3     DICAP     LEO    NaN             0          0             0   
4    BRANDO  MARLON      E             0          0             0   
5     HANKS     TOM    NaN             0          0             0   
6    DENIRO  ROBERT      E             0          1             0   
7    PACINO      AL      E             0          0             0   
8  WILLIAMS   ROBIN      E             0          0             1   
9  EASTWOOD   CLINT      E             0          0             0   

   eri_hispanic  eri_nat_amer  eri_white rno_defined    race_label  
0             0             0          1       White         White  
1             1             0          0       White      Hispanic  
2             0             0          1     Unknown         White  
3             0             0          1     Unknown         White  
4             0             0          0       White         Other  
5             0             0          1     Unknown         White  
6             0             0          1       White   Two Or More  
7             0             0          1       White         White  
8             0             0          0       White  Haw/Pac Isl.  
9             0             0          1       White         White 

utilizzare .locinvece di apply.

migliora la vettorializzazione.

.loc funziona in modo semplice, maschera le righe in base alla condizione, applica i valori alle righe di blocco.

per maggiori dettagli visita, .loc docs

Metriche delle prestazioni:

Risposta accettata:

def label_race (row):
   if row['eri_hispanic'] == 1 :
      return 'Hispanic'
   if row['eri_afr_amer'] + row['eri_asian'] + row['eri_hawaiian'] + row['eri_nat_amer'] + row['eri_white'] > 1 :
      return 'Two Or More'
   if row['eri_nat_amer'] == 1 :
      return 'A/I AK Native'
   if row['eri_asian'] == 1:
      return 'Asian'
   if row['eri_afr_amer']  == 1:
      return 'Black/AA'
   if row['eri_hawaiian'] == 1:
      return 'Haw/Pac Isl.'
   if row['eri_white'] == 1:
      return 'White'
   return 'Other'

df=pd.read_csv('dataser.csv')
df = pd.concat([df]*1000)

%timeit df.apply(lambda row: label_race(row), axis=1)

1,15 s ± 46,5 ms per ciclo (media ± deviazione standard di 7 cicli, 1 ciclo ciascuno)

La mia risposta proposta:

def label_race(df):
    df.loc[df['eri_white']==1,'race_label'] = 'White'
    df.loc[df['eri_hawaiian']==1,'race_label'] = 'Haw/Pac Isl.'
    df.loc[df['eri_afr_amer']==1,'race_label'] = 'Black/AA'
    df.loc[df['eri_asian']==1,'race_label'] = 'Asian'
    df.loc[df['eri_nat_amer']==1,'race_label'] = 'A/I AK Native'
    df.loc[(df['eri_afr_amer'] + df['eri_asian'] + df['eri_hawaiian'] + df['eri_nat_amer'] + df['eri_white']) > 1,'race_label'] = 'Two Or More'
    df.loc[df['eri_hispanic']==1,'race_label'] = 'Hispanic'
    df['race_label'].fillna('Other', inplace=True)
df=pd.read_csv('s22.csv')
df = pd.concat([df]*1000)

%timeit label_race(df)

24,7 ms ± 1,7 ms per loop (media ± deviazione standard di 7 cicli, 10 loop ciascuno)

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.