Numpy:快速找到第一个价值指数

时间:2011-10-03 09:03:57

标签: python numpy find

如何找到Numpy数组中第一次出现数字的索引? 速度对我很重要。我对以下答案不感兴趣,因为他们扫描整个数组并且在找到第一个匹配项时不会停止:

itemindex = numpy.where(array==item)[0][0]
nonzero(array == item)[0][0]

注1:该问题的答案似乎都不相关Is there a Numpy function to return the first index of something in an array?

注2:使用C编译方法优于Python循环。

16 个答案:

答案 0 :(得分:49)

为Numpy 2.0.0安排了此功能请求:https://github.com/numpy/numpy/issues/2269

答案 1 :(得分:26)

虽然对你来说太晚了,但是为了将来参考: 使用numba(1)是numpy实现它之前最简单的方法。如果你使用anaconda python发行版,它应该已经安装。 代码将被编译,因此速度很快。

@jit(nopython=True)
def find_first(item, vec):
    """return the index of the first occurence of item in vec"""
    for i in xrange(len(vec)):
        if item == vec[i]:
            return i
    return -1

然后:

>>> a = array([1,7,8,32])
>>> find_first(8,a)
2

答案 2 :(得分:16)

我已经为几种方法制定了基准:

  • argwhere
  • nonzero,如问题
  • .tostring()和@Rob Reilink的回答
  • 一样
  • python loop
  • Fortran循环

PythonFortran代码可用。我跳过了没有希望的人,比如转换成一个列表。

对数刻度的结果。 X轴是针的位置(如果它在阵列的下方,则需要更长的时间);最后一个值是一个不在数组中的针。 Y轴是找到它的时间。

benchmark results

阵列有100万个元素,测试运行100次。结果仍然有点波动,但定性趋势很明显:Python和f2py退出第一个元素,因此它们的扩展方式不同。如果针不在前1%,Python变得太慢,而f2py很快(但你需要编译它)。

总而言之, f2py是最快的解决方案,特别是如果针很早出现。

它不是内置的烦人,但它真的只需要2分钟的工作。将this添加到名为search.f90的文件中:

subroutine find_first(needle, haystack, haystack_length, index)
    implicit none
    integer, intent(in) :: needle
    integer, intent(in) :: haystack_length
    integer, intent(in), dimension(haystack_length) :: haystack
!f2py intent(inplace) haystack
    integer, intent(out) :: index
    integer :: k
    index = -1
    do k = 1, haystack_length
        if (haystack(k)==needle) then
            index = k - 1
            exit
        endif
    enddo
end

如果您正在寻找integer以外的其他内容,只需更改类型即可。然后使用:

进行编译
f2py -c -m search search.f90

之后你可以(从Python):

import search
print(search.find_first.__doc__)
a = search.find_first(your_int_needle, your_int_array)

答案 3 :(得分:11)

您可以使用array.tostring()将布尔数组转换为Python字符串,然后使用find()方法:

(array==item).tostring().find('\x01')

这确实涉及复制数据,因为Python字符串需要是不可变的。一个优点是你也可以搜索例如找到\x00\x01

的上升趋势

答案 4 :(得分:9)

如果排序数组np.searchsorted有效。

答案 5 :(得分:7)

我认为你遇到了一个问题,其中一个不同的方法和一些先验的数组知识真的会有所帮助。在Y%的数据中你有X概率找到答案的事情。分裂问题的希望是幸运,然后在python中使用嵌套列表理解或其他东西。

使用ctypes写一个C函数来做这个暴力也不是太难。

我一起入侵的C代码(index.c):

long index(long val, long *data, long length){
    long ans, i;
    for(i=0;i<length;i++){
        if (data[i] == val)
            return(i);
    }
    return(-999);
}

和python:

# to compile (mac)
# gcc -shared index.c -o index.dylib
import ctypes
lib = ctypes.CDLL('index.dylib')
lib.index.restype = ctypes.c_long
lib.index.argtypes = (ctypes.c_long, ctypes.POINTER(ctypes.c_long), ctypes.c_long)

import numpy as np
np.random.seed(8675309)
a = np.random.random_integers(0, 100, 10000)
print lib.index(57, a.ctypes.data_as(ctypes.POINTER(ctypes.c_long)), len(a))

我得到了92。

将python包装成一个合适的函数然后你去。

对于这个种子,C版本的速度要快很多(约20倍)(警告我对时间不好)

