如何将函数应用到两列pandas数据框中

假设我有一个df ,里面有'ID', 'col_1', 'col_2' 。 我定义了一个函数:

f = lambda x, y : my_function_expression

现在我想将f应用到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)' 

怎么做 ?

** 添加如下细节示例 ***

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

下面是一个使用apply的例子,我用axis = 1调用数据框。

注意不同之处在于,不是试图将两个值传递给函数f ,而是重写该函数以接受一个pandas Series对象,然后对该Series进行索引以获取所需的值。

 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 

根据您的使用情况,创build一个pandas group对象有时会很有帮助,然后在组上apply

一个有趣的问题! 我的答案如下:

 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 

输出:

  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] 

我将列名更改为ID,J1,J2,J3以确保ID <J1 <J2 <J3,所以列按正确的顺序显示。

一个更简短的版本:

 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 

简单的解决scheme是:

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

你写的方式需要两个input。 如果你看看错误信息,说你没有提供两个input到f,只有一个。 错误消息是正确的。
不匹配是因为df [[col1','col2']]返回一个有两列的数据框,而不是两个单独的列。

你需要改变你的f,以便它需要一个单一的input,保持上面的数据框架作为input,然后将其分解到函数体内的x,y。 然后做任何你需要的,并返回一个单一的值。

你需要这个函数签名,因为语法是.apply(f)所以f需要采取单一的东西= dataframe,而不是两个东西,这是你目前的f期望。

既然你没有提供f的主体,我不能再详细说明了 – 但是这应该提供出路,而不会从根本上改变你的代码,或者使用其他方法而不是应用

您正在寻找的方法是Series.combine。 但是,似乎需要注意数据types。 在你的例子中,你会(像我在testing答案时那样)天真地打电话

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

但是,这会引发错误:

 ValueError: setting an array element with a sequence. 

我最好的猜测是,似乎期望结果与调用该方法的系列具有相同的types(此处为df.col_1)。 但是,以下工作:

 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] 

我打算为np.vectorize投票。 它允许你仅仅拍摄x列,而不是处理函数中的数据框,所以对于你不能控制的函数或者像发送2列和常量的函数(比如col_1,col_2, '富')。

 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] 

我相信这不是使用Pandas或Numpy操作的解决scheme,但如果你不想重写你的function,你可以使用地图。 使用原始示例数据 –

 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 

我们可以按照我们想要的方式通过尽可能多的参数。 输出是我们想要的

 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] 

我的例子给你的问题:

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

apply返回一个列表是一个危险的操作,因为结果对象不能保证是一个Series或一个DataFrame。 在某些情况下可能会出现例外情况。 我们来看一个简单的例子:

 df = pd.DataFrame(data=np.random.randint(0, 5, (5,3)), columns=['a', 'b', 'c']) df abc 0 4 0 0 1 2 0 1 2 2 2 2 3 1 2 2 4 3 0 0 

apply返回列表有三种可能的结果

1)如果返回列表的长度不等于列数,则返回一系列列表。

 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)当返回的列表的长度等于列的数量时,返回一个DataFrame,每列获得列表中相应的值。

 df.apply(lambda x: list(range(3)), axis=1) # returns a DataFrame abc 0 0 1 2 1 0 1 2 2 0 1 2 3 0 1 2 4 0 1 2 

3)如果返回列表的长度等于第一行的列数,但至less有一行,列表的元素数量不同于列数,则引发ValueError。

 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) 

回答问题不适用

使用apply于axis = 1是非常缓慢的。 使用基本的迭代方法可以获得更好的性能(特别是在较大的数据集上)。

创build更大的数据框

 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) 

@托马斯回答

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