如何将函数应用于两列Pandas数据帧

big*_*bug 289 python dataframe pandas

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

f = lambda x, y : my_function_expression.

现在我想应用fto 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)'
Run Code Online (Sandbox Code Playgroud)

怎么做 ?

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

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']
Run Code Online (Sandbox Code Playgroud)

Ama*_*man 259

这是一个使用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
Run Code Online (Sandbox Code Playgroud)

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

  • @Aman:使用Pandas版本0.14.1(可能更早),use也可以使用lambda表达式.给你定义的`df`对象,另一种方法(具有相同的结果)是`df.apply(lambda x:x [0] + x [1],axis = 1)`. (3认同)
  • 请粘贴您的代码吗?我重写函数: def get_sublist(x): return mylist[x[1]:x[2] + 1] 和 df['col_3'] = df.apply(get_sublist, axis=1) 给出 'ValueError: operands can不得与形状 (2) (3)' 一起广播 (2认同)
  • @CanCeylan,您可以只在函数中使用列名而不是索引,那么您就不必担心顺序更改,也不必按名称获取索引,例如,请参见/sf/ask/911515811/从Python熊猫中的列名称开始索引 (2认同)

ajr*_*ite 87

在熊猫中有一种干净,单行的方式:

df['col_3'] = df.apply(lambda x: f(x.col_1, x.col_2), axis=1)
Run Code Online (Sandbox Code Playgroud)

这允许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)
Run Code Online (Sandbox Code Playgroud)

产量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]
Run Code Online (Sandbox Code Playgroud)

  • 就是这个!我只是没有意识到您可以将具有多个输入参数的用户定义函数插入到 lambda 中。重要的是要注意(我认为)您使用的是 DF.apply() 而不是 Series.apply()。这使您可以使用所需的两列对 df 进行索引,并将整列传递给函数,但由于您使用的是 apply(),因此它以元素方式在整列中应用该函数。杰出的!感谢您的发表! (7认同)
  • @Mez13,如果需要,您还可以使用 `f(x['col 1'], x['col 2'])` 样式索引(例如,如果您的列名称有空格或受保护的名称)。 (4认同)
  • 最后!你救了我的命! (3认同)
  • 请注意,如果使用 `axis=1` 并且你的列被称为 `name`,它实际上不会返回你的列数据而是 `index`。类似于在 `groupby()` 中获取 `name`。我通过重命名我的专栏解决了这个问题。 (2认同)

小智 75

一个简单的解决方案是

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

  • 在我的例子中,这个方法速度快了一倍,有 100k 行(与 `df.apply(lambda x: f(x.col_1, x.col_2), axis=1)` 相比) (9认同)
  • 这个答案与问题中的方法有何不同: df['col_3'] = df[['col_1','col_2']].apply(f) 只是为了确认,问题中的方法不起作用,因为发帖者没有指定这个axis=1,默认的是axis=0? (4认同)
  • 这个答案与@Anman的答案相当,但更圆滑一些。他正在构造一个匿名函数,该函数接受一个可迭代对象,并在将其传递给函数 f 之前将其解包。 (3认同)
  • @sjm 不错!但是如果 x 的参数是 args 和 kwargs 等的混合呢? (2认同)

小智 38

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

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
Run Code Online (Sandbox Code Playgroud)

输出:

  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]
Run Code Online (Sandbox Code Playgroud)

我将列名更改为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
Run Code Online (Sandbox Code Playgroud)


Riv*_*ers 23

这是一个更快的解决方案:

def func_1(a,b):
    return a + b

df["C"] = func_1(df["A"].to_numpy(),df["B"].to_numpy())
Run Code Online (Sandbox Code Playgroud)

这比df.apply(f, axis=1)@Aman 快 380 倍,比 @ajrwhite 快 310 倍df['col_3'] = df.apply(lambda x: f(x.col_1, x.col_2), axis=1)

我也添加了一些基准:

结果:

  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
Run Code Online (Sandbox Code Playgroud)

简而言之:

使用 apply 很慢。我们可以非常简单地加快速度,只需使用一个直接在 Pandas Series 上运行的函数(或者更好地在 numpy 数组上运行)。因为我们将在 Pandas Series 或 numpy 数组上进行操作,所以我们将能够对操作进行向量化。该函数将返回一个 Pandas Series 或 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)))
Run Code Online (Sandbox Code Playgroud)

