python - Pandas 的性能应用 vs np.vectorize 从现有列创建新列

标签 python arrays pandas performance numpy

我正在使用 Pandas 数据框并希望创建一个新列作为现有列的函数。没看到关于df.apply()速度差异的好讨论和 np.vectorize() ,所以我想我会在这里问。

Pandas apply()功能很慢。从我测量到的(在下面的一些实验中显示),使用 np.vectorize()比使用 DataFrame 函数快 25 倍(或更多)apply() ,至少在我 2016 年的 MacBook Pro 上是这样。 这是预期的结果,为什么?

例如,假设我有以下数据框 N行:

N = 10
A_list = np.random.randint(1, 100, N)
B_list = np.random.randint(1, 100, N)
df = pd.DataFrame({'A': A_list, 'B': B_list})
df.head()
#     A   B
# 0  78  50
# 1  23  91
# 2  55  62
# 3  82  64
# 4  99  80

进一步假设我想创建一个新列作为两列的函数 AB .在下面的例子中,我将使用一个简单的函数 divide() .要应用该功能,我可以使用 df.apply()np.vectorize() :
def divide(a, b):
    if b == 0:
        return 0.0
    return float(a)/b

df['result'] = df.apply(lambda row: divide(row['A'], row['B']), axis=1)

df['result2'] = np.vectorize(divide)(df['A'], df['B'])

df.head()
#     A   B    result   result2
# 0  78  50  1.560000  1.560000
# 1  23  91  0.252747  0.252747
# 2  55  62  0.887097  0.887097
# 3  82  64  1.281250  1.281250
# 4  99  80  1.237500  1.237500

如果我增加 N到 100 万或更多的真实世界大小,然后我观察到 np.vectorize()df.apply() 快 25 倍或更多.

下面是一些完整的基准测试代码:
import pandas as pd
import numpy as np
import time

def divide(a, b):
    if b == 0:
        return 0.0
    return float(a)/b

for N in [1000, 10000, 100000, 1000000, 10000000]:    

    print ''
    A_list = np.random.randint(1, 100, N)
    B_list = np.random.randint(1, 100, N)
    df = pd.DataFrame({'A': A_list, 'B': B_list})

    start_epoch_sec = int(time.time())
    df['result'] = df.apply(lambda row: divide(row['A'], row['B']), axis=1)
    end_epoch_sec = int(time.time())
    result_apply = end_epoch_sec - start_epoch_sec

    start_epoch_sec = int(time.time())
    df['result2'] = np.vectorize(divide)(df['A'], df['B'])
    end_epoch_sec = int(time.time())
    result_vectorize = end_epoch_sec - start_epoch_sec


    print 'N=%d, df.apply: %d sec, np.vectorize: %d sec' % \
            (N, result_apply, result_vectorize)

    # Make sure results from df.apply and np.vectorize match.
    assert(df['result'].equals(df['result2']))

结果如下所示:
N=1000, df.apply: 0 sec, np.vectorize: 0 sec

N=10000, df.apply: 1 sec, np.vectorize: 0 sec

N=100000, df.apply: 2 sec, np.vectorize: 0 sec

N=1000000, df.apply: 24 sec, np.vectorize: 1 sec

N=10000000, df.apply: 262 sec, np.vectorize: 4 sec

np.vectorize()通常总是比 df.apply() 快,那为什么是 np.vectorize()没有提到更多?我只看到过与 df.apply() 相关的 StackOverflow 帖子,例如:

pandas create new column based on values from other columns

How do I use Pandas 'apply' function to multiple columns?

How to apply a function to two columns of Pandas dataframe

最佳答案

我首先要说的是 Pandas 和 NumPy 数组的强大之处在于高性能 矢量化 数值数组上的计算。1 矢量化计算的重点是通过将计算转移到高度优化的 C 代码并利用连续的内存块来避免 Python 级循环。2

Python 级循环

现在我们可以看看一些时间。以下是全部 产生 pd.Series 的 Python 级循环, np.ndarraylist包含相同值的对象。为了分配给数据框中的系列,结果具有可比性。

# Python 3.6.5, NumPy 1.14.3, Pandas 0.23.0

np.random.seed(0)
N = 10**5

%timeit list(map(divide, df['A'], df['B']))                                   # 43.9 ms
%timeit np.vectorize(divide)(df['A'], df['B'])                                # 48.1 ms
%timeit [divide(a, b) for a, b in zip(df['A'], df['B'])]                      # 49.4 ms
%timeit [divide(a, b) for a, b in df[['A', 'B']].itertuples(index=False)]     # 112 ms
%timeit df.apply(lambda row: divide(*row), axis=1, raw=True)                  # 760 ms
%timeit df.apply(lambda row: divide(row['A'], row['B']), axis=1)              # 4.83 s
%timeit [divide(row['A'], row['B']) for _, row in df[['A', 'B']].iterrows()]  # 11.6 s

