如何将函数应用到熊猫数据框架的两列

假设我有一个df,它的列是'ID', 'col_1', 'col_2'。我定义了一个函数:

# EYZ0。

现在,我想应用fdf的两个列'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)'

怎么办?

# # EYZ1 EYZ0 * * * * * < / >强

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']
930420 次浏览

这里有一个在数据帧上使用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

根据您的用例,有时创建一个熊猫group对象,然后在组上使用apply是有帮助的。

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

你需要改变你的f,让它只接受一个输入,保持上面的数据帧作为输入,然后把它分解成x,y 内部这个函数体。然后执行所需的操作并返回一个值。

你需要这个函数签名,因为语法是。apply(f) f需要取一个= dataframe的东西,而不是当前f所期望的两个东西。< / p >

由于你没有提供f的主体,我不能提供更多的细节-但这应该提供了出路,而不需要从根本上改变你的代码或使用一些其他方法而不是应用

您正在寻找的方法是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]

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

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 <j - 1 & lt;J2 & lt;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

我要投票支持np。vectorize。它允许你只拍摄x个列,而不处理函数中的数据帧,所以它非常适合你不控制的函数,或者做一些像发送2列和一个常数到一个函数(即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]

我相信这不会像使用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]

一个简单的解决方案是:

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

我举个例子来回答你的问题:

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
a  b  c
0  4  0  0
1  2  0  1
2  2  2  2
3  1  2  2
4  3  0  0

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

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

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

如果返回列表的长度等于第一行的列数,但至少有一行列表的元素数与列数不同,则会引发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)

胡乱回答问题

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

# EYZ0

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回答

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

我猜你不想改变get_sublist函数,而只是想使用DataFrame的apply方法来完成这项工作。为了得到你想要的结果,我写了两个帮助函数:get_sublist_listunlist。正如函数名所示,首先获取子列表的列表,然后从该列表中提取子列表。最后,我们需要调用apply函数,随后将这两个函数应用到df[['col_1','col_2']]数据帧。

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所提到的。

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

如果你的列名包含空格或与现有的dataframe属性共享一个名称,你可以用方括号索引:

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

如果你有一个巨大的数据集,那么你可以使用一种简单但更快(执行时间)的方式来做到这一点,使用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)

另一个选项是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

由于itertuples返回# eyz3中的Iterable,您可以通过列名(即点表法)和索引来访问元组元素:

b, d = row
b = row.b
d = row[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系列(或者更好地操作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)))

输出:

0.7
0.56
0.3
0.01
0.0026
0.0018
0.0018

最后注意:事情可以优化Cython和其他numba技巧。

有两种简单的方法可以做到: 比方说,我们想要在名为col_sum

的输出列中得到col1col2的和
  • 方法1
f = lambda x : x.col1 + x.col2
df['col_sum'] = df.apply(f, axis=1)
  • 方法2
def f(x):
x['col_sum'] = x.col_1 + col_2
return x
df = df.apply(f, axis=1)

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