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

368

Angenommen, ich habe eine, dfdie Spalten von hat 'ID', 'col_1', 'col_2'. Und ich definiere eine Funktion:

f = lambda x, y : my_function_expression.

Jetzt möchte ich die beiden Spalten von to anwenden f, um eine neue Spalte elementweise zu berechnen , ähnlich wie:df'col_1', 'col_2''col_3'

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

Wie macht man ?

** **. Fügen Sie ein Detailbeispiel wie unten hinzu ***.

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']
großer Käfer
quelle
4
können Sie f direkt auf Spalten anwenden: df ['col_3'] = f (df ['col_1'], df ['col_2'])
btel
1
wäre nützlich zu wissen, was zu ftun ist
tehmisvh
2
nein, df ['col_3'] = f (df ['col_1'], df ['col_2']) funktioniert nicht. Für f werden nur skalare Eingaben akzeptiert, keine Vektoreingaben. OK, Sie können f = Lambda x, y: x + y annehmen. (Natürlich ist mein echtes f nicht so einfach, sonst kann ich direkt df ['col_3'] = df ['col_1'] + df ['col_2'])
Bigbug
1
Ich habe unter der folgenden URL eine verwandte Frage und Antwort gefunden, aber mein Problem besteht darin, eine neue Spalte anhand von zwei vorhandenen Spalten zu berechnen, nicht anhand von 2 aus 1. stackoverflow.com/questions/12356501/…
Bigbug
Ich denke, meine Antwort stackoverflow.com/a/52854800/5447172 beantwortet dies auf die pythonischste / pandanischste Weise, ohne Problemumgehungen oder numerische Indizierung. Es erzeugt genau die Ausgabe, die Sie in Ihrem Beispiel benötigt haben.
Ajrwhite

Antworten:

291

Hier ist ein Beispiel applyfür den Datenrahmen, mit dem ich anrufe axis = 1.

Beachten Sie, dass der Unterschied darin besteht, anstatt zu versuchen, zwei Werte an die Funktion zu übergeben f, die Funktion neu zu schreiben, um ein Pandas-Serienobjekt zu akzeptieren, und dann die Serie zu indizieren, 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

Abhängig von Ihrem Anwendungsfall ist es manchmal hilfreich, ein Pandas- groupObjekt zu erstellen und es dann applyfür die Gruppe zu verwenden.

Ein Mann
quelle
Ja, ich habe versucht, apply zu verwenden, kann aber den gültigen Syntaxausdruck nicht finden. Und wenn jede Zeile von df einzigartig ist, verwenden Sie trotzdem groupby?
Bigbug
Ich habe meiner Antwort ein Beispiel hinzugefügt und hoffe, dass dies das tut, wonach Sie suchen. Wenn nicht, geben Sie bitte eine spezifischere Beispielfunktion an, da sumdiese mit einer der bisher vorgeschlagenen Methoden erfolgreich gelöst wird.
Aman
1
Würden Sie bitte Ihren Code einfügen? Ich schreibe die Funktion neu: def get_sublist (x): return mylist [x [1]: x [2] + 1] und df ['col_3'] = df.apply (get_sublist, axis = 1) ergibt 'ValueError: Operanden könnten nicht zusammen mit Formen gesendet werden (2) (3) '
Bigbug
3
@Aman: Mit Pandas Version 0.14.1 (und möglicherweise früher) kann die Verwendung auch einen Lambda-Ausdruck verwenden. Geben Sie das von dfIhnen definierte Objekt an. Ein anderer Ansatz (mit äquivalenten Ergebnissen) ist df.apply(lambda x: x[0] + x[1], axis = 1).
Jubelt
2
@CanCeylan Sie können einfach die Spaltennamen in der Funktion anstelle von Indizes verwenden, dann müssen Sie sich keine Gedanken über die Änderung der Reihenfolge machen oder den Index nach Namen abrufen,
Davos
165

In Pandas gibt es eine saubere, einzeilige Methode:

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

