two - python apply function on row




Wie man eine Funktion auf zwei Spalten des Pandas-Datenrahmens anwendet (7)

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

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, wenn Sie eine 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 der Anzahl der Spalten entspricht, 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 gleich der Anzahl der Spalten für die erste Zeile ist, aber mindestens eine Zeile hat, in der die Liste eine andere Anzahl von Elementen hat als die Anzahl der Spalten, 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)

Beantworten Sie das Problem ohne zu übernehmen

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

Erstellen Sie einen größeren Datenrahmen

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

Zeiten

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

Angenommen, ich habe ein df das Spalten mit 'ID', 'col_1', 'col_2' . Und ich definiere eine Funktion:

f = lambda x, y : my_function_expression .

Jetzt möchte ich das f auf 'col_1', 'col_2' zwei Spalten 'col_1', 'col_2' um elementweise eine neue Spalte 'col_3' berechnen, etwa so:

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

Wie macht man ?

** Detailbeispiel wie folgt hinzufügen ***

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

Die Art und Weise, wie Sie geschrieben haben, benötigt zwei Eingaben. Wenn Sie sich die Fehlermeldung ansehen, heißt es, dass Sie f nicht nur zwei Eingänge zur Verfügung stellen. Die Fehlermeldung ist korrekt.
Der Unterschied liegt darin, dass df [['col1', 'col2']] einen einzelnen Datenrahmen mit zwei Spalten, nicht zwei separaten Spalten, zurückgibt.

Sie müssen Ihr f so ändern, dass es eine einzige Eingabe benötigt, behalten Sie den obigen Datenrahmen als Eingabe und zerlegen Sie ihn dann in x, y innerhalb des Funktionskörpers. Dann tu was immer du brauchst und gib einen einzelnen Wert zurück.

Sie benötigen diese Funktionssignatur, da die Syntax .apply (f) lautet. Also muss f das Einzelding = Datenframe und nicht zwei Dinge annehmen, was Ihr aktuelles f erwartet.

Da Sie den Körper von f nicht bereitgestellt haben, kann ich Ihnen nicht mehr weiterhelfen - aber dies sollte den Ausweg bieten, ohne Ihren Code grundlegend zu ändern oder andere Methoden zu verwenden, statt sich zu bewerben


Eine einfache Lösung ist:

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

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 ändere den Spaltennamen in ID, J1, J2, J3, um ID <J1 <J2 <J3 zu gewährleisten, so dass die Spalte in der richtigen Reihenfolge angezeigt wird.

Eine weitere 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

Ich bin mir sicher, dass dies nicht so schnell ist wie bei den Lösungen, die Pandas- oder Numpy-Operationen verwenden, aber wenn Sie Ihre Funktion nicht neu schreiben möchten, können Sie map 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 wie gewünscht in die Funktion einbringen. Die Ausgabe ist, 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]

Ich nehme an, Sie möchten die Funktion get_sublist nicht ändern und möchten nur die Apply-Methode von DataFrame verwenden, um die Aufgabe zu erledigen. Um das gewünschte Ergebnis zu erhalten, habe ich zwei Hilfefunktionen geschrieben: get_sublist_list und unlist . Wie der Name der Funktion vermuten lässt, rufen Sie zuerst die Liste der Unterlisten ab, und extrahieren Sie dann die Unterliste aus dieser Liste. Schließlich müssen wir apply Funktion apply aufrufen, um diese beiden Funktionen anschließend auf den df[['col_1','col_2']] 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 verwenden, um die Funktion get_sublist , gibt die Funktion get_sublist_list eine einfache Liste zurück, so dass ValueError: could not broadcast input array from shape (3) into shape (2) , als @Ted Petrou hatte erwähnt.


Mein Beispiel zu deinen 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')




pandas