当前位置: 首页 > 面试题库 >

熊猫面具/哪里方法与NumPy np.where

许学真
2023-03-14
问题内容

在有条件地更新一系列值时,我经常使用Pandasmaskwhere方法来获得更清晰的逻辑。但是,对于性能相对关键的代码,我注意到相对于而言性能明显下降numpy.where

尽管我很高兴在特定情况下接受此建议,但我很想知道:

  1. 做熊猫mask/where方法提供任何额外的功能, 除了 inplace/ errors/try-cast参数?我了解这3个参数,但很少使用它们。例如,我不知道该level参数指的是什么。
  2. 是否有任何非凡的反例,其中mask/where表现优于numpy.where?如果存在这样的示例,则可能会影响我如何选择合适的方法。

作为参考,以下是Pandas 0.19.2 / Python 3.6.0的一些基准测试:

np.random.seed(0)

n = 10000000
df = pd.DataFrame(np.random.random(n))

assert (df[0].mask(df[0] > 0.5, 1).values == np.where(df[0] > 0.5, 1, df[0])).all()

%timeit df[0].mask(df[0] > 0.5, 1)       # 145 ms per loop
%timeit np.where(df[0] > 0.5, 1, df[0])  # 113 ms per loop

对于非标量值,性能似乎 进一步不同

%timeit df[0].mask(df[0] > 0.5, df[0]*2)       # 338 ms per loop
%timeit np.where(df[0] > 0.5, df[0]*2, df[0])  # 153 ms per loop

问题答案:

我使用的是pandas 0.23.3和Python 3.6,因此仅在您的第二个示例中,我才能看到运行时间的真正差异。

但是,让我们研究一下第二个示例的稍有不同的版本(这样我们就可以避免2*df[0]了)。这是我们计算机上的基准:

twice = df[0]*2
mask = df[0] > 0.5
%timeit np.where(mask, twice, df[0])  
# 61.4 ms ± 1.51 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit df[0].mask(mask, twice)
# 143 ms ± 5.27 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Numpy的版本比熊猫快2.3倍。

因此,让我们对这两个函数进行概要分析,以了解两者之间的区别-
当人们不太熟悉代码基础时,概要分析是一种了解全局的好方法:它比调试更快,并且比试图弄清楚发生什么情况更容易出错只需阅读代码即可。

我在Linux上使用perf。对于numpy的版本,我们得到(有关列表,请参阅附录A):

>>> perf record python np_where.py
>>> perf report

Overhead  Command  Shared Object                                Symbol                              
  68,50%  python   multiarray.cpython-36m-x86_64-linux-gnu.so   [.] PyArray_Where
   8,96%  python   [unknown]                                    [k] 0xffffffff8140290c
   1,57%  python   mtrand.cpython-36m-x86_64-linux-gnu.so       [.] rk_random

我们可以看到,大部分时间都花在了PyArray_Where-大约69%上。未知符号是一个内核函数(事实上clear_page)-我在没有root特权的情况下运行,因此该符号无法解析。

对于大熊猫,我们得到了(代码参见附录B):

>>> perf record python pd_mask.py
>>> perf report

Overhead  Command  Shared Object                                Symbol                                                                                               
  37,12%  python   interpreter.cpython-36m-x86_64-linux-gnu.so  [.] vm_engine_iter_task
  23,36%  python   libc-2.23.so                                 [.] __memmove_ssse3_back
  19,78%  python   [unknown]                                    [k] 0xffffffff8140290c
   3,32%  python   umath.cpython-36m-x86_64-linux-gnu.so        [.] DOUBLE_isnan
   1,48%  python   umath.cpython-36m-x86_64-linux-gnu.so        [.] BOOL_logical_not

情况截然不同:

  • 熊猫并没有PyArray_Where在后台使用-最主要的时间消耗vm_engine_iter_task是numexpr-functionity。
  • 正在进行大量的内存复制-__memmove_ssse3_back大约25花费%的时间!内核的某些功能可能也与内存访问相关。

