Tengo una matriz 1D en numpy y quiero encontrar la posición del índice donde un valor excede el valor en una matriz numpy.
P.ej
aa = range(-10,10)
Encuentre la posición en aa
donde 5
se excede el valor .
Tengo una matriz 1D en numpy y quiero encontrar la posición del índice donde un valor excede el valor en una matriz numpy.
P.ej
aa = range(-10,10)
Encuentre la posición en aa
donde 5
se excede el valor .
Esto es un poco más rápido (y se ve mejor)
np.argmax(aa>5)
Dado que argmax
se detendrá en la primera True
("En caso de que aparezcan múltiples valores máximos, se devuelven los índices correspondientes a la primera aparición") y no guarda otra lista.
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
no parece detenerse al principio True
. (Esto se puede probar creando matrices booleanas con una sola True
en diferentes posiciones). La velocidad probablemente se explica por el hecho de que argmax
no es necesario crear una lista de salida.
argmax
.
aa
está ordenado, como en la respuesta de @ Michael).
argmax
en matrices booleanas de 10 millones de elementos con una sola True
en diferentes posiciones usando NumPy 1.11.2, y la posición de lo True
importado. Entonces, 1.11.2 argmax
parece "cortocircuitar" en matrices booleanas.
Dado el contenido ordenado de su matriz, existe un método aún más rápido: ordenado por búsqueda .
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
argumento solo hace una diferencia si hay valores repetidos en la matriz ordenada. No cambia el significado del índice devuelto, que siempre es el índice en el que puede insertar el valor de la consulta, desplazando todas las siguientes entradas a la derecha y mantiene una matriz ordenada.
side
tiene un efecto cuando el mismo valor está tanto en la matriz ordenada como en la insertada, independientemente de los valores repetidos en ambas. Los valores repetidos en la matriz ordenada simplemente exageran el efecto (la diferencia entre los lados es la cantidad de veces que el valor que se inserta aparece en la matriz ordenada). side
no cambiar el significado del índice de regresar, a pesar de que no cambia la matriz resultante de la inserción de los valores en la matriz ordenada en esos índices. Una distinción sutil pero importante; De hecho, esta respuesta da el índice incorrecto si N/2
no está en aa
.
N/2
no está en aa
. La forma correcta sería np.searchsorted(aa, N/2, side='right')
(sin el +1
). Ambas formas dan el mismo índice de lo contrario. Considere el caso de prueba de N
ser impar (y N/2.0
forzar la flotación si usa Python 2).
También estaba interesado en esto y comparé todas las respuestas sugeridas con perfplot . (Descargo de responsabilidad: soy el autor de perfplot).
Si sabe que la matriz que está buscando ya está ordenada , entonces
numpy.searchsorted(a, alpha)
es para ti. Es una operación de tiempo constante, es decir, la velocidad no depende del tamaño de la matriz. Tú no puedes ser más rápido que eso.
Si no sabes nada sobre tu matriz, no te equivocarás con
numpy.argmax(a > alpha)
Ya ordenado:
Sin clasificar:
Código para reproducir 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
No es tiempo constante. En realidad es O(log(n))
. Pero su caso de prueba realmente compara el mejor de los casos searchsorted
(que es O(1)
).
searchsorted
(o cualquier algoritmo) puede superar la O(log(n))
búsqueda binaria de datos ordenados distribuidos uniformemente. EDITAR: searchsorted
es una búsqueda binaria.
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
En el caso de una range
o cualquier otra matriz que aumente linealmente, simplemente puede calcular el índice mediante programación, sin necesidad de iterar sobre la matriz:
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
Uno probablemente podría mejorar eso un poco. Me he asegurado de que funcione correctamente para algunas matrices y valores de muestra, pero eso no significa que no pueda haber errores allí, especialmente teniendo en cuenta que usa flotantes ...
>>> 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
Dado que puede calcular la posición sin ninguna iteración, será un tiempo constante ( O(1)
) y probablemente pueda vencer a todos los otros enfoques mencionados. Sin embargo, requiere un paso constante en la matriz, de lo contrario producirá resultados incorrectos.
Un enfoque más general sería usar una función numba:
@nb.njit
def first_index_numba(val, arr):
for idx in range(len(arr)):
if arr[idx] > val:
return idx
return -1
Eso funcionará para cualquier matriz, pero tiene que iterar sobre la matriz, por lo que en el caso promedio será O(n)
:
>>> first_index_numba(4.8, np.arange(-10, 10))
15
>>> first_index_numba(5, np.arange(-10, 10))
16
Aunque Nico Schlömer ya proporcionó algunos puntos de referencia, pensé que podría ser útil incluir mis nuevas soluciones y probar diferentes "valores".
La configuración de prueba:
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
y las parcelas se generaron 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 función numba funciona mejor seguida de la función de cálculo y la función de clasificación de búsqueda. Las otras soluciones funcionan mucho peor.
b = benchmark(
funcs,
{2**i: MultiArgument([2**i-2, np.arange(2**i)]) for i in range(2, 20)},
argument_name="array size")
Para las matrices pequeñas, la función numba funciona increíblemente rápido, sin embargo, para las matrices más grandes, la función de cálculo y la función ordenada de búsqueda la superan.
b = benchmark(
funcs,
{2**i: MultiArgument([np.sqrt(2**i), np.arange(2**i)]) for i in range(2, 20)},
argument_name="array size")
Esto es mas interesante. Nuevamente, numba y la función de cálculo funcionan muy bien, sin embargo, esto en realidad está desencadenando el peor caso de búsqueda ordenada, que realmente no funciona bien en este caso.
Otro punto interesante es cómo se comportan estas funciones si no hay ningún valor cuyo índice deba devolverse:
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 este resultado:
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 y numba simplemente devuelven un valor incorrecto. Sin embargo searchsorted
ynumba
devolver un índice que no es un índice válido para la matriz.
Las funciones where
, min
, nonzero
y calculate
lanzan una excepción. Sin embargo, solo la excepción paracalculate
realmente dice algo útil.
Eso significa que uno realmente tiene que ajustar estas llamadas en una función de contenedor apropiada que capture excepciones o valores de retorno no válidos y manejar adecuadamente, al menos si no está seguro de si el valor podría estar en la matriz.
Nota: El cálculo y las searchsorted
opciones solo funcionan en condiciones especiales. La función "calcular" requiere un paso constante y la búsqueda ordenada requiere que se ordene la matriz. Por lo tanto, estos podrían ser útiles en las circunstancias correctas, pero no son soluciones generales para este problema. En caso de que estés lidiando con ordenados listas de Python es posible que desee echar un vistazo a la bisect módulo en lugar de utilizar Numpys searchsorted.
Me gustaría proponer
np.min(np.append(np.where(aa>5)[0],np.inf))
Esto devolverá el índice más pequeño donde se cumple la condición, mientras que devuelve el infinito si la condición nunca se cumple (y where
devuelve una matriz vacía).
Yo iria con
i = np.min(np.where(V >= x))
donde V
es vector (matriz 1d), x
es el valor y i
es el índice resultante.