我什么时候应该(不)想在我的代码中使用 pandas apply() ?

2024-01-24

我在 Stack Overflow 上看到很多涉及 Pandas 方法使用的问题的答案apply。我还看到有网友在下面评论说“apply速度慢,应该避免”。

我读过很多关于性能主题的文章,这些文章解释了apply是慢的。我还在文档中看到了关于如何进行的免责声明apply只是一个传递 UDF 的便利函数(现在似乎找不到)。所以,普遍的共识是apply如果可能的话应该避免。然而,这引发了以下问题:

  1. If apply这么糟糕,那为什么会出现在 API 中呢?
  2. 我应该如何以及何时编写代码apply-free?
  3. 是否有任何情况apply is good(比其他可能的解决方案更好)?

apply,您从未需要的便利功能

我们首先一一解决OP中的问题。

"If apply这么糟糕,那为什么它会出现在 API 中呢?”

DataFrame.apply https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.apply.html#pandas.DataFrame.apply and Series.apply https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.apply.html are 便利功能分别定义在 DataFrame 和 Series 对象上。apply接受在 DataFrame 上应用转换/聚合的任何用户定义的函数。apply实际上是一个灵丹妙药,可以完成任何现有 pandas 函数无法完成的任务。

有些事apply can do:

  • 在 DataFrame 或 Series 上运行任何用户定义的函数
  • 逐行应用函数 (axis=1) 或逐列 (axis=0) 在 DataFrame 上
  • 应用函数时执行索引对齐
  • 使用用户定义的函数执行聚合(但是,我们通常更喜欢agg or transform在这些情况下)
  • 执行逐元素转换
  • 将聚合结果广播到原始行(请参阅result_type争论)。
  • 接受位置/关键字参数传递给用户定义的函数。

...除其他外。有关更多信息,请参阅行或列函数应用 https://pandas.pydata.org/pandas-docs/stable/user_guide/basics.html#row-or-column-wise-function-application在文档中。

那么,有了所有这些功能,为什么apply坏的?这是because apply is slow。 Pandas 不会对你的函数的性质做出任何假设,所以迭代地应用你的函数根据需要到每一行/列。此外,处理all上述情况意味着apply每次迭代都会产生一些主要开销。更远,apply消耗更多的内存,这对于内存有限的应用程序来说是一个挑战。

极少数情况下apply适合使用(更多内容见下文)。如果您不确定是否应该使用apply,你可能不应该。



我们来解决下一个问题。

“我应该如何以及何时编写代码apply-free?"

换言之,以下是一些您需要的常见情况get rid任何呼叫apply.

数值数据

如果您正在处理数字数据,则可能已经有一个矢量化 cython 函数可以完全满足您的要求(如果没有,请在 Stack Overflow 上提问或在 GitHub 上提出功能请求)。

性能对比apply进行简单的加法运算。

df = pd.DataFrame({"A": [9, 4, 2, 1], "B": [12, 7, 5, 4]})
df

   A   B
0  9  12
1  4   7
2  2   5
3  1   4
df.apply(np.sum)

A    16
B    28
dtype: int64

df.sum()

A    16
B    28
dtype: int64

性能方面,没有可比性,cythonized 的等效版本要快得多。不需要图表,因为即使对于玩具数据,差异也很明显。

%timeit df.apply(np.sum)
%timeit df.sum()
2.22 ms ± 41.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
471 µs ± 8.16 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

即使您启用了传递原始数组raw争论,它仍然慢两倍。

%timeit df.apply(np.sum, raw=True)
840 µs ± 691 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

另一个例子:

df.apply(lambda x: x.max() - x.min())

A    8
B    8
dtype: int64

df.max() - df.min()

A    8
B    8
dtype: int64

%timeit df.apply(lambda x: x.max() - x.min())
%timeit df.max() - df.min()

2.43 ms ± 450 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
1.23 ms ± 14.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

一般来说,如果可能的话,寻找矢量化的替代方案。


字符串/正则表达式

Pandas 在大多数情况下提供“向量化”字符串函数,但在极少数情况下,这些函数不......“适用”,可以这么说。

一个常见的问题是检查某一列中的值是否存在于同一行的另一列中。

df = pd.DataFrame({
    'Name': ['mickey', 'donald', 'minnie'],
    'Title': ['wonderland', "welcome to donald's castle", 'Minnie mouse clubhouse'],
    'Value': [20, 10, 86]})
df

     Name  Value                       Title
0  mickey     20                  wonderland
1  donald     10  welcome to donald's castle
2  minnie     86      Minnie mouse clubhouse

