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

2024-12-06 08:40:00
admin
原创
128
摘要:问题描述:假设我有一个函数和一个数据框,定义如下:def get_sublist(sta, end): return mylist[sta:end+1] df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]}...

问题描述:

假设我有一个函数和一个数据框,定义如下:

def get_sublist(sta, end):
    return mylist[sta:end+1]

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

现在我想get_sublistdf的两列'col_1', 'col_2'应用元素计算一个新列'col_3'以获得如下输出:

  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(get_sublist, axis=1)

但这会导致以下错误:

TypeError: get_sublist() 缺少 1 个必需的位置参数:

我该如何做?


解决方案 1:

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

解决方案 2:

下面是使用数据框的示例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

根据您的使用情况,创建一个 pandasgroup对象然后apply在组上使用有时会有所帮助。

解决方案 3:

一个简单的解决方案是:

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

解决方案 4:

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

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

解决方案 5:

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

解决方案 6:

返回列表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)

不申请就回答问题

使用applyaxis=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)

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

解决方案 7:

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

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

df["C"] = func_1(df["A"].to_numpy(),df["B"].to_numpy())

这比df.apply(f, axis=1)@Aman 快 380 倍,比df['col_3'] = df.apply(lambda x: f(x.col_1, x.col_2), axis=1)@ajrwhite 快 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 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 技巧进行优化。

解决方案 8:

我确信这不如使用 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]

解决方案 9:

我要为 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]

解决方案 10:

您编写 f 的方式需要两个输入。如果您查看错误消息,它会说您没有为 f 提供两个输入,只有一个。错误消息是正确的。

不匹配是因为 df[['col1','col2']] 返回一个包含两列的数据框,而不是两个单独的列。

您需要更改 f 以使其接受单个输入,将上述数据框保留为输入,然后函数体内将其分解为 x、y。然后执行您需要的任何操作并返回单个值。

您需要此函数签名,因为语法是 .apply(f),所以 f 需要采取单个事物 = 数据框而不是两个事物,这是您当前 f 所期望的。

由于您没有提供 f 的主体,我无法提供更多详细信息 - 但这应该提供解决方法,而无需从根本上更改您的代码或使用其他方法,而不是应用

解决方案 11:

另一个选择是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返回Iterables namedtuple,您可以通过列名(又名点符号)和索引将元组元素作为属性进行访问:

b, d = row
b = row.b
d = row[1]

解决方案 12:

我对你的问题举的例子:

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

解决方案 13:

可以通过两种简单的方法完成:假设我们想要在输出列中计算col1和的总和,名为col2`col_sum`

  • 方法 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。

解决方案 14:

我猜你不想改变get_sublist函数,只想使用 DataFrame 的apply方法来完成这项工作。为了得到你想要的结果,我写了两个帮助函数:get_sublist_listunlist。正如函数名称所暗示的那样,首先获取子列表的列表,然后从该列表中提取该子列表。最后,我们需要调用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 提到的那样。

解决方案 15:

如果您拥有庞大的数据集,那么您可以使用一种更简单但更快(执行时间)的方式来执行此操作,即使用 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)

解决方案 16:

矢量化解决方案

在大多数情况下,可以使用 numpy 和 pandas 内置的矢量化方法执行给定的数据操作任务,这些方法通常比显式循环或apply函数更快。例如,在 OP 中,get_sublist()我们可以定义一个布尔数组而不是函数,其中对于每一行,必须选择的msk值都标记为 True。mylist

然后我们可以使用字符串操作方法来创建所需的列表。

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

arr = np.arange(len(mylist))
msk = (df[['col_1']].values <= arr) & (df[['col_2']].values >= arr)

print(msk)
[[ True  True False False False False]    # <--- 'a', 'b' must be selected
 [False False  True  True  True False]    # <--- 'c', 'd', 'e' must be selected
 [False False False  True  True  True]]


df['new'] = (msk * pd.Index(mylist, dtype=object)).str.join(',').str.strip(',').str.split(',')

print(df)
  ID  col_1  col_2        new
0  1      0      1     [a, b]
1  2      2      4  [c, d, e]
2  3      3      5  [d, e, f]

列表推导

OP 中的问题是一个罕见的例子,其中使用矢量化方法不是最快的方法(因为最终结果是锯齿状数组)。话虽如此,不要逐行应用函数(例如)df.apply(get_sublist, axis=1),而是使用显式循环。原因是这.apply(..., axis=1)只是Python for 循环的语法糖,并且由于 pandas 开销,它永远不会比 Python 循环更快。可以使用列表推导在每一行上调用函数:

df['new'] = [get_sublist(s, e) for s,e in zip(df['col_1'], df['col_2'])]

或者starmap()来自标准itertools库:

from itertools import starmap
df['new'] = list(starmap(get_sublist, df[['col_1','col_2']].values.tolist()))
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1579  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1355  
  信创产品在政府采购中的占比分析随着信息技术的飞速发展以及国家对信息安全重视程度的不断提高,信创产业应运而生并迅速崛起。信创,即信息技术应用创新,旨在实现信息技术领域的自主可控,减少对国外技术的依赖,保障国家信息安全。政府采购作为推动信创产业发展的重要力量,其对信创产品的采购占比情况备受关注。这不仅关系到信创产业的发展前...
信创和国产化的区别   8  
  信创,即信息技术应用创新产业,旨在实现信息技术领域的自主可控,摆脱对国外技术的依赖。近年来,国货国用信创发展势头迅猛,在诸多领域取得了显著成果。这一发展趋势对科技创新产生了深远的推动作用,不仅提升了我国在信息技术领域的自主创新能力,还为经济社会的数字化转型提供了坚实支撑。信创推动核心技术突破信创产业的发展促使企业和科研...
信创工作   9  
  信创技术,即信息技术应用创新产业,旨在实现信息技术领域的自主可控与安全可靠。近年来,信创技术发展迅猛,对中小企业产生了深远的影响,带来了诸多不可忽视的价值。在数字化转型的浪潮中,中小企业面临着激烈的市场竞争和复杂多变的环境,信创技术的出现为它们提供了新的发展机遇和支撑。信创技术对中小企业的影响技术架构变革信创技术促使中...
信创国产化   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用