web-dev-qa-db-fra.com

Numpy: recherche rapidement le premier indice de valeur

Comment trouver l'index de la première occurrence d'un nombre dans un tableau Numpy? La vitesse est importante pour moi. Les réponses suivantes ne m'intéressent pas car elles analysent tout le tableau et ne s'arrêtent pas lorsqu'elles trouvent la première occurrence:

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

Remarque 1: aucune des réponses à cette question ne semble pertinente Existe-t-il une fonction Numpy pour renvoyer le premier index de quelque chose dans un tableau?

Remarque 2: l'utilisation d'une méthode compilée en C est préférable à une boucle Python.

87
cyborg

Il existe une demande de fonctionnalité pour cette opération prévue pour Numpy 2.0.0: https://github.com/numpy/numpy/issues/2269

47
cyborg

J'ai fait une référence pour plusieurs méthodes:

  • argwhere
  • nonzero comme dans la question
  • .tostring() comme dans la réponse de @Rob Reilink
  • boucle de python
  • Boucle Fortran

Les codes Python et Fortran sont disponibles. J'ai ignoré ceux qui ne promettaient pas, comme la conversion en liste.

Les résultats à l'échelle logarithmique. L'axe X est la position de l'aiguille (il faut plus de temps pour savoir si c'est plus bas dans la matrice); La dernière valeur est une aiguille qui n'est pas dans le tableau. L'axe Y est le temps de le trouver.

 benchmark results

La matrice contenait 1 million d'éléments et les tests ont été exécutés 100 fois. Les résultats fluctuent encore un peu, mais la tendance qualitative est claire: Python et f2py se sont arrêtés au premier élément afin d’agrandir différemment. Python devient trop lent si l'aiguille n'est pas dans le premier 1%, alors que f2py est rapide (mais vous devez le compiler).

Pour résumer, f2py est la solution la plus rapide, surtout si l’aiguille apparaît assez tôt.

Ce n'est pas construit dans ce qui est ennuyeux, mais c'est vraiment juste 2 minutes de travail. Ajoutez this à un fichier appelé 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

Si vous cherchez autre chose que integer, changez simplement le type. Puis compiler en utilisant:

f2py -c -m search search.f90

après quoi vous pouvez faire (depuis Python):

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

Vous pouvez convertir un tableau booléen en chaîne Python en utilisant array.tostring(), puis en utilisant la méthode find ():

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

Cela implique toutefois la copie des données, car les chaînes Python doivent être immuables. Un avantage est que vous pouvez également rechercher par exemple un front montant en trouvant \x00\x01

11
Rob Reilink

En cas de tableaux triés, np.searchsorted fonctionne.

8
bubu

Je pense que vous avez rencontré un problème où une méthode différente et une certaine connaissance à priori du tableau aideraient vraiment. Le genre de chose où vous avez une probabilité X de trouver votre réponse dans les Y premiers pourcent des données. La scission du problème avec l'espoir d'avoir de la chance puis de le faire dans python avec une compréhension de liste imbriquée ou quelque chose du genre.

Ecrire une fonction C pour faire cette force brute n'est pas trop dur en utilisant ctypes non plus.

Le code C que j'ai piraté ensemble (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);
}

et le 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))

et j'ai 92 ans.

Enveloppez le python dans une fonction appropriée et le tour est joué.

La version C est beaucoup plus rapide (~ 20x) pour cette graine (attention je ne suis pas bon avec le temps)

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
7
Brian Larsen

Si votre liste est triée , vous pouvez réaliser très rapidement recherche dans l'index avec le paquet 'bisect' . C'est O(log(n)) au lieu de O (n) .

bisect.bisect(a, x)

trouve x dans le tableau a, nettement plus rapidement dans le cas trié que n'importe quelle routine C passant par tous les premiers éléments (pour des listes assez longues).

C'est bon à savoir parfois.

2
ngrislain

@tal a déjà présenté une fonction numba pour trouver le premier index, mais cela ne fonctionne que pour les tableaux 1D. Avec np.ndenumerate vous pouvez également trouver le premier index dans un tableau de dimensions arbitraires:

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

Cas d'échantillon:

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

Les timings montrent que ses performances sont similaires à celles de tals solution:

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
2
MSeifert

J'avais besoin de ça pour mon travail, alors j'ai appris moi-même l'interface C de Python et Numpy et écrit la mienne. http://Pastebin.com/GtcXuLyd Ce n'est que pour les tableaux 1D, mais fonctionne pour la plupart des types de données (int, float ou chaînes) et les tests ont montré qu'il est encore environ 20 fois plus rapide que l'approche attendue en pur Python-numpy.

1
dpitch40

Pour autant que je sache, seuls np.any et np.all sur les tableaux booléens sont court-circuités.

Dans votre cas, numpy doit parcourir tout le tableau deux fois, une fois pour créer la condition booléenne et une seconde fois pour trouver les index.

Ma recommandation dans ce cas serait d'utiliser Cython. Je pense qu'il devrait être facile d'ajuster un exemple pour ce cas, surtout si vous n'avez pas besoin de beaucoup de flexibilité pour différents types et formes.

1
Josef

que dis-tu de ça 

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

Remarquez simplement que si vous effectuez une séquence de recherches, le gain de performances obtenu par quelque chose d'intelligent, tel que la conversion en chaîne, risque d'être perdu dans la boucle externe si la dimension de recherche n'est pas assez grande. Observez les performances de la recherche itérative find1 qui utilise l'astuce de conversion de chaîne proposée ci-dessus et find2 qui utilise argmax le long de l'axe interne (plus un ajustement pour garantir une non-correspondance renvoyée sous la forme -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)

les sorties

(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)

Cela dit, une découverte écrite en C serait au moins un peu plus rapide que l'une ou l'autre de ces approches.

0
dlm

En tant qu'utilisateur de matlab de longue date, je cherche depuis longtemps déjà une solution efficace à ce problème. Enfin, motivé par les discussions, une proposition de ce thread / j’essaie de trouver une solution qui implémente une API similaire à celle suggérée ici , ne supportant pour le moment que des tableaux 1D. Pour plus d’efficacité, l’extension est écrite en C et devrait donc être plutôt efficace.

Vous trouvez la source, les repères et d’autres détails ici:

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

pour l'utilisation dans notre équipe (anaconda sur linux et macos), j'ai créé un programme d'installation anaconda qui simplifie l'installation, vous pouvez l'utiliser comme décrit ici.

https://anaconda.org/roebel/py_find_1st

0
A Roebel