import timeit
t = timeit.Timer('np.where(a==57)[0][0]', 'import numpy as np; np.random.seed(1); a = np.random.random_integers(0, 1000000, 10000000)')
t.timeit(100)/100
# 0.09761879920959472
t2 = timeit.Timer('lib.index(57, a.ctypes.data_as(ctypes.POINTER(ctypes.c_long)), len(a))', 'import numpy as np; np.random.seed(1); a = np.random.random_integers(0, 1000000, 10000000); import ctypes; lib = ctypes.CDLL("index.dylib"); lib.index.restype = ctypes.c_long; lib.index.argtypes = (ctypes.c_long, ctypes.POINTER(ctypes.c_long), ctypes.c_long) ')
t2.timeit(100)/100
# 0.005288000106811523

答案 6 :(得分:4)

如果您要查找第一个非零元素,则可以使用以下技巧:

idx = x.view(bool).argmax() // x.itemsize
idx = idx if x[idx] else -1

这是一个非常快速的“ numpy-pure”解决方案,但在下面讨论的某些情况下失败。

该解决方案利用了以下事实:数字类型的几乎所有零表示都由0个字节组成。它也适用于numpy的bool。在最新版本的numpy中,argmax()函数在处理bool类型时使用短路逻辑。 bool的大小为1个字节。

所以需要:

  • bool的形式创建数组的视图。没有创建副本
  • 使用argmax()通过短路逻辑查找第一个非零字节
  • 通过将偏移量的整数除(运算符//)除以以字节(x.itemsize表示的单个元素的大小,来重新计算此字节到第一个非零元素的索引的偏移量。
  • 检查x[idx]是否实际上为非零,以识别不存在非零的情况。

我已经针对numba解决方案建立了一些基准,并将其构建为np.nonzero

import numpy as np
from numba import jit
from timeit import timeit

def find_first(x):
    idx = x.view(bool).argmax() // x.itemsize
    return idx if x[idx] else -1

@jit(nopython=True)
def find_first_numba(vec):
    """return the index of the first occurence of item in vec"""
    for i in range(len(vec)):
        if vec[i]:
            return i
    return -1


SIZE = 10_000_000
# First only
x = np.empty(SIZE)

find_first_numba(x[:10])

print('---- FIRST ----')
x[:] = 0
x[0] = 1
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0][0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=1000), 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=1000), 'ms')

print('---- LAST ----')
x[:] = 0
x[-1] = 1
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0][0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=100)*10, 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=100)*10, 'ms')

print('---- NONE ----')
x[:] = 0
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=100)*10, 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=100)*10, 'ms')

print('---- ALL ----')
x[:] = 1
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0][0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=100)*10, 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=100)*10, 'ms')

我的机器上的结果是:

---- FIRST ----
ndarray.nonzero 57.63976670001284 ms
find_first 0.0010841979965334758 ms
find_first_numba 0.0002308919938514009 ms
---- LAST ----
ndarray.nonzero 58.96685277999495 ms
find_first 5.923203580023255 ms
find_first_numba 8.762269750004634 ms
---- NONE ----
ndarray.nonzero 25.13398071998381 ms
find_first 5.924289370013867 ms
find_first_numba 8.810063839919167 ms
---- ALL ----
ndarray.nonzero 55.181210660084616 ms
find_first 0.001246920000994578 ms
find_first_numba 0.00028766007744707167 ms

解决方案比numba快33%,并且是“ numpy-pure”。

缺点:

  • 不适用于numpy可接受的类型,例如object
  • 对于偶然出现在floatdouble计算中的负零失败

答案 7 :(得分:2)

如果您的列表已排序,您可以使用'bisect'包实现非常快速搜索索引。 它是O(log(n))而不是O(n)。

bisect.bisect(a, x)

在数组a中找到x,在排序的情况下肯定比通过所有第一个元素的任何C例程(对于足够长的列表)更快。

有时候知道很好。

答案 8 :(得分:2)

@tal已经提供了一个numba函数来查找第一个索引,但这只适用于1D数组。使用np.ndenumerate,您还可以在任意维数组中找到第一个索引:

from numba import njit
import numpy as np

@njit
def index(array, item):
    for idx, val in np.ndenumerate(array):
        if val == item:
            return idx
    return None

示例案例:

>>> arr = np.arange(9).reshape(3,3)
>>> index(arr, 3)
(1, 0)

Timings表明它与tals解决方案的性能相似:

arr = np.arange(100000)
%timeit index(arr, 5)           # 1000000 loops, best of 3: 1.88 µs per loop
%timeit find_first(5, arr)      # 1000000 loops, best of 3: 1.7 µs per loop

