Ho un array 1D in numpy e voglio trovare la posizione dell'indice in cui un valore supera il valore in array numpy.
Per esempio
aa = range(-10,10)
Trova la posizione in aacui 5viene superato il valore .
Ho un array 1D in numpy e voglio trovare la posizione dell'indice in cui un valore supera il valore in array numpy.
Per esempio
aa = range(-10,10)
Trova la posizione in aacui 5viene superato il valore .
Risposte:
Questo è un po 'più veloce (e sembra più bello)
np.argmax(aa>5)
Poiché argmaxsi fermerà al primo True("In caso di più occorrenze dei valori massimi, vengono restituiti gli indici corrispondenti alla prima occorrenza") e non salva un altro elenco.
In [2]: N = 10000
In [3]: aa = np.arange(-N,N)
In [4]: timeit np.argmax(aa>N/2)
100000 loops, best of 3: 52.3 us per loop
In [5]: timeit np.where(aa>N/2)[0][0]
10000 loops, best of 3: 141 us per loop
In [6]: timeit np.nonzero(aa>N/2)[0][0]
10000 loops, best of 3: 142 us per loop
argmaxnon sembra fermarsi al primo True. (Questo può essere testato creando array booleani con un singolo Truein posizioni diverse.) La velocità è probabilmente spiegata dal fatto che argmaxnon è necessario creare un elenco di output.
argmax.
aaè ordinato, come nella risposta di @Michael).
argmaxarray booleani da 10 milioni di elementi con un singolo Truein posizioni diverse usando NumPy 1.11.2 e la posizione Truedell'argomento. Quindi 1.11.2 argmaxsembra "cortocircuitare" su array booleani.
dato il contenuto ordinato dell'array, esiste un metodo ancora più veloce: searchsorted .
import time
N = 10000
aa = np.arange(-N,N)
%timeit np.searchsorted(aa, N/2)+1
%timeit np.argmax(aa>N/2)
%timeit np.where(aa>N/2)[0][0]
%timeit np.nonzero(aa>N/2)[0][0]
# Output
100000 loops, best of 3: 5.97 µs per loop
10000 loops, best of 3: 46.3 µs per loop
10000 loops, best of 3: 154 µs per loop
10000 loops, best of 3: 154 µs per loop
+1connp.searchsorted(..., side='right')
sideargomento faccia la differenza solo se ci sono valori ripetuti nella matrice ordinata. Non cambia il significato dell'indice restituito, che è sempre l'indice in cui è possibile inserire il valore della query, spostando tutte le voci seguenti a destra e mantenendo un array ordinato.
sideha effetto quando lo stesso valore si trova sia nell'array ordinato che in quello inserito, indipendentemente dai valori ripetuti in entrambi. I valori ripetuti nella matrice ordinata esagerano semplicemente l'effetto (la differenza tra i lati è il numero di volte in cui il valore che viene inserito appare nella matrice ordinata). side non cambiare il significato dell'indice restituito, anche se non cambia la matrice risultante dall'inserimento i valori nella matrice ordinata a tali indici. Una distinzione sottile ma importante; infatti questa risposta fornisce l'indice sbagliato se N/2non è presente aa.
N/2non è presente aa. La forma corretta sarebbe np.searchsorted(aa, N/2, side='right')(senza il +1). Entrambi i moduli danno lo stesso indice in caso contrario. Considera il caso test di Nessere dispari (e N/2.0forzare il float se usi python 2).
Mi interessava anche questo e ho confrontato tutte le risposte suggerite con perfplot . (Dichiarazione di non responsabilità: sono l'autore di perfplot.)
Se sai che l'array che stai guardando è già ordinato , allora
numpy.searchsorted(a, alpha)
è per te. È un'operazione a tempo costante, ovvero la velocità non dipende dalla dimensione dell'array. Non puoi andare più veloce di così.
Se non sai nulla del tuo array, non sbaglierai
numpy.argmax(a > alpha)
Già ordinato:
indifferenziati:
Codice per riprodurre la trama:
import numpy
import perfplot
alpha = 0.5
def argmax(data):
return numpy.argmax(data > alpha)
def where(data):
return numpy.where(data > alpha)[0][0]
def nonzero(data):
return numpy.nonzero(data > alpha)[0][0]
def searchsorted(data):
return numpy.searchsorted(data, alpha)
out = perfplot.show(
# setup=numpy.random.rand,
setup=lambda n: numpy.sort(numpy.random.rand(n)),
kernels=[
argmax, where,
nonzero,
searchsorted
],
n_range=[2**k for k in range(2, 20)],
logx=True,
logy=True,
xlabel='len(array)'
)
np.searchsortednon è un tempo costante. In realtà lo è O(log(n)). Ma il tuo caso di test in realtà confronta il caso migliore di searchsorted(che è O(1)).
searchsorted(o qualsiasi algoritmo) di battere O(log(n))di una ricerca binaria per dati distribuiti uniformemente ordinati. EDIT: searchsorted è una ricerca binaria.
In caso di uno rangeo qualsiasi altro array che aumenta in modo lineare, è possibile semplicemente calcolare l'indice a livello di codice, senza che sia necessario eseguire l'iterazione dell'array:
def first_index_calculate_range_like(val, arr):
if len(arr) == 0:
raise ValueError('no value greater than {}'.format(val))
elif len(arr) == 1:
if arr[0] > val:
return 0
else:
raise ValueError('no value greater than {}'.format(val))
first_value = arr[0]
step = arr[1] - first_value
# For linearly decreasing arrays or constant arrays we only need to check
# the first element, because if that does not satisfy the condition
# no other element will.
if step <= 0:
if first_value > val:
return 0
else:
raise ValueError('no value greater than {}'.format(val))
calculated_position = (val - first_value) / step
if calculated_position < 0:
return 0
elif calculated_position > len(arr) - 1:
raise ValueError('no value greater than {}'.format(val))
return int(calculated_position) + 1
Probabilmente si potrebbe migliorare un po '. Mi sono assicurato che funzioni correttamente per alcuni array e valori di esempio, ma ciò non significa che non possano esserci errori, soprattutto considerando che utilizza float ...
>>> import numpy as np
>>> first_index_calculate_range_like(5, np.arange(-10, 10))
16
>>> np.arange(-10, 10)[16] # double check
6
>>> first_index_calculate_range_like(4.8, np.arange(-10, 10))
15
Dato che può calcolare la posizione senza alcuna iterazione, sarà un tempo costante ( O(1)) e probabilmente potrà battere tutti gli altri approcci citati. Tuttavia richiede un passaggio costante nell'array, altrimenti produrrà risultati errati.
Un approccio più generale sarebbe l'utilizzo di una funzione numba:
@nb.njit
def first_index_numba(val, arr):
for idx in range(len(arr)):
if arr[idx] > val:
return idx
return -1
Funzionerà per qualsiasi array ma deve iterare sull'array, quindi nel caso medio sarà O(n):
>>> first_index_numba(4.8, np.arange(-10, 10))
15
>>> first_index_numba(5, np.arange(-10, 10))
16
Anche se Nico Schlömer ha già fornito alcuni parametri di riferimento, ho pensato che potesse essere utile includere le mie nuove soluzioni e testare diversi "valori".
L'impostazione del test:
import numpy as np
import math
import numba as nb
def first_index_using_argmax(val, arr):
return np.argmax(arr > val)
def first_index_using_where(val, arr):
return np.where(arr > val)[0][0]
def first_index_using_nonzero(val, arr):
return np.nonzero(arr > val)[0][0]
def first_index_using_searchsorted(val, arr):
return np.searchsorted(arr, val) + 1
def first_index_using_min(val, arr):
return np.min(np.where(arr > val))
def first_index_calculate_range_like(val, arr):
if len(arr) == 0:
raise ValueError('empty array')
elif len(arr) == 1:
if arr[0] > val:
return 0
else:
raise ValueError('no value greater than {}'.format(val))
first_value = arr[0]
step = arr[1] - first_value
if step <= 0:
if first_value > val:
return 0
else:
raise ValueError('no value greater than {}'.format(val))
calculated_position = (val - first_value) / step
if calculated_position < 0:
return 0
elif calculated_position > len(arr) - 1:
raise ValueError('no value greater than {}'.format(val))
return int(calculated_position) + 1
@nb.njit
def first_index_numba(val, arr):
for idx in range(len(arr)):
if arr[idx] > val:
return idx
return -1
funcs = [
first_index_using_argmax,
first_index_using_min,
first_index_using_nonzero,
first_index_calculate_range_like,
first_index_numba,
first_index_using_searchsorted,
first_index_using_where
]
from simple_benchmark import benchmark, MultiArgument
e i grafici sono stati generati usando:
%matplotlib notebook
b.plot()
b = benchmark(
funcs,
{2**i: MultiArgument([0, np.arange(2**i)]) for i in range(2, 20)},
argument_name="array size")
La funzione numba esegue al meglio seguita dalla funzione calcola e dalla funzione di ricerca. Le altre soluzioni funzionano molto peggio.
b = benchmark(
funcs,
{2**i: MultiArgument([2**i-2, np.arange(2**i)]) for i in range(2, 20)},
argument_name="array size")
Per array di piccole dimensioni la funzione numba si comporta in modo sorprendentemente veloce, tuttavia per array di dimensioni maggiori è sovraperformata dalla funzione di calcolo e dalla funzione di ricerca.
b = benchmark(
funcs,
{2**i: MultiArgument([np.sqrt(2**i), np.arange(2**i)]) for i in range(2, 20)},
argument_name="array size")
Questo è più interessante Anche in questo caso numba e la funzione di calcolo funzionano alla grande, tuttavia questo in realtà sta innescando il caso peggiore di ricerche che in questo caso in realtà non funziona bene.
Un altro punto interessante è come si comportano queste funzioni se non esiste alcun valore il cui indice deve essere restituito:
arr = np.ones(100)
value = 2
for func in funcs:
print(func.__name__)
try:
print('-->', func(value, arr))
except Exception as e:
print('-->', e)
Con questo risultato:
first_index_using_argmax
--> 0
first_index_using_min
--> zero-size array to reduction operation minimum which has no identity
first_index_using_nonzero
--> index 0 is out of bounds for axis 0 with size 0
first_index_calculate_range_like
--> no value greater than 2
first_index_numba
--> -1
first_index_using_searchsorted
--> 101
first_index_using_where
--> index 0 is out of bounds for axis 0 with size 0
Searchsorted, argmax e numba restituiscono semplicemente un valore errato. Tuttavia searchsortede numbarestituire un indice che non è un indice valido per l'array.
Le funzioni where, min, nonzeroe calculategenerano un'eccezione. Tuttavia, solo l'eccezione per calculateeffettivamente dice qualcosa di utile.
Ciò significa che uno deve effettivamente avvolgere queste chiamate in una funzione wrapper appropriata che rileva eccezioni o valori di ritorno non validi e gestisce in modo appropriato, almeno se non si è sicuri che il valore possa essere nell'array.
Nota: il calcolo e le searchsortedopzioni funzionano solo in condizioni speciali. La funzione "calcola" richiede un passaggio costante e la ricerca ordinata richiede che l'array sia ordinato. Quindi questi potrebbero essere utili nelle giuste circostanze ma non sono soluzioni generali per questo problema. Nel caso in cui hai a che fare con ordinati liste Python si potrebbe desiderare di dare un'occhiata al bisect modulo invece di utilizzare Numpys searchsorted.