这应该返回第二行和第三行,因为“donald”和“minnie”出现在各自的“Title”列中。

使用 apply,可以使用以下方法来完成

df.apply(lambda x: x['Name'].lower() in x['Title'].lower(), axis=1)

0    False
1     True
2     True
dtype: bool
 
df[df.apply(lambda x: x['Name'].lower() in x['Title'].lower(), axis=1)]

     Name                       Title  Value
1  donald  welcome to donald's castle     10
2  minnie      Minnie mouse clubhouse     86

然而,使用列表推导式存在更好的解决方案。

df[[y.lower() in x.lower() for x, y in zip(df['Title'], df['Name'])]]

     Name                       Title  Value
1  donald  welcome to donald's castle     10
2  minnie      Minnie mouse clubhouse     86
%timeit df[df.apply(lambda x: x['Name'].lower() in x['Title'].lower(), axis=1)]
%timeit df[[y.lower() in x.lower() for x, y in zip(df['Title'], df['Name'])]]

2.85 ms ± 38.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
788 µs ± 16.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

这里要注意的是,迭代例程恰好比apply,因为开销较低。如果您需要处理 NaN 和无效的数据类型,您可以使用自定义函数构建此函数,然后使用列表理解中的参数进行调用。

有关何时应将列表推导式视为一个好的选择的更多信息,请参阅我的文章:pandas 中的 for 循环真的很糟糕吗?我什么时候应该关心? https://stackoverflow.com/questions/54028199/for-loops-with-pandas-when-should-i-care.

Note
日期和日期时间操作也有矢量化版本。因此,例如,您应该更喜欢pd.to_datetime(df['date']), 超过, 说,df['date'].apply(pd.to_datetime).

阅读更多内容docs https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html.


一个常见的陷阱:爆炸列表列

s = pd.Series([[1, 2]] * 3)
s

0    [1, 2]
1    [1, 2]
2    [1, 2]
dtype: object

人们很想使用apply(pd.Series)。这是horrible在性能方面。

s.apply(pd.Series)

   0  1
0  1  2
1  1  2
2  1  2

更好的选择是列出列并将其传递给 pd.DataFrame。

pd.DataFrame(s.tolist())

   0  1
0  1  2
1  1  2
2  1  2
%timeit s.apply(pd.Series)
%timeit pd.DataFrame(s.tolist())

2.65 ms ± 294 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
816 µs ± 40.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)


Lastly,

“是否有任何情况apply很好?”

Apply 是一个方便的函数,所以有are开销可以忽略不计到可以原谅的情况。这实际上取决于该函数被调用的次数。

针对系列而非 DataFrame 进行矢量化的函数
如果要对多列应用字符串操作怎么办?如果您想将多列转换为日期时间怎么办?这些函数仅针对系列进行矢量化,因此它们必须是applied在您想要转换/操作的每一列上。

df = pd.DataFrame(
         pd.date_range('2018-12-31','2019-01-31', freq='2D').date.astype(str).reshape(-1, 2), 
         columns=['date1', 'date2'])
df

       date1      date2
0 2018-12-31 2019-01-02
1 2019-01-04 2019-01-06
2 2019-01-08 2019-01-10
3 2019-01-12 2019-01-14
4 2019-01-16 2019-01-18
5 2019-01-20 2019-01-22
6 2019-01-24 2019-01-26
7 2019-01-28 2019-01-30

df.dtypes

date1    object
date2    object
dtype: object
    

这是一个可以受理的案件apply:

df.apply(pd.to_datetime, errors='coerce').dtypes

date1    datetime64[ns]
date2    datetime64[ns]
dtype: object

请注意,这也是有意义的stack,或者只是使用显式循环。所有这些选项都比使用稍快apply,但差异小到可以原谅。

%timeit df.apply(pd.to_datetime, errors='coerce')
%timeit pd.to_datetime(df.stack(), errors='coerce').unstack()
%timeit pd.concat([pd.to_datetime(df[c], errors='coerce') for c in df], axis=1)
%timeit for c in df.columns: df[c] = pd.to_datetime(df[c], errors='coerce')

5.49 ms ± 247 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
3.94 ms ± 48.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
3.16 ms ± 216 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.41 ms ± 1.71 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

您可以对其他操作(例如字符串操作或转换为类别)进行类似的处理。

u = df.apply(lambda x: x.str.contains(...))
v = df.apply(lambda x: x.astype(category))

v/s

