Supposons que j’ai une df
qui a des colonnes de 'ID', 'col_1', 'col_2'
. Et je définis une fonction:
f = lambda x, y : my_function_expression
.
Maintenant, je veux appliquer les f
aux df
colonnes 'col_1', 'col_2'
pour calculer par élément une nouvelle colonne 'col_3'
, un peu comme:
df['col_3'] = df[['col_1','col_2']].apply(f)
# Pandas gives : TypeError: ('<lambda>() takes exactly 2 arguments (1 given)'
Comment faire ?
** Ajouter un exemple de détail comme ci-dessous ***
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']
Voici un exemple utilisant apply
sur la structure de données, que j'appelle avec axis = 1
.
Notez que la différence est qu'au lieu d'essayer de transmettre deux valeurs à la fonction f
, réécrivez la fonction pour accepter un objet Series pandas, puis indexez la série pour obtenir les valeurs requises.
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
En fonction de votre cas d'utilisation, il est parfois utile de créer un objet pandas group
, puis d'utiliser apply
sur le groupe.
Une solution simple est:
df['col_3'] = df[['col_1','col_2']].apply(lambda x: f(*x), axis=1)
Une question intéressante! ma réponse comme ci-dessous:
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
Sortie:
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]
J'ai changé le nom de la colonne en ID, J1, J2, J3 pour que l'ID <J1 <J2 <J3, afin que la colonne s'affiche dans le bon ordre.
Une autre version brève:
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
Il existe un moyen simple et unique de procéder de la sorte dans les pandas:
df['col_3'] = df.apply(lambda x: f(x.col_1, x.col_2), axis=1)
Cela permet à f
d'être une fonction définie par l'utilisateur avec plusieurs valeurs d'entrée et utilise des noms de colonne (sécurisés) plutôt que des index numériques (non sécurisés) pour accéder aux colonnes.
Exemple avec des données (basées sur la question initiale):
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)
Sortie de 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]
La méthode que vous recherchez est Series.combine. Cependant, il semble qu’il faille faire attention aux types de données. Dans votre exemple, vous appelez naïvement (comme je l’ai fait lors du test de la réponse)
df['col_3'] = df.col_1.combine(df.col_2, func=get_sublist)
Cependant, cela jette l'erreur:
ValueError: setting an array element with a sequence.
Ma meilleure hypothèse est qu'il semble s'attendre à ce que le résultat soit du même type que la série appelant la méthode (df.col_1 here). Cependant, les travaux suivants:
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]
La façon dont vous avez écrit a besoin de deux entrées. Si vous regardez le message d'erreur, il est indiqué que vous ne fournissez pas deux entrées à f, une seule. Le message d'erreur est correct.
La disparité est due au fait que df [['col1', 'col2']] renvoie une seule trame de données avec deux colonnes et non deux colonnes séparées.
Vous devez modifier votre f pour qu'il ne prenne qu'une seule entrée, conservez le bloc de données ci-dessus comme entrée, puis divisez-le en x, y inside le corps de la fonction. Faites ensuite ce dont vous avez besoin et renvoyez une valeur unique.
Vous avez besoin de cette signature de fonction car la syntaxe est .apply (f) Donc, f doit prendre la seule chose = dataframe et non deux choses, ce à quoi votre f actuel s'attend.
Comme vous n'avez pas fourni le corps de f, je ne peux pas vous aider avec plus de détails - mais cela devrait vous permettre de sortir sans changer fondamentalement votre code ni utiliser d'autres méthodes que d'appliquer
Je vais voter pour np.vectorize. Cela vous permet de créer des x colonnes et de ne pas traiter le cadre de données dans la fonction. Il est donc idéal pour les fonctions que vous ne contrôlez pas ou qui consistent à envoyer 2 colonnes et une constante dans une fonction (par exemple, 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]
Renvoyer une liste de apply
est une opération dangereuse, car l'objet résultant n'est pas nécessairement une série ni un DataFrame. Et des exceptions peuvent être soulevées dans certains cas. Passons en exemple simple:
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
Il y a trois résultats possibles avec le renvoi d'une liste de apply
1) Si la longueur de la liste renvoyée n'est pas égale au nombre de colonnes, une série de listes est alors renvoyée.
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) Lorsque la longueur de la liste renvoyée est égale au nombre de colonnes, un DataFrame est renvoyé et chaque colonne reçoit le valeur correspondante dans la 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) Si la longueur de la liste renvoyée est égale au nombre de colonnes de la première ligne mais comporte au moins une ligne où la liste comporte un nombre d'éléments différent du nombre de colonnes, une valeur ValueError est générée.
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)
Utiliser apply
avec axe = 1 est très lent. Il est possible d'obtenir de bien meilleures performances (en particulier sur des jeux de données plus volumineux) avec des méthodes itératives de base.
Créer une base de données plus grande
df1 = df.sample(100000, replace=True).reset_index(drop=True)
# 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 répond
%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)
Je suis sûr que ce n'est pas aussi rapide que les solutions utilisant les opérations Pandas ou Numpy, mais si vous ne voulez pas réécrire votre fonction, vous pouvez utiliser map. Utilisation des données d'exemple originales -
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
Nous pourrions ainsi transmettre autant d’arguments que nous le souhaitions à la fonction. La sortie est ce que nous voulions
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]
Mon exemple à vos questions:
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')
Je suppose que vous ne voulez pas changer la fonction get_sublist
et que vous voulez simplement utiliser la méthode apply
de DataFrame pour faire le travail. Pour obtenir le résultat souhaité, j'ai écrit deux fonctions d'aide: get_sublist_list
et unlist
. Comme le suggère le nom de la fonction, commencez par obtenir la liste des sous-listes, puis extrayez-les de la liste. Enfin, nous devons appeler la fonction apply
pour appliquer ces deux fonctions au df[['col_1','col_2']]
DataFrame ultérieurement.
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
Si vous n'utilisez pas []
pour inclure la fonction get_sublist
, la fonction get_sublist_list
renverra une liste en clair, elle soulèvera ValueError: could not broadcast input array from shape (3) into shape (2)
, comme l'a mentionné @Ted Petrou.
Si vous avez un énorme ensemble de données, vous pouvez utiliser un moyen simple mais plus rapide (temps d'exécution) d'utiliser 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)