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 aa
cui 5
viene 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 aa
cui 5
viene superato il valore .
Risposte:
Questo è un po 'più veloce (e sembra più bello)
np.argmax(aa>5)
Poiché argmax
si 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
argmax
non sembra fermarsi al primo True
. (Questo può essere testato creando array booleani con un singolo True
in posizioni diverse.) La velocità è probabilmente spiegata dal fatto che argmax
non è necessario creare un elenco di output.
argmax
.
aa
è ordinato, come nella risposta di @Michael).
argmax
array booleani da 10 milioni di elementi con un singolo True
in posizioni diverse usando NumPy 1.11.2 e la posizione True
dell'argomento. Quindi 1.11.2 argmax
sembra "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
+1
connp.searchsorted(..., side='right')
side
argomento 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.
side
ha 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/2
non è presente aa
.
N/2
non è 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 N
essere dispari (e N/2.0
forzare 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.searchsorted
non è 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 range
o 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 searchsorted
e numba
restituire un indice che non è un indice valido per l'array.
Le funzioni where
, min
, nonzero
e calculate
generano un'eccezione. Tuttavia, solo l'eccezione per calculate
effettivamente 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 searchsorted
opzioni 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.