Numpy prima occorrenza di valore maggiore del valore esistente


144

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 .


2
Uno dovrebbe essere chiaro se non ci potrebbe essere una soluzione (dal momento che ad esempio la risposta argmax non funzionerà in quel caso (max di (0,0,0,0) = 0) come ha commentato
Ambrus

Risposte:


199

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

103
Solo un avvertimento: se non c'è un valore True nel suo array di input, np.argmax restituirà felicemente 0 (che non è quello che vuoi in questo caso).
ambrus,

8
I risultati sono corretti, ma trovo la spiegazione un po 'sospetta. 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.
DrV,

1
Penso che tu abbia ragione, @DrV. La mia spiegazione doveva essere sul motivo per cui dà il risultato corretto nonostante l'intento originale non sia effettivamente alla ricerca del massimo, non perché sia ​​più veloce in quanto non posso pretendere di capire i dettagli interni di argmax.
askewchan,

1
@ George, temo di non sapere esattamente il perché. Posso solo dire che è più veloce nell'esempio particolare che ho mostrato, quindi non lo considererei generalmente più veloce senza (i) sapere perché (vedi il commento di @ DrV) o (ii) testare più casi (ad esempio, se aaè ordinato, come nella risposta di @Michael).
askewchan,

3
@DrV, ho appena eseguito 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.
Ulrich Stern,

96

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

19
Questa è davvero la risposta migliore supponendo che l'array sia ordinato (che non è in realtà specificato nella domanda). Puoi evitare l'imbarazzo +1connp.searchsorted(..., side='right')
askewchan,

3
Penso che l' 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.
Gus

@Gus, 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.
askewchan,

Come accennato nel commento sopra, questa risposta è disattivata da una se 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).
askewchan,

21

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:

inserisci qui la descrizione dell'immagine

indifferenziati:

inserisci qui la descrizione dell'immagine

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

4
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)).
MSeifert,

@MSeifert Che tipo di array di input / alpha hai bisogno di vedere O (log (n))?
Nico Schlömer,

1
Ottenere l'articolo a indice sqrt (lunghezza) ha portato a prestazioni pessime. Ho anche scritto una risposta qui includendo quel benchmark.
MSeifert,

Dubito searchsorted(o qualsiasi algoritmo) di battere O(log(n))di una ricerca binaria per dati distribuiti uniformemente ordinati. EDIT: searchsorted è una ricerca binaria.
Mateen Ulhaq,

16
In [34]: a=np.arange(-10,10)

In [35]: a
Out[35]:
array([-10,  -9,  -8,  -7,  -6,  -5,  -4,  -3,  -2,  -1,   0,   1,   2,
         3,   4,   5,   6,   7,   8,   9])

In [36]: np.where(a>5)
Out[36]: (array([16, 17, 18, 19]),)

In [37]: np.where(a>5)[0][0]
Out[37]: 16

8

Matrici che hanno un passaggio costante tra gli elementi

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.

Soluzione generale usando numba

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

Prova delle prestazioni

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

l'oggetto è all'inizio

b = benchmark(
    funcs,
    {2**i: MultiArgument([0, np.arange(2**i)]) for i in range(2, 20)},
    argument_name="array size")

inserisci qui la descrizione dell'immagine

La funzione numba esegue al meglio seguita dalla funzione calcola e dalla funzione di ricerca. Le altre soluzioni funzionano molto peggio.

l'oggetto è alla fine

b = benchmark(
    funcs,
    {2**i: MultiArgument([2**i-2, np.arange(2**i)]) for i in range(2, 20)},
    argument_name="array size")

inserisci qui la descrizione dell'immagine

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.

l'articolo è a sqrt (len)

b = benchmark(
    funcs,
    {2**i: MultiArgument([np.sqrt(2**i), np.arange(2**i)]) for i in range(2, 20)},
    argument_name="array size")

inserisci qui la descrizione dell'immagine

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.

Confronto delle funzioni quando nessun valore soddisfa la condizione

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.


3

Mi piacerebbe proporti

np.min(np.append(np.where(aa>5)[0],np.inf))

Ciò restituirà l'indice più piccolo in cui viene soddisfatta la condizione, restituendo l'infinito se la condizione non viene mai soddisfatta (e whererestituisce un array vuoto).


1

Vorrei andare con

i = np.min(np.where(V >= x))

dove Vè il vettore (1d array), xè il valore e il'indice risultante.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.