输出:

0.7
0.56
0.3
0.01
0.0026
0.0018
0.0018
Run Code Online (Sandbox Code Playgroud)

最后一点:也可以使用 Cython 和其他 numba 技巧来优化。


Joe*_*ron 20

您正在寻找的方法是Series.combine.但是,似乎必须注意数据类型.在你的例子中,你会(就像我在测试答案时所做的那样)天真地打电话

df['col_3'] = df.col_1.combine(df.col_2, func=get_sublist)
Run Code Online (Sandbox Code Playgroud)

但是,这会引发错误:

ValueError: setting an array element with a sequence.
Run Code Online (Sandbox Code Playgroud)

我最好的猜测是,它似乎期望结果与调用方法的系列(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]
Run Code Online (Sandbox Code Playgroud)


Nit*_*tin 12

你写的方式需要两个输入.如果您查看错误消息,则说明您没有为f提供两个输入,只有一个.错误消息是正确的.
不匹配是因为df [['col1','col2']]返回一个包含两列的数据帧,而不是两列.

你需要改变你的f,将它需要一个输入,保持上述数据帧作为输入,然后把它分解成X,Y 内部函数体.然后做你需要的任何事情并返回一个值.

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

由于你还没有提供f的主体,我不能再详细介绍了 - 但这应该提供出路,而不是从根本上改变你的代码或使用其他方法而不是申请


Tra*_*ace 12

我打算投票给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]
Run Code Online (Sandbox Code Playgroud)

  • 问题是"如何将函数应用于两列Pandas数据帧"而不是"如何仅使用Pandas方法将函数应用于两列Pandas数据帧",而numpy是Pandas的依赖,因此您必须安装它,所以这似乎是一个奇怪的反对意见. (18认同)

Cas*_*mir 9

另一种选择是(通常更快,并且由文档用户测试df.itertuples()推荐):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
Run Code Online (Sandbox Code Playgroud)

由于itertuples返回一个Iterableof namedtuple,您可以通过列名(又名点符号)和索引作为属性访问元组元素:

b, d = row
b = row.b
d = row[1]
Run Code Online (Sandbox Code Playgroud)

  • 根据我的经验,“itertuples”有时比“df.apply(..., axis=1)”快得多。对于大型表,我发现时间从大约 3 分钟(使用“apply”)缩短到 10 秒(使用“itertuples”)。我个人也认为“itertuples”有时更具可读性;它读起来像伪代码。请注意,元组可以通过名称或位置访问(即,在上面的答案中,其中“index=False”,“row.b”相当于“row[0]”)。 (3认同)

小智 7

我对你的问题的例子:

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')
Run Code Online (Sandbox Code Playgroud)


Ted*_*rou 7

返回列表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
Run Code Online (Sandbox Code Playgroud)

从中返回列表有三种可能的结果 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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

没有申请就回答问题

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

创建更大的数据帧

df1 = df.sample(100000, replace=True).reset_index(drop=True)
Run Code Online (Sandbox Code Playgroud)

计时

# 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)
Run Code Online (Sandbox Code Playgroud)

@Thomas回答

%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)
Run Code Online (Sandbox Code Playgroud)

  • 很高兴从可以学习的地方看到如此详细的答案。 (3认同)

Tho*_*mas 6

我敢肯定这不如使用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
Run Code Online (Sandbox Code Playgroud)

我们可以通过这种方式将任意数量的参数传递给函数。输出就是我们想要的

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]
Run Code Online (Sandbox Code Playgroud)

  • 这已经是4年过去了,但这么快的成语比申请起来还快!来自未来的感谢。 (3认同)
  • 这实际上比使用带有“axis=1”的“apply”的答案要快得多 (2认同)

Rah*_*hul 6

它可以通过两种简单的方式完成:比方说,我们想要名为 的输出列中col1和的总和col2col_sum

  • 方法一
f = lambda x : x.col1 + x.col2
df['col_sum'] = df.apply(f, axis=1)
Run Code Online (Sandbox Code Playgroud)
  • 方法二
def f(x):
    x['col_sum'] = x.col_1 + col_2
    return x
df = df.apply(f, axis=1)
Run Code Online (Sandbox Code Playgroud)

当某些复杂函数必须应用于数据帧时,应使用方法 2。当需要多列输出时也可以使用方法2。