it-swarm.com.de

Numpy erstes Auftreten eines Werts, der größer als der vorhandene Wert ist

Ich habe ein 1D-Array in Numpy und möchte die Position des Index ermitteln, an der ein Wert den Wert in Numpy-Array überschreitet.

Z.B.

aa = range(-10,10)

Suchen Sie die Position in aa, wobei der Wert 5 wird überschritten.

115
user308827

Das ist etwas schneller (und sieht schöner aus)

np.argmax(aa>5)

Da argmax am ersten True anhält ("Bei mehrfachem Auftreten der Maximalwerte werden die dem ersten Auftreten entsprechenden Indizes zurückgegeben.") Und speichert keine andere Liste.

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
154
askewchan

angesichts des sortierten Inhalts Ihres Arrays gibt es eine noch schnellere Methode: 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
79
MichaelKaisers
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
16
Moj

Das hat mich auch interessiert und ich habe alle vorgeschlagenen Antworten mit perfplot verglichen. (Haftungsausschluss: Ich bin der Autor von perfplot.)

Wenn Sie wissen, dass das von Ihnen durchsuchte Array bereits sortiert ist , dann

numpy.searchsorted(a, alpha)

ist für Sie. Es ist eine Operation mit konstanter Zeit, d. H. Die Geschwindigkeit istnotabhängig von der Größe des Arrays. Schneller geht es nicht.

Wenn Sie nichts über Ihr Array wissen, können Sie nichts falsch machen

numpy.argmax(a > alpha)

Bereits sortiert:

enter image description here

Unsortiert:

enter image description here

Code zum Reproduzieren des Plots:

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)'
    )
13
Nico Schlömer

Arrays, die einen konstanten Schritt zwischen Elementen haben

Im Falle eines range oder eines anderen linear ansteigenden Arrays können Sie den Index einfach programmgesteuert berechnen, ohne das Array durchlaufen zu müssen:

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

Das könnte man wohl ein bisschen verbessern. Ich habe dafür gesorgt, dass es für einige Sample-Arrays und -Werte korrekt funktioniert, aber das heißt nicht, dass darin keine Fehler enthalten sein könnten, besonders wenn man bedenkt, dass es Floats verwendet ...

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

Da es die Position ohne Iteration berechnen kann, ist es eine konstante Zeit (O(1)) und kann wahrscheinlich alle anderen genannten Ansätze übertreffen. Es ist jedoch ein konstanter Schritt im Array erforderlich, da sonst falsche Ergebnisse erzielt werden.

Allgemeine Lösung mit numba

Ein allgemeinerer Ansatz wäre die Verwendung einer Numba-Funktion:

@nb.njit
def first_index_numba(val, arr):
    for idx in range(len(arr)):
        if arr[idx] > val:
            return idx
    return -1

Das funktioniert für jedes Array, muss aber über das Array iterieren. Im Durchschnitt ist es also O(n):

>>> first_index_numba(4.8, np.arange(-10, 10))
15
>>> first_index_numba(5, np.arange(-10, 10))
16

Benchmark

Obwohl Nico Schlömer bereits einige Benchmarks lieferte, hielt ich es für sinnvoll, meine neuen Lösungen einzubeziehen und auf unterschiedliche "Werte" zu testen.

Der Testaufbau:

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

und die Plots wurden erstellt mit:

%matplotlib notebook
b.plot()

artikel ist am Anfang

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

enter image description here

Die numba-Funktion bietet die beste Leistung, gefolgt von der berechnenden und der suchsortierten Funktion. Die anderen Lösungen schneiden viel schlechter ab.

artikel ist am Ende

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

enter image description here

Bei kleinen Arrays arbeitet die Numba-Funktion erstaunlich schnell, bei größeren Arrays wird sie jedoch von der Berechnungsfunktion und der suchsortierten Funktion übertroffen.

artikel ist bei 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")

enter image description here

Das ist interessanter. Auch hier leisten numba und die Berechnungsfunktion eine hervorragende Leistung. Dies löst jedoch den schlimmsten Fall von searchsorted aus, der in diesem Fall nicht gut funktioniert.

Vergleich der Funktionen, wenn kein Wert die Bedingung erfüllt

Ein weiterer interessanter Punkt ist, wie sich diese Funktionen verhalten, wenn es keinen Wert gibt, dessen Index zurückgegeben werden soll:

arr = np.ones(100)
value = 2

for func in funcs:
    print(func.__name__)
    try:
        print('-->', func(value, arr))
    except Exception as e:
        print('-->', e)

Mit diesem Ergebnis:

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 und numba geben einfach einen falschen Wert zurück. searchsorted und numba geben jedoch einen Index zurück, der kein gültiger Index für das Array ist.

Die Funktionen where, min, nonzero und calculate lösen eine Ausnahme aus. Tatsächlich sagt jedoch nur die Ausnahme für calculate etwas Hilfreiches aus.

Das bedeutet, dass man diese Aufrufe tatsächlich in eine entsprechende Wrapper-Funktion einbinden muss, die Ausnahmen oder ungültige Rückgabewerte abfängt und entsprechend behandelt, zumindest wenn Sie nicht sicher sind, ob der Wert im Array enthalten sein könnte.


Hinweis: Die Optionen berechne und searchsorted funktionieren nur unter besonderen Bedingungen. Für die Funktion "Berechnen" ist ein konstanter Schritt erforderlich, und für die Suche nach "Sortieren" muss das Array sortiert werden. Diese könnten unter den richtigen Umständen nützlich sein, sind jedoch keine allgemeinen Lösungen für dieses Problem. Falls es sich um sortiert Python - Listen handelt, sollten Sie sich das bisect -Modul ansehen, anstatt Numpys zu verwenden suchsortiert.

5
MSeifert

Ich würde gerne vorschlagen

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

Dies gibt den kleinsten Index zurück, in dem die Bedingung erfüllt ist, während unendlich zurückgegeben wird, wenn die Bedingung nie erfüllt ist (und where gibt ein leeres Array zurück).

3
mfeldt

Ich würde mitgehen

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

dabei ist V der Vektor (1d-Array), x der Wert und i der resultierende Index.

1
sivic