Come applicare una funzione a due colonne del dataframe di Pandas


368

Supponiamo di avere un dfche ha colonne di 'ID', 'col_1', 'col_2'. E definisco una funzione:

f = lambda x, y : my_function_expression.

Ora voglio applicare la fa df's due colonne 'col_1', 'col_2'per calcolare elemento-saggio una nuova colonna 'col_3', un po' come:

df['col_3'] = df[['col_1','col_2']].apply(f)  
# Pandas gives : TypeError: ('<lambda>() takes exactly 2 arguments (1 given)'

Come fare ?

** Aggiungi esempio di dettaglio come di seguito ***

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

#df['col_3'] = df[['col_1','col_2']].apply(get_sublist,axis=1)
# expect above to output df as below 

  ID  col_1  col_2            col_3
0  1      0      1       ['a', 'b']
1  2      2      4  ['c', 'd', 'e']
2  3      3      5  ['d', 'e', 'f']

4
puoi applicare f direttamente alle colonne: df ['col_3'] = f (df ['col_1'], df ['col_2'])
btel,

1
sarebbe utile sapere cosa fsta facendo
tehmisvh,

2
no, df ['col_3'] = f (df ['col_1'], df ['col_2']) non funziona. Per f accetta solo input scalari, non input vettoriali. OK, puoi assumere f = lambda x, y: x + y. (ovviamente, la mia vera f non è così semplice, altrimenti posso direttamente df ['col_3'] = df ['col_1'] + df ['col_2'])
bigbug

1
Ho trovato domande e risposte correlate nell'URL di seguito, ma il mio problema è calcolare una nuova colonna per due colonne esistenti, non 2 per 1. stackoverflow.com/questions/12356501/...
bigbug

Penso che la mia risposta stackoverflow.com/a/52854800/5447172 risponda a questo nel modo più pitonico / pandanico, senza soluzioni alternative o indicizzazione numerica. Produce esattamente l'output richiesto nel tuo esempio.
ajrwhite,

Risposte:


291

Ecco un esempio usando applysul dataframe, con cui sto chiamandoaxis = 1 .

Si noti che la differenza è che invece di provare a passare due valori alla funzione f, riscrivere la funzione per accettare un oggetto Serie Panda, quindi indicizzare la Serie per ottenere i valori necessari.

In [49]: df
Out[49]: 
          0         1
0  1.000000  0.000000
1 -0.494375  0.570994
2  1.000000  0.000000
3  1.876360 -0.229738
4  1.000000  0.000000

In [50]: def f(x):    
   ....:  return x[0] + x[1]  
   ....:  

In [51]: df.apply(f, axis=1) #passes a Series object, row-wise
Out[51]: 
0    1.000000
1    0.076619
2    1.000000
3    1.646622
4    1.000000

A seconda del caso d'uso, a volte è utile creare un groupoggetto Panda e quindi utilizzarlo applynel gruppo.


Sì, ho provato a utilizzare applicare, ma non riesco a trovare l'espressione di sintassi valida. E se ogni riga di df è unica, usi ancora groupby?
Bigbug,

Aggiunto un esempio alla mia risposta, spero che questo faccia quello che stai cercando. In caso contrario, fornire una funzione di esempio più specifica poiché sumviene risolta correttamente con uno dei metodi suggeriti finora.
Aman,

1
Vuoi incollare il tuo codice? Riscrivo la funzione: def get_sublist (x): return mylist [x [1]: x [2] + 1] e df ['col_3'] = df.apply (get_sublist, axis = 1) dà 'ValueError: gli operandi potrebbero non essere trasmesso insieme alle forme (2) (3) '
bigbug

3
@Aman: con Pandas versione 0.14.1 (e possibilmente precedente), use può usare anche un'espressione lambda. Dai l' dfoggetto che hai definito, è un altro approccio (con risultati equivalenti) df.apply(lambda x: x[0] + x[1], axis = 1).
Jubbles

2
@CanCeylan puoi semplicemente usare i nomi delle colonne nella funzione invece degli indici, quindi non devi preoccuparti di cambiare l'ordine, o ottenere l'indice per nome, ad es. Vedi stackoverflow.com/questions/13021654/…
Davos

