假设我有一个包含 'ID', 'col_1', 'col_2'
列的 df
。我定义了一个函数:
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']
在 Pandas 中有一种简洁的单行方式可以做到这一点:
df['col_3'] = df.apply(lambda x: f(x.col_1, x.col_2), axis=1)
这允许 f
成为具有多个输入值的用户定义函数,并使用(安全)列名而不是(不安全)数字索引来访问列。
数据示例(基于原始问题):
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)
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]
如果您的列名包含空格或与现有数据框属性共享名称,则可以使用方括号进行索引:
df['col_3'] = df.apply(lambda x: f(x['col 1'], x['col 2']), axis=1)
这是一个在数据帧上使用 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
根据您的用例,有时创建一个 pandas group
对象,然后在组上使用 apply
会很有帮助。
sum
。
df
对象,另一种方法(具有等效结果)是 df.apply(lambda x: x[0] + x[1], axis = 1)
。
一个简单的解决方案是:
df['col_3'] = df[['col_1','col_2']].apply(lambda x: f(*x), axis=1)
df.apply(lambda x: f(x.col_1, x.col_2), axis=1)
相比)
一个有趣的问题!我的回答如下:
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
您正在寻找的方法是 Series.combine。但是,似乎必须注意数据类型。在您的示例中,您会(就像我在测试答案时所做的那样)天真地打电话
df['col_3'] = df.col_1.combine(df.col_2, func=get_sublist)
但是,这会引发错误:
ValueError: setting an array element with a sequence.
我最好的猜测是,它似乎期望结果与调用该方法的系列的类型相同(此处为 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]
从 apply
返回列表是一项危险的操作,因为不能保证生成的对象是 Series 或 DataFrame。在某些情况下可能会引发异常。让我们来看一个简单的例子:
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
从 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
a b c
0 0 1 2
1 0 1 2
2 0 1 2
3 0 1 2
4 0 1 2
3) 如果返回列表的长度等于第一行的列数,但至少有一行列表的元素数与列数不同,则会引发 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 一起使用非常慢。使用基本的迭代方法可以获得更好的性能(尤其是在较大的数据集上)。
创建更大的数据框
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)
f = lambda x: list(range(2))
、df.T.apply(f, axis=0).T
和 df.apply(f, axis=1)
不相同时。
我将为 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]
您编写 f 的方式需要两个输入。如果您查看错误消息,它会说您没有为 f 提供两个输入,只有一个。错误信息是正确的。不匹配是因为 df[['col1','col2']] 返回具有两列的单个数据帧,而不是两个单独的列。
您需要更改 f 以便它接受单个输入,将上述数据框保留为输入,然后将其分解为函数体内的 x,y 。然后做任何你需要的事情并返回一个值。
您需要此函数签名,因为语法是 .apply(f) 所以 f 需要采用单个事物 = 数据框,而不是您当前 f 期望的两个事物。
由于您尚未提供 f 的主体,因此我无法提供更多详细信息-但这应该提供出路,而无需从根本上更改您的代码或使用其他一些方法,而不是应用
我确信这不如使用 Pandas 或 Numpy 操作的解决方案快,但如果你不想重写你的函数,你可以使用 map.使用原始示例数据 -
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]
apply
和 axis=1
的答案快得多
这是一个更快的解决方案:
def func_1(a,b):
return a + b
df["C"] = func_1(df["A"].to_numpy(),df["B"].to_numpy())
这比 @Aman 的 df.apply(f, axis=1)
快 380 倍,比 @ajrwhite 的 df['col_3'] = df.apply(lambda x: f(x.col_1, x.col_2), axis=1)
快 310 倍。
我也添加了一些基准:
结果:
FUNCTIONS TIMINGS GAIN
apply lambda 0.7 x 1
apply 0.56 x 1.25
map 0.3 x 2.3
np.vectorize 0.01 x 70
f3 on Series 0.0026 x 270
f3 on np arrays 0.0018 x 380
f3 numba 0.0018 x 380
简而言之:
使用 apply 很慢。我们可以非常简单地加快速度,只需使用一个可以直接在 Pandas Series 上运行的函数(或者在 numpy 数组上更好)。因为我们将在 Pandas Series 或 numpy 数组上进行操作,所以我们将能够对操作进行矢量化。该函数将返回一个 Pandas 系列或 numpy 数组,我们将其分配为新列。
这是基准代码:
import timeit
timeit_setup = """
import pandas as pd
import numpy as np
import numba
np.random.seed(0)
# Create a DataFrame of 10000 rows with 2 columns "A" and "B"
# containing integers between 0 and 100
df = pd.DataFrame(np.random.randint(0,10,size=(10000, 2)), columns=["A", "B"])
def f1(a,b):
# Here a and b are the values of column A and B for a specific row: integers
return a + b
def f2(x):
# Here, x is pandas Series, and corresponds to a specific row of the DataFrame
# 0 and 1 are the indexes of columns A and B
return x[0] + x[1]
def f3(a,b):
# Same as f1 but we will pass parameters that will allow vectorization
# Here, A and B will be Pandas Series or numpy arrays
# with df["C"] = f3(df["A"],df["B"]): Pandas Series
# with df["C"] = f3(df["A"].to_numpy(),df["B"].to_numpy()): numpy arrays
return a + b
@numba.njit('int64[:](int64[:], int64[:])')
def f3_numba_vectorize(a,b):
# Here a and b are 2 numpy arrays with dtype int64
# This function must return a numpy array whith dtype int64
return a + b
"""
test_functions = [
'df["C"] = df.apply(lambda row: f1(row["A"], row["B"]), axis=1)',
'df["C"] = df.apply(f2, axis=1)',
'df["C"] = list(map(f3,df["A"],df["B"]))',
'df["C"] = np.vectorize(f3) (df["A"].to_numpy(),df["B"].to_numpy())',
'df["C"] = f3(df["A"],df["B"])',
'df["C"] = f3(df["A"].to_numpy(),df["B"].to_numpy())',
'df["C"] = f3_numba_vectorize(df["A"].to_numpy(),df["B"].to_numpy())'
]
for test_function in test_functions:
print(min(timeit.repeat(setup=timeit_setup, stmt=test_function, repeat=7, number=10)))
输出:
0.7
0.56
0.3
0.01
0.0026
0.0018
0.0018
最后一点:也可以使用 Cython 和其他 numba 技巧来优化事情。
另一个选项是 df.itertuples()
(通常比 docs 和 user testing 更快并推荐超过 df.iterrows()
):
import pandas as pd
df = pd.DataFrame([range(4) for _ in range(4)], columns=list("abcd"))
df
a b c d
0 0 1 2 3
1 0 1 2 3
2 0 1 2 3
3 0 1 2 3
df["e"] = [sum(row) for row in df[["b", "d"]].itertuples(index=False)]
df
a b c d e
0 0 1 2 3 4
1 0 1 2 3 4
2 0 1 2 3 4
3 0 1 2 3 4
由于 itertuples
返回 namedtuple
的 Iterable
,因此您可以通过列名(也称为点表示法)和索引作为属性访问元组元素:
b, d = row
b = row.b
d = row[1]
itertuples
有时比 df.apply(..., axis=1)
快得多。对于大型表,我看到时间从大约 3 分钟(使用 apply
)减少到 10 秒(使用 itertuples
。我个人也认为 itertuples
有时更具可读性;它读起来像伪代码。请注意,元组可以通过名称或位置访问(即,在上面的答案中,index=False
,row.b
等同于 row[0]
)。
我对您的问题的示例:
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')
我想您不想更改 get_sublist
函数,只想使用 DataFrame 的 apply
方法来完成这项工作。为了获得您想要的结果,我编写了两个帮助函数:get_sublist_list
和 unlist
。正如函数名所暗示的那样,首先获取子列表的列表,然后从该列表中提取该子列表。最后,我们需要调用 apply
函数将这两个函数应用到 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
如果您不使用 []
将 get_sublist
函数括起来,那么 get_sublist_list
函数将返回一个普通列表,它将引发 ValueError: could not broadcast input array from shape (3) into shape (2)
,正如@Ted Petrou 所提到的。
如果您有一个庞大的数据集,那么您可以使用更简单但更快(执行时间)的方式来使用 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)
它可以通过两种简单的方式完成:假设我们想要在名为 col_sum
的输出列中的 col1
和 col2
的总和
方法一
f = lambda x : x.col1 + x.col2
df['col_sum'] = df.apply(f, axis=1)
方法二
def f(x):
x['col_sum'] = x.col_1 + col_2
return x
df = df.apply(f, axis=1)
当某些复杂功能必须应用于数据帧时,应使用方法 2。当需要在多列中输出时,也可以使用方法 2。
axis=1
并且您的列称为name
,它实际上不会返回您的列数据,而是index
。类似于在groupby()
中获取name
。我通过重命名我的专栏解决了这个问题。f(x['col 1'], x['col 2'])
样式索引(例如,如果您的列名有空格或受保护的名称)。