Dies erlaubt f eine benutzerdefinierte Funktion mit mehreren Eingabewerten und verwendet (sichere) Spaltennamen anstelle von (unsicheren) numerischen Indizes, um auf die Spalten zuzugreifen.

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]

Wenn Ihre Spaltennamen Leerzeichen enthalten oder einen Namen mit einem vorhandenen Datenrahmenattribut teilen, können Sie mit eckigen Klammern indizieren:

df['col_3'] = df.apply(lambda x: f(x['col 1'], x['col 2']), axis=1)
ajrwhite
quelle
2
Beachten Sie, dass bei Verwendung von axis=1und Ihrer Spalte namenicht Ihre Spaltendaten zurückgegeben werden, sondern die index. Ähnlich wie bei der namein a groupby(). Ich habe dies gelöst, indem ich meine Spalte umbenannt habe.
Tom Hemmes
2
DAS IST ES! Ich wusste nur nicht, dass Sie benutzerdefinierte Funktionen mit mehreren Eingabeparametern in Lambdas einfügen können. Es ist wichtig zu beachten (glaube ich), dass Sie DF.apply () anstelle von Series.apply () verwenden. Auf diese Weise können Sie die df mithilfe der beiden gewünschten Spalten indizieren und die gesamte Spalte an die Funktion übergeben. Da Sie jedoch apply () verwenden, wird die Funktion elementweise in der gesamten Spalte angewendet. Brillant! Vielen Dank für die Veröffentlichung!
Data-Phile
1
ENDLICH! Du hast meinen Tag gerettet!
Mysterio
Ich glaube, der vorgeschlagene Weg, dies zu tun, ist df.loc [:, 'new col'] = df.apply .....
valearner
@valearner Ich glaube, es gibt keinen Grund, .locdas Beispiel zu bevorzugen . Dies kann erforderlich sein, wenn Sie dies an eine andere Problemeinstellung anpassen (z. B. Arbeiten mit Slices).
Ajrwhite
86

Eine einfache Lösung ist:

df['col_3'] = df[['col_1','col_2']].apply(lambda x: f(*x), axis=1)
sjm
quelle
1
Wie unterscheidet sich diese Antwort von dem Ansatz in der Frage: df ['col_3'] = df [['col_1', 'col_2']] Poster hat diese Achse nicht angegeben = 1, die Standardeinstellung ist Achse = 0?
Lost1
1
Diese Antwort ist vergleichbar mit der Antwort von @ Anman, aber etwas schlauer. Er erstellt eine anonyme Funktion, die eine iterierbare Funktion übernimmt und diese entpackt, bevor sie an die Funktion f übergeben wird.
Tiao
39

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

quelle
23

Die Methode, nach der Sie suchen, ist Series.combine. Es scheint jedoch, dass bei Datentypen einige Vorsicht geboten ist. In Ihrem Beispiel würden Sie (wie beim Testen der Antwort) naiv anrufen

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

Dies löst jedoch den Fehler aus:

ValueError: setting an array element with a sequence.

Ich vermute, dass das Ergebnis vom selben Typ zu sein scheint 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]
JoeCondron
quelle
12

Die Art und Weise, wie Sie geschrieben haben, benötigt zwei Eingänge. Wenn Sie sich die Fehlermeldung ansehen, heißt es, dass Sie nicht zwei Eingänge für f bereitstellen, sondern nur einen. Die Fehlermeldung ist korrekt.
Die Nichtübereinstimmung liegt daran, dass df [['col1', 'col2']] einen einzelnen Datenrahmen mit zwei Spalten und nicht zwei separaten Spalten zurückgibt.

Sie müssen Ihr f so ändern, dass es eine einzelne Eingabe benötigt, den obigen Datenrahmen als Eingabe beibehalten und ihn dann innerhalb des Funktionskörpers in x, y aufteilen . Tun Sie dann alles, was Sie brauchen, und geben Sie einen einzelnen Wert zurück.

