为什么df.lookup比df.min慢?

tot*_*ico 2 python-3.x pandas

我想通过使用lookupafter idxmin而不是调用minand 来节省一些时间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)
Run Code Online (Sandbox Code Playgroud)

然后我做了一些计时:

%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)
Run Code Online (Sandbox Code Playgroud)

请注意,即使min_columns为预先计算了lookup,结果也比单纯寻找最小值要差4倍。

其他尺寸的比较:

RowsxCols    min        lookup
100000x10    12.2ms     46.9ms
1000000x10   162ms      682ms
10000x1000   173ms      220ms
1000x10000   295ms      7.97ms
Run Code Online (Sandbox Code Playgroud)

从上表中可以看出,通过添加行(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)
Run Code Online (Sandbox Code Playgroud)

比较结果(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
Run Code Online (Sandbox Code Playgroud)

Meh*_*ari 6

如果您查看查找功能的源代码实现,那么看起来效率不是很高。源代码可以在这里找到:

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)
Run Code Online (Sandbox Code Playgroud)

我不确定if case的详细实现,但是您可能想在大量行和大量列的情况下尝试使用此方法,并且从查找功能中可以获得更好的结果。

您可能应该尝试定义自己的查找表,以便确切地知道运行时,而不是使用此查找功能