web-dev-qa-db-fra.com

Numpy première occurrence de valeur supérieure à la valeur existante

J'ai un tableau 1D dans numpy et je veux trouver la position de l'index où une valeur dépasse la valeur dans le tableau numpy.

Par exemple.

aa = range(-10,10)

Trouver la position dans aa où la valeur 5 est dépassée.

115
user308827

C'est un peu plus rapide (et plus joli)

np.argmax(aa>5)

Puisque argmax s'arrête au premier True ("En cas d'occurrences multiples des valeurs maximales, les indices correspondant à la première occurrence sont renvoyés.") et ne sauve pas une autre 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

étant donné le contenu trié de votre tableau, il existe une méthode encore plus rapide: 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

Cela m'a aussi intéressé et j'ai comparé toutes les réponses suggérées avec perfplot . (Avertissement: je suis l'auteur de perfplot.)

Si vous savez que le tableau que vous regardez est déjà trié , alors

numpy.searchsorted(a, alpha)

est pour toi. C’est une opération à temps constant, c’est-à-dire que la vitesse ne dépend pas de la taille du tableau. Vous ne pouvez pas aller plus vite que ça.

Si vous ne savez rien de votre tableau, vous ne vous trompez pas

numpy.argmax(a > alpha)

Déjà trié:

enter image description here

Non trié:

enter image description here

Code pour reproduire l'intrigue:

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

Tableaux qui ont un pas constant entre les éléments

Dans le cas d'un range ou de tout autre tableau à croissance linéaire, vous pouvez simplement calculer l'index par programmation, sans avoir à itérer du tout sur le tableau:

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

On pourrait probablement améliorer cela un peu. Je me suis assuré qu'il fonctionne correctement pour quelques exemples de tableaux et de valeurs, mais cela ne signifie pas qu'il ne puisse pas y avoir d'erreur, en particulier étant donné qu'il utilise des flottants ...

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

Etant donné qu'il peut calculer la position sans aucune itération, ce sera un temps constant (O(1)) et peut probablement battre toutes les autres approches mentionnées. Cependant, cela nécessite une étape constante dans le tableau, sinon cela produira des résultats erronés.

Solution générale en utilisant numba

Une approche plus générale consisterait à utiliser une fonction numba:

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

Cela fonctionnera pour n'importe quel tableau, mais il devra parcourir le tableau, donc dans le cas moyen, il s'agira de O(n):

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

Référence

Même si Nico Schlömer a déjà fourni quelques points de repère, j’ai pensé qu’il pourrait être utile d’inclure mes nouvelles solutions et de tester différentes "valeurs".

La configuration du 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

et les parcelles ont été générées en utilisant:

%matplotlib notebook
b.plot()

l'article est au début

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

La fonction numba fonctionne mieux, suivie de la fonction calculer et de la fonction tri par recherche. Les autres solutions fonctionnent beaucoup moins bien.

l'article est à la fin

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

Pour les petits tableaux, la fonction numba est incroyablement rapide, mais pour les grands tableaux, elle est surperformée par la fonction de calcul et la fonction searchsorted.

l'article est à 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

C'est plus intéressant. Encore une fois, numba et la fonction de calcul fonctionnent bien, mais cela déclenche en fait le pire cas de tri sélectif qui ne fonctionne vraiment pas bien dans ce cas.

Comparaison des fonctions quand aucune valeur ne satisfait à la condition

Un autre point intéressant est le comportement de ces fonctions s’il n’ya pas de valeur dont l’index doit être retourné:

arr = np.ones(100)
value = 2

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

Avec ce résultat:

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 et numba renvoient simplement une valeur incorrecte. Cependant, searchsorted et numba renvoient un index qui n'est pas un index valide pour le tableau.

Les fonctions where, min, nonzero et calculate lèvent une exception. Cependant, seule l'exception pour calculate dit quelque chose d'utile.

Cela signifie que vous devez en fait encapsuler ces appels dans une fonction d'encapsulage appropriée qui capture les exceptions ou les valeurs de retour non valides et les gère correctement, au moins si vous n'êtes pas sûr que la valeur puisse être dans le tableau.


Remarque: les options de calcul et searchsorted ne fonctionnent que dans des conditions spéciales. La fonction "calculer" nécessite une étape constante et la recherche triée requiert le tri du tableau. Celles-ci pourraient donc être utiles dans les bonnes circonstances mais ne sont pas des solutions générales à ce problème. Si vous avez affaire à des listes triées Python, jetez un œil au module bisect au lieu d'utiliser Numpys. recherche triés.

5
MSeifert

Je voudrais proposer

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

Cela retournera le plus petit index où la condition est remplie, tout en renvoyant l'infini si la condition n'est jamais remplie (et where renvoie un tableau vide).

3
mfeldt

J'irais avec

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

V est un vecteur (tableau 1d), x est la valeur et i est l'indice obtenu.

1
sivic