Pandas:高效获取小于给定值的首行元素
我在想,在pandas中有没有更高效的方法来做到这一点:给定一个数据框,如何找到第一个小于某个值的行?比如,给定:
addr
0 4196656
1 4197034
2 4197075
3 4197082
4 4197134
我想知道第一个小于4197080的值是什么?我希望它能返回只有4197075这一行。一个解决办法是先筛选出小于4197080的行,然后再取最后一行,但这样做似乎会非常慢,复杂度是O(N)(首先构建一个数据框,然后再取它的最后一行),而二分查找的复杂度是O(logN)。
df.addr[ df.addr < 4197080].tail(1)
我测了一下,创建df.addr[ df.addr < 4197080]
的时间和df.addr[ df.addr < 4197080].tail(1)
差不多,这强烈暗示它内部首先构建了一个完整的数据框。
num = np.random.randint(0, 10**8, 10**6)
num.sort()
df = pd.DataFrame({'addr':num})
df = df.set_index('addr', drop=False)
df = df.sort_index()
获取第一个小于的值非常慢:
%timeit df.addr[ df.addr < 57830391].tail(1)
100 loops, best of 3: 7.9 ms per loop
使用lt可以稍微改善一下:
%timeit df.lt(57830391)[-1:]
1000 loops, best of 3: 853 µs per loop
但还是没有二分查找快:
%timeit bisect(num, 57830391, 0, len(num))
100000 loops, best of 3: 6.53 µs per loop
有没有更好的方法呢?
1 个回答
8
这个需要版本0.14.0
注意,这个框架并没有经过排序。
In [16]: s = df['addr']
找到小于所需值的最大值
In [18]: %timeit s[s<5783091]
100 loops, best of 3: 9.01 ms per loop
In [19]: %timeit s[s<5783091].nlargest(1)
100 loops, best of 3: 11 ms per loop
所以,这种方法比先完全排序再索引要快。这里的.copy
是为了避免影响原地排序的结果。
In [32]: x = np.random.randint(0, 10**8, 10**6)
In [33]: def f(x):
....: x.copy().sort()
....:
In [35]: %timeit f(x)
10 loops, best of 3: 67.2 ms per loop
如果你只是想在一个已经排序好的序列中查找,那么可以使用searchsorted
。注意,你必须使用numpy版本(比如在.values
上操作)。序列版本会在0.14.1中定义。
In [41]: %timeit s.values.searchsorted(5783091)
100000 loops, best of 3: 2.5 µs per loop