167

C'è un modo pulito e su una linea per farlo in Panda:

df['col_3'] = df.apply(lambda x: f(x.col_1, x.col_2), axis=1)

Questo permette f di essere una funzione definita dall'utente con più valori di input e utilizza nomi di colonna (sicuri) anziché indici numerici (non sicuri) per accedere alle colonne.

Esempio con dati (basato sulla domanda originale):

import pandas as pd

df = pd.DataFrame({'ID':['1', '2', '3'], 'col_1': [0, 2, 3], 'col_2':[1, 4, 5]})
mylist = ['a', 'b', 'c', 'd', 'e', 'f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

df['col_3'] = df.apply(lambda x: get_sublist(x.col_1, x.col_2), axis=1)

Uscita di print(df):

  ID  col_1  col_2      col_3
0  1      0      1     [a, b]
1  2      2      4  [c, d, e]
2  3      3      5  [d, e, f]

Se i nomi delle colonne contengono spazi o condividono un nome con un attributo di frame di dati esistente, puoi indicizzare con parentesi quadre:

df['col_3'] = df.apply(lambda x: f(x['col 1'], x['col 2']), axis=1)

2
Nota, se si utilizza axis=1e viene chiamata namela colonna, in realtà non verranno restituiti i dati della colonna ma il index. Simile a ottenere namein a groupby(). Ho risolto questo problema rinominando la mia colonna.
Tom Hemmes,

2
QUESTO È! Non mi rendevo conto che potresti inserire funzioni definite dall'utente con più parametri di input in lambdas. È importante notare (penso) che stai utilizzando DF.apply () anziché Series.apply (). Questo ti permette di indicizzare il df usando le due colonne che vuoi, e passare l'intera colonna nella funzione, ma poiché stai usando apply (), applica la funzione in modo elementare in fondo all'intera colonna. Brillante! Grazie per la pubblicazione!
Data-file del

1
FINALMENTE! Mi hai salvato la giornata!
Mysterio,

Credo che il modo suggerito per farlo sia df.loc [:, 'new col'] = df.apply .....
valearner

@valearner Non credo ci sia motivo di preferire .locnell'esempio. Potrebbe essere necessario se lo si adatta a un'altra impostazione del problema (ad es. Lavorare con le sezioni).
ajrwhite,

86

Una soluzione semplice è:

df['col_3'] = df[['col_1','col_2']].apply(lambda x: f(*x), axis=1)

1
in che modo questa risposta è diversa dall'approccio nella domanda: df ['col_3'] = df [['col_1', 'col_2']]. applica (f) solo per confermare, l'approccio nella domanda non ha funzionato perché il il poster non ha specificato questo asse = 1, il valore predefinito è axis = 0?
Lost1,

1
Questa risposta è paragonabile alla risposta di @ Anman ma un po 'più forte. Sta costruendo una funzione anonima che accetta un iterabile e la decomprime prima di passarla alla funzione f.
Tiao

39

Una domanda interessante! la mia risposta come di seguito:

import pandas as pd

def sublst(row):
    return lst[row['J1']:row['J2']]

df = pd.DataFrame({'ID':['1','2','3'], 'J1': [0,2,3], 'J2':[1,4,5]})
print df
lst = ['a','b','c','d','e','f']

df['J3'] = df.apply(sublst,axis=1)
print df

Produzione:

  ID  J1  J2
0  1   0   1
1  2   2   4
2  3   3   5
  ID  J1  J2      J3
0  1   0   1     [a]
1  2   2   4  [c, d]
2  3   3   5  [d, e]

Ho cambiato il nome della colonna in ID, J1, J2, J3 per garantire l'ID <J1 <J2 <J3, in modo che la colonna venga visualizzata nella sequenza corretta.

Un'altra versione breve:

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'J1': [0,2,3], 'J2':[1,4,5]})
print df
lst = ['a','b','c','d','e','f']

df['J3'] = df.apply(lambda row:lst[row['J1']:row['J2']],axis=1)
print df

