前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如果 .apply() 太慢怎么办?

如果 .apply() 太慢怎么办?

作者头像
磐创AI
发布2024-03-22 12:36:54
790
发布2024-03-22 12:36:54
举报

如果你在Python中处理数据,Pandas必然是你最常使用的库之一,因为它具有方便和强大的数据处理功能。

如果我们想要将相同的函数应用于Pandas数据帧中整个列的值,我们可以简单地使用 .apply()。Pandas数据帧和Pandas系列(数据帧中的一列)都可以与 .apply() 一起使用。

但是,你是否注意到当我们有一个超大数据集时,.apply() 可能会非常慢?

在本文中,我们将讨论一些加速数据操作的技巧,当你想要将某个函数应用于列时。

将函数应用于单个列

例如,这是我们的示例数据集。

代码语言:javascript
复制
import pandas as pd
import numpy as np
import timeit

d = {'category': ['apple', 'pear', 'peach'], 'radius': [3, 4, 2], 'sweetness': [1, 2, 3]}
df = pd.DataFrame(data=d)
df

如果我们想要在数据帧中添加一个名为'diameter'的列,基于半径列中的值,基本上是直径 = 半径 * 2,我们可以使用 .apply()。

代码语言:javascript
复制
df['diameter'] = df['radius'].apply(lambda x: x*2)
df

我们来计算一下执行时间,

代码语言:javascript
复制
# Timing
setup_code = """
import pandas as pd
d = {'category': ['apple', 'pear', 'peach'], 'radius': [3, 4, 2], 'sweetness': [1, 2, 3]}
df = pd.DataFrame(data=d)
"""

mycode = '''
df['radius'].apply(lambda x: max(x,3))
'''

# timeit statement
t1 = timeit.timeit(setup=setup_code,
                     stmt = mycode,
                     number = 10000)
print(f"10000 runs of mycode is {t1}")

这给了我们 0.56 秒。但如果数据有数百万行,需要多长时间?我这里没有展示,但是需要几十分钟。这么简单的操纵是不可接受的,对吧?

我们应该如何加快速度呢?

这是使用 NumPy 而不是 .apply() 函数的技巧。

代码语言:javascript
复制
df['radius_or_3'] = np.maximum(df['radius'],3)

与 .apply() 相比,这里的 NumPy 函数 max 是一种更好的矢量化函数。我们来计算一下时间。

代码语言:javascript
复制
# Timing
setup_code = """
import pandas as pd
import numpy as np
d = {'category': ['apple', 'pear', 'peach'], 'radius': [3, 4, 2], 'sweetness': [1, 2, 3]}
df = pd.DataFrame(data=d)
"""

mycode = '''
np.maximum(df['radius'],3)
'''

# timeit statement
t1 = timeit.timeit(setup=setup_code,
                     stmt = mycode,
                     number = 10000)
print(f"10000 runs of mycode is {t1}")

0.31 秒,比 .apply() 函数快 2 倍,对吗?

因此,要点是,在简单地使用 .apply() 函数处理所有内容之前,首先尝试为您的任务找到相应的 NumPy 函数。

将函数应用于多列

有时我们需要使用数据中的多列作为函数的输入。例如,我们想要创建一列列表来记录“radius_or_3”和“diameter”之间可能的大小。

我们可以对整个数据框使用 .apply(),

代码语言:javascript
复制
df['sizes'] = df.apply(lambda x: list(range(x.radius_or_3,x.diameter)), axis=1)
df

| | category | radius | sweetness | diameter | radius_or_3 | sizes | | :------- | :----- | :-------- | :------- | :---------- | :---- | ---- | ------------ | | 0 | apple | 3 | 1 | 6 | 3 | [3, 4, 5] | | 1 | pear | 4 | 2 | 8 | 4 | [4, 5, 6, 7] | | 2 | peach | 2 | 3 | 4 | 3 | [3] |

这一步实际上非常耗时,因为我们实际上在 .apply() 函数中传递了很多不必要的东西。执行时间是,

代码语言:javascript
复制
# Timing
setup_code = """
import pandas as pd
import numpy as np
d = {'category': ['apple', 'pear', 'peach'], 'radius': [3, 4, 2], 'sweetness': [1, 2, 3]}
df = pd.DataFrame(data=d)
df['diameter'] = df['radius']*2
df['radius_or_3'] = np.maximum(df['radius'],3)
"""

mycode = '''
df.apply(lambda x: list(range(x.radius_or_3,x.diameter)), axis=1)
'''

# timeit statement
t1 = timeit.timeit(setup=setup_code,
                     stmt = mycode,
                     number = 10000)
print(f"10000 runs of mycode is {t1}")

这给了我们 1.84 秒。我告诉你,对于一个数百万行的数据框,需要 20 多分钟。

我们是否能够找到更高效的方法来执行这项任务呢?

答案是肯定的。唯一需要做的是创建一个接受所需的数量的NumPy数组(Pandas系列)作为输入的函数。

代码语言:javascript
复制
def create_range(a,b):
    range_l = np.empty((len(a),1),object)
    for i,val in enumerate(a):
        range_l[i,0] = list(range(val,b[i]))
    return range_l

df['sizes'] = create_range(df['radius_or_3'].values,df['diameter'].values)
df

这一部分的代码包含一个名为create_range的函数,它接受两个NumPy数组,并通过简单的for循环返回一个NumPy数组。返回的NumPy数组可以自动转换为Pandas Series。

让我们看看我们节省了多少时间。

代码语言:javascript
复制
# Timing
setup_code = """
import pandas as pd
import numpy as np
d = {'category': ['apple', 'pear', 'peach'], 'radius': [3, 4, 2], 'sweetness': [1, 2, 3]}
df = pd.DataFrame(data=d)
df['diameter'] = df['radius']*2
df['radius_or_3'] = np.maximum(df['radius'],3)
"""

mycode = '''
def create_range(a,b):
    range_l = np.empty((len(a),1),object)
    for i,val in enumerate(a):
        range_l[i,0] = list(range(val,b[i]))
    return range_l

create_range(df['radius_or_3'].values,df['diameter'].values)
'''

# timeit statement
t1 = timeit.timeit(setup=setup_code,
                     stmt = mycode,
                     number = 10000)
print(f"10000 runs of mycode is {t1}")

这将得到0.07秒的时间!!!

看到了吗?这比对整个数据帧使用的 .apply() 函数快26倍!!

总结
  • 如果你尝试对Pandas数据帧中的单个列使用 .apply(),请尝试找到更简单的执行方式,例如 df['radius']*2。或者尝试找到适用于任务的现有NumPy函数。
  • 如果你想要对Pandas数据帧中的多个列使用 .apply(),请尽量避免使用 .apply(,axis=1) 格式。编写一个独立的函数,可以将NumPy数组作为输入,并直接在Pandas Series(数据帧的列)的 .values 上使用它。 为了方便起见,这是本文中的全部Jupyter笔记本代码。:https://github.com/jiananlin/what_if_apply_too_slow/blob/main/apply_too_slow.ipynb
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-03-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 磐创AI 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 将函数应用于单个列
  • 将函数应用于多列
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档