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.
Il existe une demande de fonctionnalité pour cette opération prévue pour Numpy 2.0.0: https://github.com/numpy/numpy/issues/2269
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 ReilinkLes 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.
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)
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
En cas de tableaux triés, np.searchsorted
fonctionne.
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
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.
@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
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.
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.
que dis-tu de ça
import numpy as np
np.amin(np.where(array==item))
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.
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.