Um deve estar claro se poderia haver nenhuma solução (já que por exemplo a resposta argmax não vai funcionar nesse caso (máximo de (0,0,0,0) = 0) como Ambrus comentou
seanv507
Respostas:
199
Isso é um pouco mais rápido (e parece melhor)
np.argmax(aa>5)
Desde argmaxque parará na primeira True("No caso de várias ocorrências dos valores máximos, os índices correspondentes à primeira ocorrência serão retornados.") E não salva outra 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
Apenas uma palavra de cautela: se não houver valor True em sua matriz de entrada, o np.argmax retornará 0 com prazer (o que não é o que você deseja neste caso).
7602 ambrus
8
Os resultados estão corretos, mas acho a explicação um pouco suspeita. argmaxparece não parar no começo True. (Isso pode ser testado criando matrizes booleanas com uma única Trueem posições diferentes.) A velocidade é provavelmente explicada pelo fato de que argmaxnão é necessário criar uma lista de saída.
DrV
1
Acho que você está certo, @DrV. Minha explicação deveria ser sobre por que ele dá o resultado correto, apesar da intenção original de não buscar o máximo, e não por que é mais rápido, pois não posso afirmar que compreendo os detalhes internos de argmax.
askewchan
1
@ George, eu tenho medo, eu não sei exatamente por que. Só posso dizer que é mais rápido no exemplo específico que mostrei, por isso não o consideraria geralmente mais rápido sem (i) saber por que é (consulte o comentário do @ DrV) ou (ii) testar mais casos (por exemplo, se aaestá classificado, como na resposta de @ Michael).
askewchan
3
@DrV, eu acabei de rodar argmaxem arrays booleanos de 10 milhões de elementos com um único Trueem diferentes posições usando o NumPy 1.11.2 e a posição do que Trueimportava. Portanto, o 1.11.2 argmaxparece "curto-circuito" em matrizes booleanas.
Ulrich Stern
96
dado o conteúdo classificado da sua matriz, existe um método ainda mais rápido: a classificação da pesquisa .
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
Essa é realmente a melhor resposta, supondo que a matriz esteja classificada (que não está realmente especificada na pergunta). Você pode evitar o estranho +1comnp.searchsorted(..., side='right')
askewchan
3
Eu acho que o sideargumento só faz diferença se houver valores repetidos na matriz classificada. Ele não altera o significado do índice retornado, que é sempre o índice no qual você pode inserir o valor da consulta, deslocando todas as seguintes entradas para a direita e mantendo uma matriz classificada.
Gus
@Gus, sidetem um efeito quando o mesmo valor é em ambos os classificados e a matriz inserido, independentemente dos valores repetidos em ambos. Os valores repetidos na matriz classificada apenas exageram o efeito (a diferença entre os lados é o número de vezes que o valor que está sendo inserido aparece na matriz classificada). sidefaz mudar o sentido do índice retornado, embora ela não altera a matriz resultante da inserção dos valores para a matriz classificada a esses índices. Uma distinção sutil, mas importante; de fato, esta resposta fornece o índice errado, se N/2não estiver aa.
askewchan
Como sugerido no comentário acima, essa resposta é desativada em um se N/2não estiver aa. A forma correta seria np.searchsorted(aa, N/2, side='right')(sem o +1). Ambas as formas fornecem o mesmo índice caso contrário. Considere o caso de teste Nestranho (e N/2.0forçar a flutuação se estiver usando o python 2).
askewchan
21
Eu também estava interessado nisso e comparei todas as respostas sugeridas com o perfplot . (Aviso: sou o autor do perfplot.)
Se você sabe que a matriz que você está visualizando já está classificada ,
numpy.searchsorted(a, alpha)
é para você. É uma operação de tempo constante, ou seja, a velocidade não depende do tamanho da matriz. Você não pode ficar mais rápido do que isso.
Se você não sabe nada sobre sua matriz, não está errado com
np.searchsortednão é tempo constante. Na verdade é O(log(n)). Mas seu caso de teste realmente benchmarks do melhor caso de searchsorted(o que é O(1)).
MSDIFER #
@MSeifert Que tipo de matriz de entrada / alfa você precisa ver O (log (n))?
Nico Schlömer 19/04/19
1
Obter o item no índice sqrt (comprimento) levou a um desempenho muito ruim. Também escrevi uma resposta aqui, incluindo essa referência.
MSEifert 19/04/19
Duvido que searchsorted(ou qualquer algoritmo) consiga superar O(log(n))uma pesquisa binária de dados classificados uniformemente distribuídos. EDIT: searchsortedé uma pesquisa binária.
Mateen Ulhaq 27/11/18
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
Matrizes que possuem um passo constante entre elementos
No caso de uma rangeou qualquer outra matriz de aumento linear, você pode simplesmente calcular o índice programaticamente, sem necessidade de iterar a 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
Provavelmente, poderia-se melhorar um pouco. Verifiquei se ele funciona corretamente para algumas matrizes e valores de amostra, mas isso não significa que não possam haver erros, especialmente considerando que ele usa flutuadores ...
Dado que ele pode calcular a posição sem nenhuma iteração, será um tempo constante ( O(1)) e provavelmente poderá superar todas as outras abordagens mencionadas. No entanto, requer uma etapa constante na matriz, caso contrário, produzirá resultados errados.
Solução geral usando numba
Uma abordagem mais geral seria usar uma função numba:
Embora Nico Schlömer já tenha fornecido algumas referências, achei que seria útil incluir minhas novas soluções e testar diferentes "valores".
A configuração do teste:
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
e as parcelas foram geradas usando:
%matplotlib notebook
b.plot()
item está no começo
b = benchmark(
funcs,{2**i:MultiArgument([0, np.arange(2**i)])for i in range(2,20)},
argument_name="array size")
A função numba apresenta o melhor desempenho, seguida pela função de cálculo e pela função de busca variada. As outras soluções apresentam desempenho muito pior.
item está no 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 matrizes pequenas, a função numba executa incrivelmente rápido, no entanto, para matrizes maiores, ela é superada pela função de cálculo e pela função de seleção de pesquisa.
item está no 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")
Isso é mais interessante. Novamente, o numba e a função de cálculo têm um ótimo desempenho, no entanto, isso está realmente desencadeando o pior caso de searchsorted, o que realmente não funciona bem nesse caso.
Comparação das funções quando nenhum valor satisfaz a condição
Outro ponto interessante é como essas funções se comportam se não houver valor cujo índice deva ser retornado:
arr = np.ones(100)
value =2for func in funcs:print(func.__name__)try:print('-->', func(value, arr))exceptExceptionas e:print('-->', e)
Com 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 e numba simplesmente retornam um valor errado. No entanto searchsortede numbaretornar um índice que não é um índice válido para a matriz.
As funções where, min, nonzeroe calculatelançar uma exceção. No entanto, apenas a exceção para calculaterealmente diz algo útil.
Isso significa que é necessário agrupar essas chamadas em uma função de wrapper apropriada que captura exceções ou valores de retorno inválidos e manipula-os adequadamente, pelo menos se você não tiver certeza se o valor pode estar na matriz.
Nota: O cálculo e as searchsortedopções funcionam apenas em condições especiais. A função "calcular" requer uma etapa constante e a pesquisa ordenada exige que a matriz seja classificada. Portanto, eles podem ser úteis nas circunstâncias certas, mas não são soluções gerais para esse problema. Caso esteja lidando com listas Python classificadas, você pode dar uma olhada no módulo bisect em vez de usar o Numpys searchsorted.
Isso retornará o menor índice em que a condição for atendida, enquanto retornará o infinito se a condição nunca for atendida (e whereretorna uma matriz vazia).
Respostas:
Isso é um pouco mais rápido (e parece melhor)
Desde
argmax
que parará na primeiraTrue
("No caso de várias ocorrências dos valores máximos, os índices correspondentes à primeira ocorrência serão retornados.") E não salva outra lista.fonte
argmax
parece não parar no começoTrue
. (Isso pode ser testado criando matrizes booleanas com uma únicaTrue
em posições diferentes.) A velocidade é provavelmente explicada pelo fato de queargmax
não é necessário criar uma lista de saída.argmax
.aa
está classificado, como na resposta de @ Michael).argmax
em arrays booleanos de 10 milhões de elementos com um únicoTrue
em diferentes posições usando o NumPy 1.11.2 e a posição do queTrue
importava. Portanto, o 1.11.2argmax
parece "curto-circuito" em matrizes booleanas.dado o conteúdo classificado da sua matriz, existe um método ainda mais rápido: a classificação da pesquisa .
fonte
+1
comnp.searchsorted(..., side='right')
side
argumento só faz diferença se houver valores repetidos na matriz classificada. Ele não altera o significado do índice retornado, que é sempre o índice no qual você pode inserir o valor da consulta, deslocando todas as seguintes entradas para a direita e mantendo uma matriz classificada.side
tem um efeito quando o mesmo valor é em ambos os classificados e a matriz inserido, independentemente dos valores repetidos em ambos. Os valores repetidos na matriz classificada apenas exageram o efeito (a diferença entre os lados é o número de vezes que o valor que está sendo inserido aparece na matriz classificada).side
faz mudar o sentido do índice retornado, embora ela não altera a matriz resultante da inserção dos valores para a matriz classificada a esses índices. Uma distinção sutil, mas importante; de fato, esta resposta fornece o índice errado, seN/2
não estiveraa
.N/2
não estiveraa
. A forma correta serianp.searchsorted(aa, N/2, side='right')
(sem o+1
). Ambas as formas fornecem o mesmo índice caso contrário. Considere o caso de testeN
estranho (eN/2.0
forçar a flutuação se estiver usando o python 2).Eu também estava interessado nisso e comparei todas as respostas sugeridas com o perfplot . (Aviso: sou o autor do perfplot.)
Se você sabe que a matriz que você está visualizando já está classificada ,
é para você. É uma operação de tempo constante, ou seja, a velocidade não depende do tamanho da matriz. Você não pode ficar mais rápido do que isso.
Se você não sabe nada sobre sua matriz, não está errado com
Já classificado:
Não triados:
Código para reproduzir o gráfico:
fonte
np.searchsorted
não é tempo constante. Na verdade éO(log(n))
. Mas seu caso de teste realmente benchmarks do melhor caso desearchsorted
(o que éO(1)
).searchsorted
(ou qualquer algoritmo) consiga superarO(log(n))
uma pesquisa binária de dados classificados uniformemente distribuídos. EDIT:searchsorted
é uma pesquisa binária.fonte
Matrizes que possuem um passo constante entre elementos
No caso de uma
range
ou qualquer outra matriz de aumento linear, você pode simplesmente calcular o índice programaticamente, sem necessidade de iterar a matriz:Provavelmente, poderia-se melhorar um pouco. Verifiquei se ele funciona corretamente para algumas matrizes e valores de amostra, mas isso não significa que não possam haver erros, especialmente considerando que ele usa flutuadores ...
Dado que ele pode calcular a posição sem nenhuma iteração, será um tempo constante (
O(1)
) e provavelmente poderá superar todas as outras abordagens mencionadas. No entanto, requer uma etapa constante na matriz, caso contrário, produzirá resultados errados.Solução geral usando numba
Uma abordagem mais geral seria usar uma função numba:
Isso funcionará para qualquer matriz, mas precisará iterar sobre a matriz; portanto, no caso médio, será
O(n)
:Referência
Embora Nico Schlömer já tenha fornecido algumas referências, achei que seria útil incluir minhas novas soluções e testar diferentes "valores".
A configuração do teste:
e as parcelas foram geradas usando:
item está no começo
A função numba apresenta o melhor desempenho, seguida pela função de cálculo e pela função de busca variada. As outras soluções apresentam desempenho muito pior.
item está no final
Para matrizes pequenas, a função numba executa incrivelmente rápido, no entanto, para matrizes maiores, ela é superada pela função de cálculo e pela função de seleção de pesquisa.
item está no sqrt (len)
Isso é mais interessante. Novamente, o numba e a função de cálculo têm um ótimo desempenho, no entanto, isso está realmente desencadeando o pior caso de searchsorted, o que realmente não funciona bem nesse caso.
Comparação das funções quando nenhum valor satisfaz a condição
Outro ponto interessante é como essas funções se comportam se não houver valor cujo índice deva ser retornado:
Com este resultado:
Searchsorted, argmax e numba simplesmente retornam um valor errado. No entanto
searchsorted
enumba
retornar um índice que não é um índice válido para a matriz.As funções
where
,min
,nonzero
ecalculate
lançar uma exceção. No entanto, apenas a exceção paracalculate
realmente diz algo útil.Isso significa que é necessário agrupar essas chamadas em uma função de wrapper apropriada que captura exceções ou valores de retorno inválidos e manipula-os adequadamente, pelo menos se você não tiver certeza se o valor pode estar na matriz.
Nota: O cálculo e as
searchsorted
opções funcionam apenas em condições especiais. A função "calcular" requer uma etapa constante e a pesquisa ordenada exige que a matriz seja classificada. Portanto, eles podem ser úteis nas circunstâncias certas, mas não são soluções gerais para esse problema. Caso esteja lidando com listas Python classificadas, você pode dar uma olhada no módulo bisect em vez de usar o Numpys searchsorted.fonte
Eu gostaria de propor
Isso retornará o menor índice em que a condição for atendida, enquanto retornará o infinito se a condição nunca for atendida (e
where
retorna uma matriz vazia).fonte
Eu iria com
onde
V
é vetor (matriz 1d),x
é o valor ei
é o índice resultante.fonte