Seria muito bom se houvesse uma função de conveniência para executar um argmax sobre uma matriz 2D e retornar os índices de linha e coluna do máximo.
Muitas vezes me vejo reutilizando o seguinte código
def argmax2d(X):
n, m = X.shape
x_ = np.ravel(X)
k = np.argmax(x_)
i, j = k // m, k % m
return i, j
Embora seja simples, é opaco o suficiente para que eu tenha que procurar consistentemente essa função.
Seria bom se isso já estivesse disponível em numpy.
Haveria alguma objeção à abertura de um PR no numpy para ter essa funcionalidade prontamente disponível?
Por que não usar np.unravel_index
?
por exemplo
np.unravel_index(X.argmax(), X.shape)
Como bônus, também funciona quando X
tem mais de 2 dimensões.
Não sou fã de argmax2d
, mas poderia ser persuadido por i, j = a.argmax(axis=(0, 1))
@ eric-wieser, como você diria que isso deveria se comportar? Atualmente a.argmax()
retorna um escalar e a.argmax(axis=0)
retorna uma matriz com a forma das dimensões restantes. Eu esperaria que a.argmax(axis=(0,1))
para um array 3d retornasse um array de forma a.shape[2]
.
Deixando esse problema de lado, seria um pouco estranho usar a.argmax(axis=range(n))
no caso geral para obter uma tupla de índice de n
-length em vez do índice linear inteiro padrão. Talvez uma nova palavra-chave pudesse alternar entre as representações de saída?
Então, novamente, como essa opção de resultado ciente de forma funcionaria junto com a palavra-chave axis
já resultando em um valor de retorno não escalar?
E se uma das linhas em sua matriz 2D não tiver um máximo (é simplesmente uma matriz constante) - como argmax pode relatar isso?
Por exemplo:
# setup the problem
import numpy as np
x=np.arange(10).reshape([2,5])
x[1,3]=2
# x is this:
# [[0, 1, 2, 3, 4],
# [5, 6, 7, 2, 9]]
# this will behave normally
np.argmax(x==2, axis=1)
# Out: [2,3]
# but this one returns 0 instead of NaN
np.argmax(x==3, axis=1)
# Out: [3,0]
# Expected: [3, nan]
Seria bom ter um argumento extra, por exemplo, para permitir que o usuário controle o que fazer no caso sem máximo disponível: np.argmax(x,axis,no_ind=0)
(0 é o padrão para preservar a compatibilidade com versões anteriores).
@ thoth291 não é uma pergunta mais genérica em relação ao comportamento de argmax
? A mesma coisa acontece sem nenhum argumento de palavra-chave axis
em uma matriz 1d:
>>> np.argmax([2,2,2])
0
E este é o comportamento habitual em caso de empate: escolha o primeiro entre os valores empatados. Da mesma forma como max
daquele array não é nan
: é 2. E se você tem um máximo, você tem que ter um índice correspondente. Ou eu perdi o seu ponto?
@adeak , agora você disse isso - estou começando a pensar que essa é realmente uma questão mais geral.
Posso concordar que max([2,2,2])
é igual a 2
.
Mas pense sobre argmax ([2,2,2]) - de acordo com a definição na documentação numpy, ele retorna the indices corresponding to the first occurrence
. Isso parece certo do ponto de vista da implementação - mas na verdade é apenas um arcaísmo do algoritmo e não tem nada a ver com o que realmente deve acontecer. Efetivamente, QUALQUER índice pode ser retornado e deve ser tratado como correto. Ou pode-se dizer que argmax é ambíguo no caso de uma matriz de valor constante.
Dito isso, prefiro evitar tal decisão em primeiro lugar e retornar inf
para sinalizar que cabe ao usuário decidir como tratar esse caso.
e não tem nada a ver com o que realmente acontece.
Você quer dizer "não tem nada a ver com a definição abstrata de argmax"? Espero que _ "o algoritmo" _ e _ "o que realmente acontece" _ não sejam apenas a mesma coisa, mas que correspondam aos documentos.
@adeak : Desculpe por nunca responder
Então, novamente, como essa opção de resultado ciente de forma funcionaria junto com a palavra-chave de eixo existente já resultando em um valor de retorno não escalar?
Acho que há uma maneira óbvia de lidar com isso. Como um exemplo:
>>> ret = argmax(np.empty((A, B, C, D, E)), axes=(0, 2))
>>> type(ret)
tuple
>>> len(ret) # == len(axes)
2
>>> ret[0].shape
(B, D, E)
>>> ret[1].shape
(B, D, e)
Com isso e manter as regras, você obteria arr[argmax(arr, axes, keepdims=True)] == max(arr, keepdims=True)
por qualquer dimensionalidade, o que me parece muito desejável
Em pseudocódigo, eu esperaria:
def argmax(arr, axes, keepdims)
was_tuple = isinstance(axes, tuple):
if not was_tuple:
axes = (axes,)
shape = np.array(arr.shape)
axis_mask = np.array([i in axes for i in range(arr.ndim)])
shape[axis_mask] = 1
ret = tuple(np.empty(shape) for _ in axes)
# do the actual work
if not keepdims:
ret = tuple(r.reshape(shape[~axis_mask]) for r in ret)
if not was_tuple:
return ret[0]
@ eric-wieser - nice catch - Eu digitei mais rápido do que fui capaz de traduzir corretamente. Terá cuidado mais tarde. Atualizado o comentário para "deve acontecer" em vez de "acontece". Espero que ajude, caso contrário - aberto para sugestões de reformulação.
O ponto é que argmax ([2,2,2]) == 0 é tão bom quanto argmax ([2,2,2]) == 1 ou qualquer outra coisa e deve ser escolhido pelo usuário em vez da biblioteca. Caso contrário, um mecanismo de fallback deve ser fornecido na forma de (por exemplo) palavra-chave extra default=
ou initial=
que instruiria o que retornar neste caso.
@ thoth291 : já que o que você está discutindo não é específico para _2D_ argmax, sugiro que você crie um novo problema
@ eric-wieser não se preocupe, obrigado por entrar em contato comigo. Acho que entendo sua sugestão, e ela parece inequívoca e prática. O fato de que o tipo do argumento axis
determina se o valor de retorno é uma matriz ou uma tupla de matrizes é um pouco surpreendente para mim (especialmente axes=0
vs axes=[0]
), mas tenho certeza de que há muitos exemplos existentes para o mesmo comportamento (além disso, a praticidade supera a pureza).
~ Apenas para ter certeza: em seu exemplo, se axes=(0,2)
então as formas retornadas devem ser (B,D,E)
, certo?
Só pra ter certeza
Ambos corrigidos, boa pegada
O fato de o tipo do argumento do eixo determinar se o valor de retorno é uma matriz ou uma tupla de matrizes é um pouco surpreendente para mim
Observe que a matriz real é a mesma. Acho que isso atinge tanto praticidade quanto pureza - a regra é que axis=(a,)
→ res = (arr,)
e axis=a
→ res = arr
. Apis, aquelas tuplas de casos especiais de tamanho 1 me parecem uma má ideia, já que esse caso especial tem que se tornar contagioso em toda a pilha de chamadas
Eu nunca teria pensado em casos especiais de tuplas de comprimento 1, isso parece errado. Eu queria saber sobre o caso escalar vs tupla. Vagamente me ocorreu que os casos de tupla escalar e de comprimento 1 correspondem um ao outro da maneira que você mencionou, mas agora que você soletrou é óbvio que os casos escalar -> 1 tupla -> tupla geral são generalizações diretas. Então, obrigado, apoio totalmente sua sugestão.
Comentários muito úteis
Por que não usar
np.unravel_index
?por exemplo
Como bônus, também funciona quando
X
tem mais de 2 dimensões.