u = pd.concat([df[c].str.contains(...) for c in df], axis=1)
v = df.copy()
for c in df:
    v[c] = df[c].astype(category)

等等...


将系列转换为str: astype versus apply

这似乎是 API 的一个特性。使用apply将 Series 中的整数转换为字符串与使用类似(有时更快)astype.

enter image description here The graph was plotted using the perfplot https://github.com/nschloe/perfplot library.

import perfplot

perfplot.show(
    setup=lambda n: pd.Series(np.random.randint(0, n, n)),
    kernels=[
        lambda s: s.astype(str),
        lambda s: s.apply(str)
    ],
    labels=['astype', 'apply'],
    n_range=[2**k for k in range(1, 20)],
    xlabel='N',
    logx=True,
    logy=True,
    equality_check=lambda x, y: (x == y).all())

有了花车,我看到了astype始终与或略快于apply。所以这与测试中的数据是整数类型有关。


GroupBy具有链式转换的操作

GroupBy.apply到目前为止还没有讨论过,但是GroupBy.apply也是一个迭代便利函数,用于处理现有的任何内容GroupBy函数没有。

一个常见的要求是执行 GroupBy,然后执行两个素数运算,例如“滞后累加”:

df = pd.DataFrame({"A": list('aabcccddee'), "B": [12, 7, 5, 4, 5, 4, 3, 2, 1, 10]})
df

   A   B
0  a  12
1  a   7
2  b   5
3  c   4
4  c   5
5  c   4
6  d   3
7  d   2
8  e   1
9  e  10

您需要在这里连续进行两次 groupby 调用:

df.groupby('A').B.cumsum().groupby(df.A).shift()
 
0     NaN
1    12.0
2     NaN
3     NaN
4     4.0
5     9.0
6     NaN
7     3.0
8     NaN
9     1.0
Name: B, dtype: float64

Using apply,您可以将其缩短为一次调用。

df.groupby('A').B.apply(lambda x: x.cumsum().shift())

0     NaN
1    12.0
2     NaN
3     NaN
4     4.0
5     9.0
6     NaN
7     3.0
8     NaN
9     1.0
Name: B, dtype: float64

很难量化性能,因为它取决于数据。但总的来说,apply如果目标是减少groupby打电话(因为groupby也相当昂贵)。



其他注意事项

除了上述注意事项外,还值得一提的是apply对第一行(或列)进行两次操作。这样做是为了确定该函数是否有任何副作用。如果不,apply也许能够使用快速路径来评估结果,否则它会退回到缓慢的实现。

df = pd.DataFrame({
    'A': [1, 2],
    'B': ['x', 'y']
})

def func(x):
    print(x['A'])
    return x

df.apply(func, axis=1)

# 1
# 1
# 2
   A  B
0  1  x
1  2  y

这种行为也见于GroupBy.apply在 pandas 版本 浏览此处获取更多信息.)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

我什么时候应该(不)想在我的代码中使用 pandas apply() ? 的相关文章