%timeit index(arr, 99999)       # 10000 loops, best of 3: 118 µs per loop
%timeit find_first(99999, arr)  # 10000 loops, best of 3: 96 µs per loop

答案 9 :(得分:1)

据我所知,只有布尔数组中的np.any和np.all被短路。

在你的情况下,numpy必须经历两次整个数组,一次创建布尔条件,第二次查找索引。

我在这种情况下的建议是使用cython。我认为应该很容易为这种情况调整一个例子,特别是如果你不需要很多灵活性来处理不同的dtypes和形状。

答案 10 :(得分:1)

我需要这个来完成我的工作,所以我自学了Python和Numpy的C界面并编写了我自己的。 http://pastebin.com/GtcXuLyd它仅适用于1-D数组,但适用于大多数数据类型(int,float或字符串),测试表明它再次比纯Python-numpy中的预期方法快20倍。

答案 11 :(得分:1)

作为Matlab的长期用户,我一直在寻找解决此问题的有效方法。最后,出于讨论的动机,我在此thread中提出了一个建议,我试图提出一种解决方案,该解决方案实现的API与建议的here类似,目前仅支持一维数组。

您会这样使用它

import numpy as np
import utils_find_1st as utf1st
array = np.arange(100000)
item = 1000
ind = utf1st.find_1st(array, item, utf1st.cmp_larger_eq)

支持的条件运算符为:cmp_equal,cmp_not_equal,cmp_larger,cmp_smaller,cmp_larger_eq,cmp_smaller_eq。为了提高效率,扩展名用c编写。

您可以在此处找到源代码,基准和其他详细信息:

https://pypi.python.org/pypi?name=py_find_1st&:action=display

供我们团队使用(在Linux和macOS上为anaconda),我制作了一个anaconda安装程序以简化安装,您可以按此处所述使用它

https://anaconda.org/roebel/py_find_1st

答案 12 :(得分:0)

请注意,如果您正在进行一系列搜索,那么如果搜索维度不够大,那么通过做一些聪明的事情(如转换为字符串)所带来的性能提升可能会在外部循环中丢失。看看迭代使用上面提出的字符串转换技巧的find1和沿内轴使用argmax的find2的性能(加上调整以确保不匹配返回为-1)

import numpy,time
def find1(arr,value):
    return (arr==value).tostring().find('\x01')

def find2(arr,value): #find value over inner most axis, and return array of indices to the match
    b = arr==value
    return b.argmax(axis=-1) - ~(b.any())


for size in [(1,100000000),(10000,10000),(1000000,100),(10000000,10)]:
    print(size)
    values = numpy.random.choice([0,0,0,0,0,0,0,1],size=size)
    v = values>0

    t=time.time()
    numpy.apply_along_axis(find1,-1,v,1)
    print('find1',time.time()-t)

    t=time.time()
    find2(v,1)
    print('find2',time.time()-t)

输出

(1, 100000000)
('find1', 0.25300002098083496)
('find2', 0.2780001163482666)
(10000, 10000)
('find1', 0.46200013160705566)
('find2', 0.27300000190734863)
(1000000, 100)
('find1', 20.98099994659424)
('find2', 0.3040001392364502)
(10000000, 10)
('find1', 206.7590000629425)
('find2', 0.4830000400543213)

也就是说,用C语言编写的发现至少比这些方法中的任何一种快一点

答案 13 :(得分:0)

这个怎么样

import numpy as np
np.amin(np.where(array==item))

答案 14 :(得分:0)

通过以大块处理数组,可以在纯numpy中有效解决此问题:

prism:RegionManager.RegionName="ContentRegion"

该数组以大小Prism的块进行处理。 VIEW步骤越长,对零阵列的处理就越快(最坏的情况)。它越小,开始处理非零数组的速度就越快。诀窍是从一个小的def find_first(x): idx, step = 0, 32 while idx < x.size: nz, = x[idx: idx + step].nonzero() if len(nz): # found non-zero, return it return nz[0] + idx # move to the next chunk, increase step idx += step step = min(9600, step + step // 2) return -1 开始,然后成倍增加。此外,由于收益有限,无需将其增加到某个阈值以上。

我已经将纯ndarary.nonzero和numba解决方案与1000万个浮点数组进行了比较。

step

结果在我的机器上

step

step绝对宽松。最好的情况下,numba解决方案的速度大约提高了5倍。在最坏的情况下,速度快大约3倍。

答案 15 :(得分:-1)

您可以将数组转换为list并使用index()方法:

i = list(array).index(item)

据我所知,这是一个C编译方法。