实际上,pandas-0.19PyArray_Where在引擎盖下使用,对于较旧的版本,perf-report报告看起来像:

Overhead  Command        Shared Object                     Symbol                                                                                                     
  32,42%  python         multiarray.so                     [.] PyArray_Where
  30,25%  python         libc-2.23.so                      [.] __memmove_ssse3_back
  21,31%  python         [kernel.kallsyms]                 [k] clear_page
   1,72%  python         [kernel.kallsyms]                 [k] __schedule

因此,从根本上讲,它那时将np.where在幕后使用+一些开销(全部在数据复制之上,请参阅参考资料__memmove_ssse3_back)。

我看不到在熊猫的0.19版本中大熊猫会比numpy更快的情况-它只是增加了numpy功能的开销。熊猫的0.23.3版本是一个完全不同的故事-
这里使用numexpr-module,很可能在某些情况下熊猫的版本(至少稍微快一些)。

我不确定是否真的需要/必须进行这种内存复制-也许有人甚至可以称它为性能缺陷,但我只是不知道可以肯定什么。

我们可以通过剥离一些间接指示(通过np.array而不是pd.Series)来帮助熊猫不要复制。例如:

%timeit df[0].mask(mask.values > 0.5, twice.values)
# 75.7 ms ± 1.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

现在,熊猫只慢25%。性能说明:

Overhead  Command  Shared Object                                Symbol                                                                                                
  50,81%  python   interpreter.cpython-36m-x86_64-linux-gnu.so  [.] vm_engine_iter_task
  14,12%  python   [unknown]                                    [k] 0xffffffff8140290c
   9,93%  python   libc-2.23.so                                 [.] __memmove_ssse3_back
   4,61%  python   umath.cpython-36m-x86_64-linux-gnu.so        [.] DOUBLE_isnan
   2,01%  python   umath.cpython-36m-x86_64-linux-gnu.so        [.] BOOL_logical_not

数据复制要少得多,但是比numpy的版本要多,后者主要负责开销。

我的主要收获是:

  • 熊猫有可能至少比numpy快一点(因为有可能更快)。但是,大熊猫对数据复制的处理有些不透明,因此很难预测何时(由于不必要的数据复制)会掩盖这种潜力。

  • where/的性能mask成为瓶颈时,我将使用numba / cython来提高性能-请参阅下文,我比较幼稚的尝试使用numba和cython。

这个想法是要

np.where(df[0] > 0.5, df[0]*2, df[0])

版本,并消除了创建临时文件(即)的需要df[0]*2

正如@ max9111所建议的那样,使用numba:

import numba as nb
@nb.njit
def nb_where(df):
    n = len(df)
    output = np.empty(n, dtype=np.float64)
    for i in range(n):
        if df[i]>0.5:
            output[i] = 2.0*df[i]
        else:
            output[i] = df[i]
    return output

assert(np.where(df[0] > 0.5, twice, df[0])==nb_where(df[0].values)).all()
%timeit np.where(df[0] > 0.5, df[0]*2, df[0])
# 85.1 ms ± 1.61 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit nb_where(df[0].values)
# 17.4 ms ± 673 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

这比numpy的版本快5倍!

这是我到目前为止在Cython的帮助下提高性能的尝试,但效果较差:

%%cython -a
cimport numpy as np
import numpy as np
cimport cython

@cython.boundscheck(False)
@cython.wraparound(False)
def cy_where(double[::1] df):
    cdef int i
    cdef int n = len(df)
    cdef np.ndarray[np.float64_t] output = np.empty(n, dtype=np.float64)
    for i in range(n):
        if df[i]>0.5:
            output[i] = 2.0*df[i]
        else:
            output[i] = df[i]
    return output

assert (df[0].mask(df[0] > 0.5, 2*df[0]).values == cy_where(df[0].values)).all()