Sie benötigen diese Funktionssignatur, da die Syntax .apply (f) lautet. F muss also das einzelne Ding = Datenrahmen und nicht zwei Dinge verwenden, was Ihr aktuelles f erwartet.

Da Sie den Textkörper von f nicht angegeben haben, kann ich nicht mehr im Detail helfen - aber dies sollte den Ausweg bieten, ohne Ihren Code grundlegend zu ändern oder andere Methoden zu verwenden, anstatt ihn anzuwenden

Nitin
quelle
12

Ich werde für np.vectorize abstimmen. Es ermöglicht Ihnen, nur über x Spalten zu schießen und sich nicht mit dem Datenrahmen in der Funktion zu befassen. Dies ist ideal für Funktionen, die Sie nicht steuern oder beispielsweise 2 Spalten und eine Konstante in eine Funktion senden (z. B. 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]
Trae Wallace
quelle
1
Dies beantwortet die Frage mit Pandas nicht wirklich.
Mnky9800n
18
Die Frage lautet "So wenden Sie eine Funktion auf zwei Spalten des Pandas-Datenrahmens an" und nicht "So wenden Sie eine Funktion nur mit Pandas-Methoden auf zwei Spalten des Pandas-Datenrahmens an". Numpy ist eine Abhängigkeit von Pandas, sodass Sie sie trotzdem installieren müssen. Das scheint also ein seltsamer Einwand zu sein.
Trae Wallace
12

Das Zurückgeben einer Liste von applyist eine gefährliche Operation, da nicht garantiert wird, dass das resultierende Objekt entweder eine Serie oder ein DataFrame ist. In bestimmten Fällen können Ausnahmen auftreten. Lassen Sie uns ein einfaches Beispiel durchgehen:

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 bei 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 der Spalten ist, wird ein DataFrame zurückgegeben und jede Spalte erhält den 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, jedoch mindestens eine Zeile enthält, in der die Liste eine andere Anzahl von Elementen als die Anzahl der Spalten enthält, wird ein ValueError ausgelöst.

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 Anwendung

Die Verwendung applymit Achse = 1 ist sehr langsam. Mit grundlegenden iterativen Methoden ist es möglich, eine viel bessere Leistung (insbesondere bei größeren Datenmengen) zu erzielen.

Erstellen Sie einen größeren Datenrahmen

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 Antwort

%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)
Ted Petrou
quelle
1
Es ist schön, so detaillierte Antworten zu sehen, von denen man lernen kann.
Andrea Moro
7

Ich bin sicher, dass dies nicht so schnell ist wie die Lösungen, die Pandas- oder Numpy-Operationen verwenden, aber wenn Sie Ihre Funktion nicht neu schreiben möchten, können Sie map verwenden. Verwendung 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

Auf diese Weise konnten wir so viele Argumente wie wir wollten in die Funktion übergeben. 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]
Thomas
quelle
1
Dies ist tatsächlich viel schneller die Antworten, die applymitaxis=1
Ted Petrou
2

Mein Beispiel für Ihre 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')
Qing Liu
quelle
2

Wenn Sie über einen großen Datensatz verfügen, 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)
durjoy
quelle
1

Ich nehme an, Sie möchten die get_sublistFunktion nicht ändern und nur die DataFrame- applyMethode verwenden, um die Aufgabe zu erledigen. Um das gewünschte Ergebnis zu erzielen, habe ich zwei Hilfefunktionen geschrieben: get_sublist_listund unlist. Wie der Funktionsname andeutet, rufen Sie zuerst die Liste der Unterlisten ab und extrahieren Sie dann die Unterliste aus dieser Liste. Schließlich müssen wir die applyFunktion 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 []die get_sublistFunktion nicht einschließen , gibt die get_sublist_listFunktion eine einfache Liste zurück und wird ValueError: could not broadcast input array from shape (3) into shape (2)ausgelöst, wie @Ted Petrou erwähnt hat.

Allenyllee
quelle