python - 使用 np.vectorize 将函数应用于 Pandas 数据框中所有列的正确方法
问题描述
我有一个带有任意列数的 Pandas 数据框。我想对每一列应用一个函数。从关于这个 SO POst的讨论来看,与 pandas apply 函数相比,使用 np.vectorize 更好。
但是,我将如何使用 np.vectorize 对每一列执行操作?
我能想出的最好的主意是带有 for 循环的 np.vectorize ,但这会在我的机器上在一个虚拟数据帧上花费 2 倍的时间。是最优apply
的raw=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))
解决方案
正确的使用方法np.vectorize
是不要使用它——除非你正在处理一个只接受标量值的函数,而且你不关心速度。当我测试过它时,显式 Python 迭代速度更快。
至少在使用numpy
数组时是这种情况。使用DataFrames
时,事情变得更加复杂,因为提取 Series 和重新创建帧会大大扭曲时间。
但是,让我们更详细地看一下您的示例。
您的示例框架:
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**
只需使用框架自己的加法运算符,我就能得到同样的结果——而且速度更快。好的,对于一个不起作用的更通用的功能。您使用apply
with default axisraw
意味着您有一个一次只能处理一列的函数。
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.
推荐阅读
- javascript - 格式化 Gtk.SpinButton 的输出不适用于单击鼠标
- javascript - id_token 未定义 google api nodejs
- python - 无法弄清楚如何让我的鼠标光标位于矩形的中心
- java - 在单元测试中检测“非法反射访问”
- spring - 放入对象列表后,无法查看 thymeleaf 中的表单
- python - 如何使用正则表达式从 NLTK 语料库中查找大写字母单词?
- wordpress - 如果用户登录 Woocommerce,如何获取最新的订单 ID?
- java - WebSphere 身份验证问题——也可能打开文件
- magento - Magento 产品图像缓存 - 背景颜色问题
- php - 根据产品类别和运输方式 WooCommerce 添加费用