web-dev-qa-db-de.com

So wenden Sie eine Funktion auf zwei Spalten des Pandas-Datenrahmens an

Angenommen, ich habe eine df, die Spalten von 'ID', 'col_1', 'col_2' enthält. Und ich definiere eine Funktion:

f = lambda x, y : my_function_expression.

Nun möchte ich die f auf die beiden Spalten von df'col_1', 'col_2' anwenden, um eine neue Spalte 'col_3' elementweise zu berechnen, etwa wie folgt:

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

Wie macht man ?

** Detailprobe hinzufügen wie unten ***

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']
225
bigbug

Hier ist ein Beispiel mit apply im Dataframe, das ich mit axis = 1 anrufe. 

Beachten Sie den Unterschied: Anstatt zu versuchen, zwei Werte an die Funktion f zu übergeben, schreiben Sie die Funktion neu, um ein Pandas Series-Objekt zu akzeptieren, und indizieren Sie dann die Serie, um die erforderlichen Werte zu erhalten. 

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

Je nach Anwendungsfall ist es manchmal hilfreich, ein Pandas-Objekt group zu erstellen und anschließend apply für die Gruppe zu verwenden. 

222
Aman

Eine einfache Lösung ist:

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

Eine interessante Frage! meine antwort wie folgt:

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

Ausgabe:

  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]

Ich habe den Spaltennamen in ID, J1, J2, J3 geändert, um sicherzustellen, dass ID <J1 <J2 <J3 ist, sodass die Spalte in der richtigen Reihenfolge angezeigt wird.

Noch eine kurze Version:

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

In Pandas gibt es eine saubere, einzeilige Methode:

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

Dadurch kann f eine benutzerdefinierte Funktion mit mehreren Eingabewerten sein und verwendet (sichere) Spaltennamen anstelle von (unsicheren) numerischen Indizes für den Zugriff auf die Spalten.

Beispiel mit Daten (basierend auf der ursprünglichen Frage):

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)

Ausgabe von 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]
18
ajrwhite

Die von Ihnen gesuchte Methode ist Series.combine. Es scheint jedoch, dass Datatypen etwas Vorsicht geboten ist. In Ihrem Beispiel würden Sie (wie ich es beim Testen der Antwort tat) naiv anrufen 

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

Dies wirft jedoch den Fehler ab: 

ValueError: setting an array element with a sequence.

Meine beste Vermutung ist, dass es zu erwarten scheint, dass das Ergebnis vom selben Typ ist wie die Reihe, die die Methode aufruft (df.col_1 hier). Folgendes funktioniert jedoch:

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]
15
JoeCondron

So wie Sie es geschrieben haben, sind zwei Eingaben erforderlich. Wenn Sie sich die Fehlermeldung ansehen, heißt es, Sie geben nicht zwei Eingaben an f, sondern nur eine. Die Fehlermeldung ist korrekt.
Die Übereinstimmung ist, weil df [['' col1 ',' col2 ']] ein einzelnes Datenframe mit zwei Spalten zurückgibt, nicht zwei separaten Spalten.

Sie müssen Ihr f so ändern, dass eine einzelne Eingabe erforderlich ist. Behalten Sie den obigen Datenrahmen als Eingabe bei, und teilen Sie ihn in x, y inside den Funktionskörper auf. Dann machen Sie, was Sie brauchen und geben Sie einen einzelnen Wert zurück.

Sie benötigen diese Funktionssignatur, da die Syntax .apply (f) .__ ist. Daher muss f das einzige Ding = Datenframe nehmen und nicht zwei Dinge, die Ihr aktuelles f erwartet. 

Da Sie nicht den Rumpf von f bereitgestellt haben, kann ich nicht mehr im Detail helfen. Dies sollte jedoch den Ausweg bieten, ohne Ihren Code grundlegend zu ändern oder andere Methoden zu verwenden, anstatt diese anzuwenden

11
Nitin

Ich werde für np.vectorize abstimmen. Es erlaubt Ihnen, nur über x Spaltenanzahl zu schießen und sich nicht mit dem Datenrahmen in der Funktion zu beschäftigen. Daher ist es ideal für Funktionen, die Sie nicht kontrollieren können, oder etwas zu tun, wie das Senden von 2 Spalten und einer Konstante in eine Funktion (dh col_1, col_2, 'foo').

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]
9
Trae Wallace

Das Zurückgeben einer Liste aus apply ist eine gefährliche Operation, da es sich nicht sicherstellt, dass das resultierende Objekt entweder eine Serie oder ein DataFrame ist. In bestimmten Fällen können Ausnahmen gemacht werden. Gehen wir ein einfaches Beispiel durch:

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

Es gibt drei mögliche Ergebnisse mit der Rückgabe einer Liste von apply

1) Wenn die Länge der zurückgegebenen Liste nicht der Anzahl der Spalten entspricht, wird eine Reihe von Listen zurückgegeben.

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) Wenn die Länge der zurückgegebenen Liste gleich der Anzahl von .__ ist. Spalten wird ein DataFrame zurückgegeben und jede Spalte erhält die entsprechenden Wert in der Liste.

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) Wenn die Länge der zurückgegebenen Liste der Anzahl der Spalten für die erste Zeile entspricht, aber mindestens eine Zeile enthält, in der die Liste eine andere Anzahl von Elementen als die Anzahl der Spalten enthält, für die ein ValueError ausgelöst wird.

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)

Beantwortung des Problems ohne zutreffend

Die Verwendung von apply mit Achse = 1 ist sehr langsam. Mit grundlegenden iterativen Methoden ist es möglich, eine wesentlich bessere Leistung (insbesondere bei größeren Datensätzen) zu erzielen.

Größeren Datenrahmen erstellen  

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

Timings

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

%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)
5
Ted Petrou

Ich bin sicher, dass dies nicht so schnell ist wie die Lösungen, die Pandas oder Numpy-Vorgänge verwenden. Verwenden der ursprünglichen Beispieldaten -

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

Wir könnten so viele Argumente in die Funktion einbringen, wie wir wollten. Die Ausgabe ist das, was wir wollten

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
Thomas

Mein Beispiel zu Ihren Fragen:

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')
1
Qing Liu

Ich nehme an, Sie möchten die get_sublist-Funktion nicht ändern und möchten nur die apply-Methode von DataFrame verwenden, um den Job auszuführen. Um das gewünschte Ergebnis zu erhalten, habe ich zwei Hilfefunktionen geschrieben: get_sublist_list und unlist. Rufen Sie, wie der Funktionsname andeutet, zuerst die Liste der Unterlisten ab, und extrahieren Sie dann diese Unterliste aus dieser Liste. Schließlich müssen wir die Funktion apply aufrufen, um diese beiden Funktionen anschließend auf den df[['col_1','col_2']] DataFrame anzuwenden.

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

Wenn Sie [] nicht zum Einschließen der get_sublist-Funktion verwenden, gibt die get_sublist_list-Funktion eine einfache Liste zurück, und es wird ValueError: could not broadcast input array from shape (3) into shape (2) aufgerufen, wie @Ted Petrou erwähnt hatte.

0
allenyllee

Wenn Sie einen großen Datenbestand haben, können Sie dies mit swifter auf einfache, aber schnellere Weise (Ausführungszeit) tun:

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)
0
durjoy