23

Il metodo che stai cercando è Series.combine. Tuttavia, sembra che si debba prestare attenzione ai tipi di dati. Nel tuo esempio, chiameresti ingenuamente (come ho fatto durante il test della risposta)

df['col_3'] = df.col_1.combine(df.col_2, func=get_sublist)

Tuttavia, questo genera l'errore:

ValueError: setting an array element with a sequence.

La mia ipotesi migliore è che sembra aspettarsi che il risultato sia dello stesso tipo della serie che chiama il metodo (df.col_1 qui). Tuttavia, i seguenti lavori:

df['col_3'] = df.col_1.astype(object).combine(df.col_2, func=get_sublist)

df

   ID   col_1   col_2   col_3
0   1   0   1   [a, b]
1   2   2   4   [c, d, e]
2   3   3   5   [d, e, f]

12

Il modo in cui hai scritto f ha bisogno di due input. Se guardi il messaggio di errore, dice che non stai fornendo due input a f, solo uno. Il messaggio di errore è corretto
La mancata corrispondenza è perché df [['' col1 ',' col2 ']] restituisce un singolo frame di dati con due colonne, non due colonne separate.

È necessario modificare la f in modo che sia necessario un singolo input, mantenere il frame di dati sopra come input, quindi suddividerlo in x, y all'interno del corpo della funzione. Quindi fai tutto ciò di cui hai bisogno e restituisci un singolo valore.

È necessaria la firma di questa funzione perché la sintassi è .apply (f) Quindi f deve prendere la singola cosa = dataframe e non due cose che è ciò che la tua attuale f si aspetta.

Dal momento che non hai fornito il corpo di f, non posso aiutarti più in dettaglio, ma questo dovrebbe fornire la via d'uscita senza cambiare fondamentalmente il tuo codice o utilizzare altri metodi piuttosto che applicare


12

Vado a votare per np.vectorize. Ti consente di sparare su x numero di colonne e di non occuparti del frame di dati nella funzione, quindi è ottimo per funzioni che non controlli o esegui qualcosa come l'invio di 2 colonne e una costante in una funzione (ad es. Col_1, col_2, 'pippo').

import numpy as np
import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

#df['col_3'] = df[['col_1','col_2']].apply(get_sublist,axis=1)
# expect above to output df as below 

df.loc[:,'col_3'] = np.vectorize(get_sublist, otypes=["O"]) (df['col_1'], df['col_2'])


df

ID  col_1   col_2   col_3
0   1   0   1   [a, b]
1   2   2   4   [c, d, e]
2   3   3   5   [d, e, f]

1
Questo in realtà non risponde alla domanda usando i panda.
mnky9800n,

18
La domanda è "Come applicare una funzione a due colonne del dataframe di Pandas" non "Come applicare una funzione a due colonne del dataframe di Pandas usando solo i metodi di Pandas" e numpy è una dipendenza di Pandas, quindi è necessario averlo installato comunque, quindi questa sembra una strana obiezione.
Trae Wallace,

12

La restituzione di un elenco da applyè un'operazione pericolosa in quanto non è garantito che l'oggetto risultante sia una serie o un DataFrame. E in alcuni casi potrebbero essere sollevate eccezioni. Vediamo un semplice esempio:

df = pd.DataFrame(data=np.random.randint(0, 5, (5,3)),
                  columns=['a', 'b', 'c'])
df
   a  b  c
0  4  0  0
1  2  0  1
2  2  2  2
3  1  2  2
4  3  0  0

Esistono tre possibili risultati con la restituzione di un elenco da apply

1) Se la lunghezza dell'elenco restituito non è uguale al numero di colonne, viene restituita una serie di elenchi.

df.apply(lambda x: list(range(2)), axis=1)  # returns a Series
0    [0, 1]
1    [0, 1]
2    [0, 1]
3    [0, 1]
4    [0, 1]
dtype: object

2) Quando la lunghezza dell'elenco restituito è uguale al numero di colonne, viene restituito un DataFrame e ogni colonna ottiene il valore corrispondente nell'elenco.