%timeit cy_where(df[0].values)
# 66.7± 753 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

使速度提高25%。不确定,为什么cython比numba慢得多。

清单:

答: np_where.py:

import pandas as pd
import numpy as np

np.random.seed(0)

n = 10000000
df = pd.DataFrame(np.random.random(n))

twice = df[0]*2
for _ in range(50):
      np.where(df[0] > 0.5, twice, df[0])

B: pd_mask.py:

import pandas as pd
import numpy as np

np.random.seed(0)

n = 10000000
df = pd.DataFrame(np.random.random(n))

twice = df[0]*2
mask = df[0] > 0.5
for _ in range(50):
      df[0].mask(mask, twice)


 类似资料:
  • 问题内容: 我是Pandas的新手,正在尝试使用。我遇到了,和这样的各种好东西,而且我希望能够快速查找适当的字符串以得到我想要的东西。昨天我在文档中的某个地方找到了一个格式良好的表,但是该表的标题太钝了,以至于我今天无法使用搜索再次找到它。 问题答案: 您可以找到它称为Offset Aliases: 为有用的通用时间序列频率提供了许多字符串别名。我们将这些别名称为偏移别名。

  • 问题内容: 我有一个4个熊猫数据框的列表,其中包含我想合并为一个数据框的一天的报价数据。我无法理解concat在时间戳上的行为。请参阅以下详细信息: 使用我得到: 使用我得到: 注意使用时索引如何变化。为什么会发生这种情况,我将如何使用该方法来重现使用所获得的结果?(因为看上去快得多;每个循环24.6 ms,而每个循环3.02 s) 问题答案: 因此,您正在执行的操作是append和concat

  • 问题内容: 我有一个看起来像这样的DataFrame: 我想将其转换为对属于某些bin的视图进行计数,如下所示: 我试过了: 但它仅提供汇总计数,而不提供用户计数。如何获得用户的垃圾箱计数? 总计计数(使用我的真实数据)如下所示: 问题答案: 您可以按垃圾箱 和 用户名分组,计算分组大小,然后使用:

  • 我正在读一个带有如下浮点数的CSV: 并导入到数据框中,然后将此数据框写入新位置 现在,此

  • 问题内容: Python 3.4和Pandas 0.15.0 df是一个数据框,而col1是一列。使用下面的代码,我正在检查是否存在值10,并将此类值替换为1000。 这是另一个例子。这次,我将基于索引更改col2中的值。 这两种都会产生以下警告: 最后, 这会产生类似的警告,并带有以下建议: 我不确定我是否理解警告中指出的讨论。编写这三行代码的更好方法是什么? 请注意,该操作有效。 问题答案:

  • 问题内容: 我想将 大于任意数(在这种情况下为100)的值替换为(因为如此大的值表示实验失败)。以前,我使用它来替换不需要的值: 但是,出现以下错误: 从这个StackExchange问​​题来看,有时似乎可以忽略此警告,但是我不能很好地跟踪讨论,无法确定这是否适用于我的情况。警告基本上是让我知道我将覆盖我的某些值吗? 编辑:据我所知,一切都按其应有的方式进行。作为后续措施,我的替换值方法是否非标

  • 我有一个数据帧,如: 所以我想通过两个“for循环”添加一些列,如: 新的类似数据帧的图片: 我的代码不起作用: 如何编写代码来获得像第二张图片这样的数据帧?

  • 问题内容: 我有一个从csv文件构建的pandas中的数据框。数据框有几列,并由其中一列进行索引(这是唯一的,因为每一行都有用于该索引的该列的唯一值。) 如何基于应用于多个列的“复杂”过滤器选择数据框的行?我可以轻松地从列中大于10的数据框中选择切片,例如: 但是,如果我想要的东西就像一个过滤器:选择的切片,其中 任何 列都大于10? 或者,如果for的值大于10但值小于5? 这些如何在熊猫中实现