一些要点:
  • tuple基于的方法(前 4 个)是一个比 pd.Series 更有效的因素基于方法(最后 3 个)。
  • np.vectorize , 列表理解 + zipmap方法,即前 3 种方法,都具有大致相同的性能。这是因为他们使用 tuple并绕过来自 pd.DataFrame.itertuples 的一些 Pandas 开销.
  • 使用 raw=True 显着提高了速度与 pd.DataFrame.apply与没有。此选项将 NumPy 数组提供给自定义函数而不是 pd.Series对象。
  • pd.DataFrame.apply : 只是另一个循环

    要准确查看 Pandas 传递的对象,您可以简单地修改您的函数:
    def foo(row):
        print(type(row))
        assert False  # because you only need to see this once
    df.apply(lambda row: foo(row), axis=1)
    

    输出:<class 'pandas.core.series.Series'> .相对于 NumPy 数组,创建、传递和查询 Pandas 系列对象会带来大量开销。这应该不足为奇:Pandas 系列包括相当数量的脚手架来保存索引、值、属性等。

    raw=True 再次做同样的练习你会看到 <class 'numpy.ndarray'> .所有这些都在文档中进行了描述,但看到它更有说服力。
    np.vectorize : 假矢量化

    np.vectorize 的文档有以下说明:

    The vectorized function evaluates pyfunc over successive tuples of the input arrays like the python map function, except it uses the broadcasting rules of numpy.



    “广播规则”在这里无关紧要,因为输入数组具有相同的维度。平行于 map很有启发性,因为 map上面的版本具有几乎相同的性能。 source code显示正在发生的事情:np.vectorize将您的输入函数转换为 Universal function (“ufunc”)通过 np.frompyfunc .有一些优化,例如缓存,这可以导致一些性能改进。

    总之,np.vectorize做了 Python 级循环应该做的事情,但是 pd.DataFrame.apply增加了大量开销。没有您在 numba 中看到的 JIT 编译(见下文)。它是 just a convenience .

    真正的矢量化:你应该使用什么

    为什么在任何地方都没有提到上述差异?因为真正矢量化计算的性能使它们变得无关紧要:
    %timeit np.where(df['B'] == 0, 0, df['A'] / df['B'])       # 1.17 ms
    %timeit (df['A'] / df['B']).replace([np.inf, -np.inf], 0)  # 1.96 ms
    

    是的,这比上述循环解决方案中最快的速度快约 40 倍。其中任何一个都是可以接受的。在我看来,第一个是简洁、可读和高效的。只看其他方法,例如numba下面,如果性能至关重要并且这是您的瓶颈的一部分。
    numba.njit : 更高的效率

    当循环被认为可行时,它们通常通过 numba 进行优化。使用底层 NumPy 数组尽可能多地移动到 C。

    确实,numba将性能提高到微秒。如果没有一些繁琐的工作,将很难获得比这更高的效率。
    from numba import njit
    
    @njit
    def divide(a, b):
        res = np.empty(a.shape)
        for i in range(len(a)):
            if b[i] != 0:
                res[i] = a[i] / b[i]
            else:
                res[i] = 0
        return res
    
    %timeit divide(df['A'].values, df['B'].values)  # 717 µs
    

    使用 @njit(parallel=True)可能为更大的阵列提供进一步的插入。

    1 数字类型包括:int , float , datetime , bool , category .他们排除 object dtype 并且可以保存在连续的内存块中。

    2
    NumPy 操作比 Python 高效的原因至少有两个:
  • Python 中的一切都是对象。与 C 不同,这包括数字。因此,Python 类型具有 native C 类型不存在的开销。
  • NumPy 方法通常基于 C。此外,优化算法
    在可能的情况下使用。
  • 关于python - Pandas 的性能应用 vs np.vectorize 从现有列创建新列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52673285/

    相关文章:

    python - 在 Pandas 系列中找到相邻的 True 组

    php 和 django 在同一个 lighttpd 服务器上

    ios - 对核心数据实体进行排序的最佳方法是什么?

    ios - 使用 for 更改 var 名称

    python - 将 JSON 对象的一部分存储在 pandas df 中

    python-3.x - 使用 Pandas Dataframe 的 TextBlob 翻译功能时出现问题

    python - 提高 Pandas 行最近邻居的性能

    python - 将 Series 平铺到给定数组

    python - 使用结构模式匹配反转测试

    c++ - 试图打印出数组,但它在打印前进入了一个新行