【问题标题】:Correct way to use np.vectorize to apply functions to all columns in Pandas dataframe使用 np.vectorize 将函数应用于 Pandas 数据框中所有列的正确方法
【发布时间】:2021-06-07 07:49:07
【问题描述】:

我有一个包含任意列数的 Pandas 数据框。我想对每一列应用一个函数。从this SO POst 的讨论来看,与 pandas 应用函数相比,使用 np.vectorize 更好。

但是,我将如何使用 np.vectorize 对每一列执行操作?

我能想到的最好的想法是 np.vectorize 与一个 for 循环,但结果表明我的机器在一个虚拟数据帧上花费 2 倍的时间。 applyraw=True 是最优的,然后就更快的选择而言,我们只能利用 numba

test_df = pd.DataFrame({'a': np.arange(5), 'b': np.arange(5), 'c': np.arange(5)})

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

start_time = time.time()
test_df.apply(lambda x: x + 3, raw = True)
print("--- %s seconds ---" % (time.time() - start_time))

start_time = time.time()
for i in range(test_df.shape[1]):
    np.vectorize(myfunc)(test_df.iloc[:,i], 3)
print("--- %s seconds ---" % (time.time() - start_time))

【问题讨论】:

  • 数据框中的典型行数是多少?只有 5 行,性能比较可能难以解释。
  • 阅读链接中的答案。 np.vectorize 不快。如果它优于 apply,那是因为 apply 非常慢。
  • 测试myfunc(test_df.iloc[:,i],3)
  • 在链接中,np.vectorize 是使用listmap 之后第二快的选项。我正在将预期功能部署在具有不同组层次结构的分组数据帧上,因此每个组中的行数将在 4 到 20K 左右之间变化
  • 在一个 10K 的虚拟数据帧上,myfunc(test_df.iloc[:,i],3) 花费的时间比 apply 长约 1/3

标签: python pandas numpy


【解决方案1】:

使用np.vectorize 的正确方法是使用它——除非你正在处理一个只接受标量值的函数,而且你不关心速度。当我测试过它时,显式 Python 迭代速度更快。

至少在使用numpy 数组时是这样。使用DataFrames,事情变得更加复杂,因为提取系列和重新创建帧可能会大大扭曲时间。


但是让我们更详细地看一下您的示例。

您的示例框架:

In [177]: test_df = pd.DataFrame({'a': np.arange(5), 'b': np.arange(5), 'c': np.arange(5)})
     ...: 
In [178]: test_df
Out[178]: 
   a  b  c
0  0  0  0
1  1  1  1
2  2  2  2
3  3  3  3
4  4  4  4
In [179]: def myfunc(a, b):
     ...:     return a+b
     ...: 

您的申请:

In [180]: test_df.apply(lambda x: x+3, raw=True)
Out[180]: 
   a  b  c
0  3  3  3
1  4  4  4
2  5  5  5
3  6  6  6
4  7  7  7
In [181]: timeit test_df.apply(lambda x: x+3, raw=True)
186 µs ± 524 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
# 1.23 ms ± 13.9 µs per loop without **raw**

我只需使用框架自己的加法运算符就可以得到同样的结果——而且速度更快。好的,对于一个不起作用的更通用的功能。您使用带有默认轴的applyraw 意味着您有一个一次只能处理一列的函数。

In [182]: test_df+3
Out[182]: 
   a  b  c
0  3  3  3
1  4  4  4
2  5  5  5
3  6  6  6
4  7  7  7
In [183]: timeit test_df+3
114 µs ± 3.02 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

使用raw,您将numpy 数组传递给lambda。整个帧的数组是:

In [184]: test_df.to_numpy()
Out[184]: 
array([[0, 0, 0],
       [1, 1, 1],
       [2, 2, 2],
       [3, 3, 3],
       [4, 4, 4]])
In [185]: test_df.to_numpy()+3
Out[185]: 
array([[3, 3, 3],
       [4, 4, 4],
       [5, 5, 5],
       [6, 6, 6],
       [7, 7, 7]])
In [186]: timeit test_df.to_numpy()+3
13.1 µs ± 119 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

这要快得多。但是返回一个帧需要时间。

In [188]: timeit pd.DataFrame(test_df.to_numpy()+3, columns=test_df.columns)
91.1 µs ± 769 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [189]: 

测试vectorize

In [218]: f=np.vectorize(myfunc)

f 迭代地将myfunc 应用于输入数组的每个元素。它有明确的性能免责声明。

即使对于这个小数组,与直接将函数应用于数组相比,它也很慢:

In [219]: timeit f(test_df.to_numpy(),3)
42.3 µs ± 123 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

传递框架本身

In [221]: timeit f(test_df,3)
69.8 µs ± 1.98 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [223]: timeit pd.DataFrame(f(test_df,3), columns=test_df.columns)
154 µs ± 2.11 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

并且迭代地应用于列 - 慢得多:

In [226]: [f(test_df.iloc[:,i], 3) for i in range(test_df.shape[1])]
Out[226]: [array([3, 4, 5, 6, 7]), array([3, 4, 5, 6, 7]), array([3, 4, 5, 6, 7])]
In [227]: timeit [f(test_df.iloc[:,i], 3) for i in range(test_df.shape[1])]
477 µs ± 17.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

但很多额外时间来自“提取”列:

In [228]: timeit [f(test_df.to_numpy()[:,i], 3) for i in range(test_df.shape[1])]
127 µs ± 357 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

引用np.vectorize 文档:

Notes
-----
The `vectorize` function is provided primarily for convenience, not for
performance. The implementation is essentially a for loop.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-09-22
    • 1970-01-01
    • 2012-09-26
    • 1970-01-01
    • 1970-01-01
    • 2012-10-31
    相关资源
    最近更新 更多