df.apply(lambda x: list(range(3)), axis=1) # returns a DataFrame
   a  b  c
0  0  1  2
1  0  1  2
2  0  1  2
3  0  1  2
4  0  1  2

3) Se la lunghezza dell'elenco restituito è uguale al numero di colonne per la prima riga ma ha almeno una riga in cui l'elenco ha un numero di elementi diverso rispetto al numero di colonne, viene generato un ValueError.

i = 0
def f(x):
    global i
    if i == 0:
        i += 1
        return list(range(3))
    return list(range(4))

df.apply(f, axis=1) 
ValueError: Shape of passed values is (5, 4), indices imply (5, 3)

Rispondere al problema senza applicare

L'uso applycon axis = 1 è molto lento. È possibile ottenere prestazioni molto migliori (soprattutto su set di dati più grandi) con metodi iterativi di base.

Crea frame di dati più grandi

df1 = df.sample(100000, replace=True).reset_index(drop=True)

Tempi

# apply is slow with axis=1
%timeit df1.apply(lambda x: mylist[x['col_1']: x['col_2']+1], axis=1)
2.59 s ± 76.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# zip - similar to @Thomas
%timeit [mylist[v1:v2+1] for v1, v2 in zip(df1.col_1, df1.col_2)]  
29.5 ms ± 534 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

@Thomas risponde

%timeit list(map(get_sublist, df1['col_1'],df1['col_2']))
34 ms ± 459 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

1
È bello vedere risposte così dettagliate da dove è possibile imparare.
Andrea Moro,

7

Sono sicuro che non è veloce come le soluzioni che usano operazioni di Panda o Numpy, ma se non vuoi riscrivere la tua funzione puoi usare map. Utilizzo dei dati di esempio originali -

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

df['col_3'] = list(map(get_sublist,df['col_1'],df['col_2']))
#In Python 2 don't convert above to list

Potremmo passare tutti gli argomenti che desideriamo nella funzione in questo modo. L'output è quello che volevamo

ID  col_1  col_2      col_3
0  1      0      1     [a, b]
1  2      2      4  [c, d, e]
2  3      3      5  [d, e, f]

1
Questo è in realtà molto più veloce quelle risposte che usano applyconaxis=1
Ted Petrou,

2

Il mio esempio per le tue domande:

def get_sublist(row, col1, col2):
    return mylist[row[col1]:row[col2]+1]
df.apply(get_sublist, axis=1, col1='col_1', col2='col_2')

2

Se si dispone di un enorme set di dati, è possibile utilizzare un modo semplice ma più veloce (tempo di esecuzione) per farlo utilizzando Swifter:

import pandas as pd
import swifter

def fnc(m,x,c):
    return m*x+c

df = pd.DataFrame({"m": [1,2,3,4,5,6], "c": [1,1,1,1,1,1], "x":[5,3,6,2,6,1]})
df["y"] = df.swifter.apply(lambda x: fnc(x.m, x.x, x.c), axis=1)

1

Suppongo che tu non voglia cambiare get_sublistfunzione e che tu voglia semplicemente usare il applymetodo DataFrame per fare il lavoro. Per ottenere il risultato desiderato, ho scritto due funzioni di aiuto: get_sublist_liste unlist. Come suggerisce il nome della funzione, innanzitutto ottenere l'elenco di elenchi secondari, quindi estrarre l'elenco secondario da tale elenco. Infine, dobbiamo chiamare la applyfunzione per applicare successivamente queste due funzioni al df[['col_1','col_2']]DataFrame.

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

def get_sublist_list(cols):
    return [get_sublist(cols[0],cols[1])]

def unlist(list_of_lists):
    return list_of_lists[0]

df['col_3'] = df[['col_1','col_2']].apply(get_sublist_list,axis=1).apply(unlist)

df

Se non si utilizza []per racchiudere la get_sublistfunzione, la get_sublist_listfunzione restituirà un semplice elenco, si solleverà ValueError: could not broadcast input array from shape (3) into shape (2), come aveva detto @Ted Petrou.

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.