Uno debería tener claro si no podría haber una solución (ya que, por ejemplo, la respuesta argmax no funcionará en ese caso (máx. De (0,0,0,0) = 0) como comentó
ambrus
Respuestas:
199
Esto es un poco más rápido (y se ve mejor)
np.argmax(aa>5)
Dado que argmaxse 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 =10000In[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
Solo una advertencia: si no hay un valor Verdadero en su matriz de entrada, np.argmax devolverá felizmente 0 (que no es lo que desea en este caso).
emboscada
8
Los resultados son correctos, pero la explicación me parece un poco sospechosa. argmaxno parece detenerse al principio True. (Esto se puede probar creando matrices booleanas con una sola Trueen diferentes posiciones). La velocidad probablemente se explica por el hecho de que argmaxno es necesario crear una lista de salida.
DrV
1
Creo que tienes razón, @DrV. Mi explicación estaba destinada a explicar por qué da el resultado correcto a pesar de que la intención original no busca realmente un máximo, no por qué es más rápido, ya que no puedo afirmar que entiendo los detalles internos argmax.
askewchan
1
@ George, me temo que no sé por qué exactamente. Solo puedo decir que es más rápido en el ejemplo particular que mostré, por lo que no lo consideraría generalmente más rápido sin (i) saber por qué es (ver el comentario de @ DrV) o (ii) probar más casos (por ejemplo, si aaestá ordenado, como en la respuesta de @ Michael).
askewchan
3
@DrV, acabo de ejecutar argmaxen matrices booleanas de 10 millones de elementos con una sola Trueen diferentes posiciones usando NumPy 1.11.2, y la posición de lo Trueimportado. Entonces, 1.11.2 argmaxparece "cortocircuitar" en matrices booleanas.
Ulrich Stern
96
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]# Output100000 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
Esta es realmente la mejor respuesta, suponiendo que la matriz esté ordenada (que en realidad no se especifica en la pregunta). Puede evitar lo incómodo +1connp.searchsorted(..., side='right')
askewchan
3
Creo que el sideargumento 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.
Gus
@Gus, sidetiene 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). sideno 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/2no está en aa.
askewchan
Como se insinuó en el comentario anterior, esta respuesta está desactivada por uno si N/2no 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 Nser impar (y N/2.0forzar la flotación si usa Python 2).
askewchan
21
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
np.searchsortedNo 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)).
MSeifert
@MSeifert ¿Qué tipo de matriz de entrada / alfa necesita para ver O (log (n))?
Nico Schlömer
1
Obtener el elemento en el índice sqrt (longitud) condujo a un rendimiento muy malo. También escribí una respuesta aquí que incluye ese punto de referencia.
MSeifert
Dudo searchsorted(o cualquier algoritmo) puede superar la O(log(n))búsqueda binaria de datos ordenados distribuidos uniformemente. EDITAR: searchsortedes una búsqueda 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
Matrices que tienen un paso constante entre elementos
En el caso de una rangeo 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:raiseValueError('no value greater than {}'.format(val))elif len(arr)==1:if arr[0]> val:return0else:raiseValueError('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:return0else:raiseValueError('no value greater than {}'.format(val))
calculated_position =(val - first_value)/ step
if calculated_position <0:return0elif calculated_position > len(arr)-1:raiseValueError('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 ...
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.
Solución general usando numba
Un enfoque más general sería usar una función numba:
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)+1def 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:raiseValueError('empty array')elif len(arr)==1:if arr[0]> val:return0else:raiseValueError('no value greater than {}'.format(val))
first_value = arr[0]
step = arr[1]- first_value
if step <=0:if first_value > val:return0else:raiseValueError('no value greater than {}'.format(val))
calculated_position =(val - first_value)/ step
if calculated_position <0:return0elif calculated_position > len(arr)-1:raiseValueError('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()
el artículo está al principio
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.
el artículo está al final
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.
el artículo está en 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")
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.
Comparación de las funciones cuando ningún valor satisface la condición
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 =2for func in funcs:print(func.__name__)try:print('-->', func(value, arr))exceptExceptionas 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 0is out of bounds for axis 0with 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 0is out of bounds for axis 0with size 0
Searchsorted, argmax y numba simplemente devuelven un valor incorrecto. Sin embargo searchsortedynumba devolver un índice que no es un índice válido para la matriz.
Las funciones where, min, nonzeroy calculatelanzan 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 searchsortedopciones 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.
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 wheredevuelve una matriz vacía).
Respuestas:
Esto es un poco más rápido (y se ve mejor)
Dado que
argmax
se detendrá en la primeraTrue
("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.fuente
argmax
no parece detenerse al principioTrue
. (Esto se puede probar creando matrices booleanas con una solaTrue
en diferentes posiciones). La velocidad probablemente se explica por el hecho de queargmax
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 solaTrue
en diferentes posiciones usando NumPy 1.11.2, y la posición de loTrue
importado. Entonces, 1.11.2argmax
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 .
fuente
+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 siN/2
no está enaa
.N/2
no está enaa
. La forma correcta seríanp.searchsorted(aa, N/2, side='right')
(sin el+1
). Ambas formas dan el mismo índice de lo contrario. Considere el caso de prueba deN
ser impar (yN/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
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
Ya ordenado:
Sin clasificar:
Código para reproducir la trama:
fuente
np.searchsorted
No es tiempo constante. En realidad esO(log(n))
. Pero su caso de prueba realmente compara el mejor de los casossearchsorted
(que esO(1)
).searchsorted
(o cualquier algoritmo) puede superar laO(log(n))
búsqueda binaria de datos ordenados distribuidos uniformemente. EDITAR:searchsorted
es una búsqueda binaria.fuente
Matrices que tienen un paso constante entre elementos
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: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 ...
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.Solución general usando numba
Un enfoque más general sería usar una función numba:
Eso funcionará para cualquier matriz, pero tiene que iterar sobre la matriz, por lo que en el caso promedio será
O(n)
:Punto de referencia
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:
y las parcelas se generaron usando:
el artículo está al principio
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.
el artículo está al final
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.
el artículo está en sqrt (len)
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.
Comparación de las funciones cuando ningún valor satisface la condición
Otro punto interesante es cómo se comportan estas funciones si no hay ningún valor cuyo índice deba devolverse:
Con este resultado:
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
ycalculate
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.fuente
Me gustaría proponer
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).fuente
Yo iria con
donde
V
es vector (matriz 1d),x
es el valor yi
es el índice resultante.fuente