Een functie toepassen op twee kolommen van Panda’s dataframe

Stel dat ik een dfheb met kolommen van 'ID', 'col_1', 'col_2'. En ik definieer een functie :

f = lambda x, y : my_function_expression.

Nu wil ik de ftoepassen op de twee kolommen van df'col_1', 'col_2'om elementsgewijs een nieuwe kolom 'col_3', ongeveer zoals :

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

Hoe te doen?

**Voeg detailvoorbeeld toe zoals hieronder ***

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

Antwoord 1, autoriteit 100%

Hier is een voorbeeld van het gebruik van applyop het dataframe, dat ik aanroep met axis = 1.

Merk op dat het verschil is dat in plaats van te proberen twee waarden door te geven aan de functie f, de functie moet worden herschreven om een pandas Series-object te accepteren en vervolgens de Series te indexeren om de benodigde waarden te krijgen.

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

Afhankelijk van uw gebruikssituatie is het soms handig om een pandas group-object te maken en vervolgens applyop de groep te gebruiken.


Antwoord 2, autoriteit 87%

Er is een duidelijke, eenregelige manier om dit te doen in Panda’s:

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

Hierdoor kan feen door de gebruiker gedefinieerde functie zijn met meerdere invoerwaarden, en worden (veilige) kolomnamen gebruikt in plaats van (onveilige) numerieke indices om toegang te krijgen tot de kolommen.

Voorbeeld met gegevens (op basis van oorspronkelijke vraag):

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)

Uitvoer van 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]

Als uw kolomnamen spaties bevatten of een naam delen met een bestaand dataframe-kenmerk, kunt u indexeren met vierkante haken:

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

Antwoord 3, autoriteit 30%

Een eenvoudige oplossing is:

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

Antwoord 4, autoriteit 11%

Een interessante vraag! mijn antwoord zoals hieronder:

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

Uitvoer:

 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]

Ik heb de kolomnaam gewijzigd in ID,J1,J2,J3 om ID < J1 < J2 < J3, dus de kolom wordt in de juiste volgorde weergegeven.

Nog een korte versie:

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

Antwoord 5, autoriteit 7%

De methode die u zoekt is Series.combine.
Het lijkt er echter op dat er enige zorg moet worden besteed aan datatypes.
In jouw voorbeeld zou je (zoals ik deed bij het testen van het antwoord) naïef bellen

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

Dit geeft echter de fout:

ValueError: setting an array element with a sequence.

Mijn beste gok is dat het lijkt te verwachten dat het resultaat van hetzelfde type is als de serie die de methode (DF.col_1 hier) oproept. De volgende werken:

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]

Antwoord 6, Autoriteit 4%

Een lijst retourneren van applyis een gevaarlijke werking als het resulterende object is niet gegarandeerd ofwel een serie of een dataframe. En uitzonderingen kunnen in bepaalde gevallen worden verhoogd. Laten we door een eenvoudig voorbeeld lopen:

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

Er zijn drie mogelijke uitkomsten met het retourneren van een lijst van apply

1) Als de lengte van de geretourneerde lijst niet gelijk is aan het aantal kolommen, wordt dan een reeks lijsten geretourneerd.

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) Wanneer de lengte van de geretourneerde lijst gelijk is aan het aantal
Kolommen Dan wordt een dataframe geretourneerd en elke kolom krijgt de
Corresponderende waarde in de lijst.

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)Als de lengte van de geretourneerde lijst gelijk is aan het aantal kolommen voor de eerste rij, maar ten minste één rij heeft waar de lijst een ander aantal elementen heeft dan het aantal kolommen, is er een ValueError verhoogd.

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)

Het probleem beantwoorden zonder te solliciteren

Het gebruik van applymet axis=1 is erg traag. Het is mogelijk om veel betere prestaties te krijgen (vooral op grotere datasets) met eenvoudige iteratieve methoden.

Maak een groter dataframe

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

Timingen

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

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

Antwoord 7, autoriteit 3%

Zoals je hebt geschreven, heeft het twee ingangen nodig. Als je naar de foutmelding kijkt, staat er dat je geen twee ingangen geeft aan f, maar één. De foutmelding is correct.
De mismatch is omdat df[[‘col1′,’col2’]] een enkel dataframe retourneert met twee kolommen, niet twee afzonderlijke kolommen.

Je moet je f veranderen zodat er een enkele invoer voor nodig is, het bovenstaande dataframe als invoer behouden en het dan opsplitsen in x,y binnende functietekst. Doe dan wat je nodig hebt en retourneer een enkele waarde.

Je hebt deze functiehandtekening nodig omdat de syntaxis .apply(f) is
Dus f moet het enige ding = dataframe nemen en niet twee dingen, wat je huidige f verwacht.

Aangezien je de hoofdtekst van f niet hebt verstrekt, kan ik je niet meer in detail helpen – maar dit zou de uitweg moeten bieden zonder je code fundamenteel te veranderen of andere methoden te gebruiken in plaats van toe te passen


Antwoord 8, autoriteit 3%

Ik ga stemmen voor np.vectorize. Hiermee kun je gewoon meer dan x aantal kolommen fotograferen en niet omgaan met het dataframe in de functie, dus het is geweldig voor functies die je niet beheert of iets doet zoals het verzenden van 2 kolommen en een constante naar een functie (dwz 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]

Antwoord 9, autoriteit 2%

Ik weet zeker dat dit niet zo snel is als de oplossingen met Pandas- of Numpy-bewerkingen, maar als je je functie niet wilt herschrijven, kun je map gebruiken. De originele voorbeeldgegevens gebruiken –

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

We zouden zoveel argumenten kunnen passeren zoals we op deze manier in de functie wilden. De uitvoer is wat we wilden

 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]

Antwoord 10

Mijn voorbeeld naar uw vragen:

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

Antwoord 11

Als u een enorme dataset hebt, kunt u een eenvoudige maar snellere (uitvoeringstijd) manier gebruiken om dit te doen met 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)

Antwoord 12

Ik veronderstel dat u niet wilt wijzigen get_sublist-functie, en wilt u gewoon dataframe gebruiken apply-methode toepassen om de taak te doen. Om het gewenste resultaat te krijgen, heb je twee hulpfuncties geschreven: get_sublist_listen unlist. Naarmate de functienaam suggereert, ontvang eerst de lijst met sublist, tweede uittreksel die sublist uit die lijst. Ten slotte moeten we u bellen met apply-functie om die twee functies toe te passen op de 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

Als u niet gebruikt []om de get_sublist-functie bij te voegen, dan zal de get_sublist_list-functie een duidelijke lijst retourneren, deze ‘ LL verhogen ValueError: could not broadcast input array from shape (3) into shape (2), zoals @TED PETRU had genoemd.

Other episodes