随机推荐

  • 如何使用 MongoDB compass 删除 MongoDB 集合中选定的多条记录

    我对 MongoDB 和 MongoDB Compass 非常陌生 我的客户集合中有大约 1000 条记录 如何通过 MongoDB compass 一次删除所有记录 非常感谢 您可以使用 MongoDB compass 提供的 Embed
  • C - /proc/pid/exe 上的 Lstat

    我正在尝试使用 lstat 获取 proc pid exe 文件的大小 以字节为单位 这是我的代码 int main int argc char argv struct stat sb char linkname ssize t r if
  • StackExchange.Redis如何订阅多个频道

    我如何订阅多个频道 据我了解 需要传递给 Subscribe 方法的 Channel 类支持模式或单通道订阅 是否可以通过一个命令订阅多个频道 例子 客户端在 3 个不同的频道上发布内容 ChannelA ChannelB 和 Channe
  • iOS 分发:将私钥/证书迁移到新机器

    我需要能够从不同于我用来提交原始文件的机器向我的应用程序提交更新 我按照文档中的说明进行操作 但是与往常一样 文档假设第一次一切正常 我尝试将我的开发人员配置文件从旧的导出到新的 但是当我提交时 应用程序加载器说它是无效签名 因此 我尝试生
  • 为根包生成 scaladoc

    我很好奇如何记录root包 显示为第一页scala 文档 http docs scala lang org 汇编 可以以某种方式创建一个引用根包的包对象 或者是否有任何配置选项 对此事的描述有些模糊 scaladoc 似乎有一个名为 doc
  • 清理 iPhone 模拟器

    在为 iPhone 模拟器构建时 是否有一种直接的方法来清理 xcode 部署应用程序的目录 我有一个 sqlite 数据库 如有必要 它会在启动时复制到 Documents 文件夹中 问题是我可能会更改我的架构 但新数据库不会被复制 因为
  • Visual Studio 2010 报告服务项目?

    因此 Visual Studio 2010 似乎不支持 SSRS 项目 至少不是旧的 有谁知道这种情况的状态是什么 以及是否可以使用 VS2010 编写新的 SSRS 报告 或者是否应该只使用 VS2008 BIDS SQL Server
  • 如何缩进 Python 列表推导式?

    列表推导式在某些情况下可能很有用 但读起来也可能相当糟糕 作为一个稍微夸张的例子 您将如何缩进以下内容 allUuids x id for x in self db query schema allPostsUuid execute tim
  • 如何在 Quickcheck 中使用修饰符(在我的例子中是积极的)

    我有一个功能 rev 它返回属于三个类型类的类型的一些值 rev Integral a Show a Read a gt a gt a rev read reverse show 我想用快速检查来测试它的一些属性 不过 我对测试 Integ
  • 相当于其他浏览器中 Firefox 的“错误控制台”

    其他浏览器中是否有与 Firefox 的 错误控制台 等效的功能 我发现错误控制台可以方便地查找 JavaScript 错误 但似乎没有一种等效的简单方法可以在其他浏览器上查看错误消息 我对 Internet Explorer Opera
  • 使用 POST COMMIT 挂钩在 SVN 存储库上提交代码时自动触发 Jenkins 作业

    我正在尝试使用 Jenkins docker 和 Ansible 来实现 CI CD 管道 我正在为我的版本控制系统使用 SVN 代码存储库 对于部署和 SVN 代码存储库 我使用 AWS EC2 部署和代码存储库位于单独的虚拟机中 我的要
  • Angular 2 - 导入外部传单打字稿库

    我正在尝试将打字稿传单库导入到我的 Angular 2 应用程序中 这是我的地图组件 我已经使用 tsd install 安装了 leaflet d ts 并且我的应用程序没有抱怨
  • 实体框架:添加迁移失败并无法更新数据库

    我已经在一个项目 VS2012 Express 中的 ASP NET MVC 中使用实体框架 5 0 一段时间了 但现在 我无法再添加迁移 PM gt Add Migration projectName MyProject DAL Test
  • Eclipse 中 HTML JavaScript jQuery 中可用 CSS 类的自动补全

    我正在使用最新的 Eclipse 版本 现在我正在使用 Javascript jQuery HTML 和 CSS 进行编码 如何让我在 CSS 中定义的类自动完成 显示在 CSS 中定义的所有可用类 并在 HTML 中显示 我的 CSS 文
  • 如何从列中删除回车符和换行符?

    我正在尝试从 csv 文件导入的列中删除回车符和换行符 我正在使用代码 SELECT replace replace column CHAR 13 CHAR 10 FROM table 它正确找到所有 CR 和 LF 但不更新数据库 您的查
  • Google Play 服务无法在模拟器上运行

    我正在尝试在模拟器上测试简单的地图应用程序 我还在模拟器上安装了以下 Apkscom google android gms 1 apk and com android vending 1 apk and Google Play Store
  • 应用自定义域后,Azure 上的 Web 应用程序加载缓慢

    我刚刚开始在 Azure 上托管我的网站 最初在 xxxx azurewebsites net 上上传和托管网站时 性能和响应都非常好 添加自定义域 从 Godaddy 购买 后 性能变得最差 页面加载需要 1 分钟 xxxx azurew
  • 此查询如何创建逗号分隔列表 SQL Server?

    我在谷歌的帮助下编写了这个查询 从表中创建一个分隔列表 但我不明白这个查询中的任何内容 谁能解释一下发生了什么事 SELECT E1 deptno allemp Replace SELECT E2 ename AS data FROM em
  • Django - 在子路径中运行时静态文件的问题

    我有 django 应用程序在子路径中运行example com api 大部分是rest API 我使用django rest framework 所有请求都正常工作 但是静态文件到处都有错误的路径 在管理面板和请求页面中 在 djang
  • 我什么时候应该(不)想在我的代码中使用 pandas apply() ?

    我在 Stack Overflow 上看到很多涉及 Pandas 方法使用的问题的答案apply 我还看到有网友在下面评论说 apply速度慢 应该避免 我读过很多关于性能主题的文章 这些文章解释了apply是慢的 我还在文档中看到了关于如