python-3.x - 为什么 df.lookup 比 df.min 慢?
问题描述
我想通过使用lookup
afteridxmin
而不是调用min
and来减少一些时间idxmin
。在我看来,第一个应该更有效,因为在第二个中需要搜索两次值(在最小值上,另一个用于最小值的索引 - 即 2 倍 O(NxM)),而在首先,搜索索引(O(NxM)),然后使用索引定位值(O(N))
请检查这个问题,以便您了解我的推理的上下文和更多详细信息。
结果开始出乎意料,所以我进行了一些测试:
我使用了 100000 行 x 10 列的数据框(添加更多行后结果会变得更糟):
import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.randint(0,100,size=(100000, 10)), columns=[f'option_{x}' for x in range(1,11)]).reset_index()
df['min_column'] = df.filter(like='option').idxmin(1)
然后我做了一些计时:
%timeit -n 100 df.filter(like='option').min(1)
# 12.2 ms ± 599 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit -n 100 df.lookup(df.index, df['min_column'])
# 46.9 ms ± 526 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
请注意,即使 是min_columns
为 预先计算的lookup
,结果也比简单地寻找最小值差 4 倍。
其他尺寸比较:
RowsxCols min lookup
100000x10 12.2ms 46.9ms
1000000x10 162ms 682ms
10000x1000 173ms 220ms
1000x10000 295ms 7.97ms
从上表中,正如预期的那样,添加行 (1000000x10) 并没有得到任何更好的结果,而添加更多列 (10000x1000) 时只是一个小的追赶。这种追赶是有道理的,但在我看来它应该更大,索引应该比搜索更快(请参阅更新的 numpy 结果),并且仅在极端情况下(几乎不切实际,例如 1000x10000)我开始看到优势。
这种行为有什么解释吗?
更新:
我用 numpy 测试了这个,我得到了预期的行为:
vals = np.random.randint(0,10,size=(100000, 10))
%timeit -n 100 np.min(vals, axis=1)
2.83 ms ± 235 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
idx_min = np.argmin(vals, axis=1)
%timeit -n 100 vals[np.arange(len(idx_min)), idx_min]
1.63 ms ± 243 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
比较结果(numpy):
RowsxCols min indexing using []
100000x10 2.83ms 1.63ms
1000000x10 24.6ms 15.4ms
100000x100 14.5ms 3.38ms
10000x1000 11.1ms 0.377ms
解决方案
如果看一下lookup函数的源码实现,看起来效率不是很高。源代码可以在这里找到:
http://github.com/pandas-dev/pandas/blob/v0.23.4/pandas/core/frame.py#L3435-L3484
特别是,在主要的 if-else 条件主体中,它确实
if not self._is_mixed_type or n > thresh:
values = self.values
ridx = self.index.get_indexer(row_labels)
cidx = self.columns.get_indexer(col_labels)
if (ridx == -1).any():
raise KeyError('One or more row labels was not found')
if (cidx == -1).any():
raise KeyError('One or more column labels was not found')
flat_index = ridx * len(self.columns) + cidx
result = values.flat[flat_index]
result = np.empty(n, dtype='O')
for i, (r, c) in enumerate(zip(row_labels, col_labels)):
result[i] = self._get_value(r, c)
我不确定 if case 的详细实现,但您可能想在大量行和大量列案例上尝试此操作,并且您可能会从查找函数中获得更好的结果。
您可能应该尝试定义自己的查找表,这样您就可以准确地知道运行时,而不是使用这个查找函数
推荐阅读
- qt - 在导入 qml singleton 的运行时加载 qml
- laravel - AWS Beanstalk 不接受 Laravel POST
- javascript - 使用 Chart.js 版本 3,如何使 y 轴标签可点击?
- android - RecyclerView 不会到原来的起始位置
- python - Python 输入永远不是 True
- powerbi - 如何绕过学习如何在 Power BI 中使用日期函数?
- mysql - MySQL - COUNT(*) 非常慢
- mongodb - Mongoose - 定义模式字段时如何知道所有可用属性?
- google-chrome-extension - 谷歌浏览器扩展清单 v3 中的弹出设置在哪里
- javascript - 从浏览器打开桌面应用程序时如何禁用警报弹出窗口