_Tíquete original http://projects.scipy.org/numpy/ticket/236 em 2006-08-07 pelo usuário trac martin_wiechert, atribuído a unknown._
In [1]:import numpy
In [2]:numpy.version.version
Out[2]:'1.0b1'
In [3]:a = numpy.arange (5)
In [4]:numpy.add.reduceat (a, (1,1))
Out[4]:array([ 1, 10])
_ @ teoliphant escreveu em
Infelizmente, talvez, o método de redução de NumPy siga o comportamento do método de redução de Numérico para este caso.
Não há facilidade para retornar o elemento "identidade" da operação em casos de igualdade de índice. O comportamento definido é retornar o elemento fornecido pelo primeiro índice se a fatia retornar uma sequência vazia. Portanto, o comportamento documentado e real de redução, neste caso, é construir
[a [1], add.reduce (a [1:])]
Este é um pedido de recurso.
_trac usuário martin_wiechert escreveu em 2006-08-08_
veja também o tíquete # 835
Marco alterado para 1.1
por @alberts em
Marco alterado para Unscheduled
por @cournape em
Acho que isso está intimamente ligado a # 835: Se um dos índices for len(a)
, reduceat
não pode produzir o elemento nesse índice, que é necessário se o índice len(a)
aparecer ou é repetido no final dos índices.
Algumas soluções:
reduceat
para não definir nenhum valor na saída onde end - start == 0
end - start == 0
where
, como em ufunc()
, que mascara quais saídas devem ser calculadas.Houve mais alguma reflexão sobre este assunto? Eu estaria interessado em ter a opção de definir a saída para o valor de identidade (se houver), onde end - start == 0.
Apoio fortemente a mudança do comportamento de reduceat
, conforme sugerido nesta questão aberta de longa data. Parece um bug claro ou erro de design óbvio que atrapalha a utilidade desta grande construção Numpy.
reduceat
deve se comportar de forma consistente para todos os índices. Ou seja, para cada índice i, ufunc.reduceat(a, indices)
deve retornar ufunc.reduce(a[indices[i]:indices[i+1]])
.
Isso também deve ser verdadeiro para o caso indices[i] == indices[i+1]
. Não consigo ver nenhuma razão razoável para que, neste caso, reduceat deva retornar a[indices[i]]
vez de ufunc.reduce(a[indices[i]:indices[i+1]])
.
Veja também AQUI um comentário semelhante do criador do Pandas , Wes McKinney .
Uau, isso é realmente terrível e quebrado.
.
Precisaríamos de alguma discussão na lista de e-mails, mas eu pelo menos seria
totalmente a favor de tornar essa questão um FutureWarning na próxima versão
e corrigir o comportamento algumas versões mais tarde. Precisamos de alguém para levar o
liderar o início dessa discussão e escrever o patch. Talvez seja você?
Obrigado pela resposta de apoio. Posso iniciar uma discussão se isso ajudar, mas infelizmente não estou em condições de corrigir o código C.
O que você pretende para ufuncs sem uma identidade, como np.maximum?
Para tais funções, uma redução vazia deve ser um erro, pois já é
quando você usa .reduce () em vez de .reduceat ().
Na verdade, o comportamento deve ser orientado pela consistência com ufunc.reduce(a[indices[i]:indices[i+1]])
, que é o que todo usuário esperaria. Portanto, isso não requer novas decisões de design. Realmente se parece com uma correção de bug de longa data para mim. A menos que alguém possa justificar o comportamento inconsistente atual.
@njsmith Não https://mail.scipy.org/mailman/listinfo/numpy-discussion mas nunca recebo nenhum "email solicitando confirmação". Não tenho certeza se é necessário requisitos especiais para se inscrever ...
@divenex : você verificou sua pasta de spam? (Sempre esqueço de fazer isso ...) Caso contrário, não tenho certeza do que pode estar errado. Definitivamente, não deve haver nenhum requisito especial para se inscrever além de "tem um endereço de e-mail". Se você ainda não conseguir fazer funcionar, fale e tentaremos rastrear o administrador de sistema relevante ... Definitivamente, queremos saber se ele está quebrado.
Uma versão de reduceat
consistente com ufunc.reduce(a[indices[i]:indices[i+1]])
seria muito, muito bom. Seria muito mais útil! Um argumento para selecionar o comportamento ou uma nova função ( reduce_intervals
? reduce_segments
? ...?) Evitaria quebrar a incompatibilidade com versões anteriores.
Eu talvez ficaria tentado a descontinuar np.ufunc.reduceat
completo - parece mais útil ser capaz de especificar um conjunto de índices inicial e final, para evitar casos em que indices[i] > indices[i+1]
. Além disso, o nome at
sugere uma semelhança muito maior com at
que realmente existe
O que eu proporia como uma substituição é np.piecewise_reduce
np.reducebins
, possivelmente pure-python, que basicamente faz:
def reducebins(func, arr, start=None, stop=None, axis=-1, out=None):
"""
Compute (in the 1d case) `out[i] = func.reduce(arr[start[i]:stop[i]])`
If only `start` is specified, this computes the same reduce at `reduceat` did:
`out[i] = func.reduce(arr[start[i]:start[i+1]])`
`out[-1] = func.reduce(arr[start[-1]:])`
If only `stop` is specified, this computes:
`out[0] = func.reduce(arr[:stop[0]])`
`out[i] = func.reduce(arr[stop[i-1]:stop[i]])`
"""
# convert to 1d arrays
if start is not None:
start = np.array(start, copy=False, ndmin=1, dtype=np.intp)
assert start.ndim == 1
if stop is not None:
stop = np.array(stop, copy=False, ndmin=1, dtype=np.intp)
assert stop.ndim == 1
# default arguments that do useful things
if start is None and stop is None:
raise ValueError('At least one of start and stop must be specified')
elif stop is None:
# start only means reduce from one index to the next, and the last to the end
stop = np.empty_like(start)
stop[:-1] = start[1:]
stop[-1] = arr.shape[axis]
elif start is None:
# stop only means reduce from the start to the first index, and one index to the next
start = np.empty_like(stop)
start[1:] = stop[:-1]
start[0] = 0
else:
# TODO: possibly confusing?
start, stop = np.broadcast_arrays(start, stop)
# allocate output - not clear how to do this safely for subclasses
if not out:
sh = list(arr.shape)
sh[axis] = len(stop)
sh = tuple(sh)
out = np.empty(shape=sh)
# below assumes axis=0 for brevity here
for i, (si, ei) in enumerate(zip(start, stop)):
func.reduce(arr[si:ei,...], out=out[i, ...], axis=axis)
return out
Que tem as boas propriedades que:
np.add.reduce(arr)
é o mesmo que np.piecewise_reduce(np.add, arr, 0, len(arr))
np.add.reduceat(arr, inds)
é o mesmo que np.piecewise_reduce(np.add, arr, inds)
np.add.accumulate(arr)
é o mesmo que np.piecewise_reduce(np.add, arr, 0, np.arange(len(arr)))
Agora, isso quer passar pela máquina __array_ufunc__
? A maior parte do que precisa ser tratado já deve estar coberto por func.reduce
- o único problema é a linha np.empty
, que é um problema que np.concatenate
compartilha.
Parece uma boa solução para mim do ponto de vista da API. Apenas ser capaz de especificar dois conjuntos de índices para reduceat
seria suficiente. De uma perspectiva de implementação? Bem, não é muito difícil alterar o PyUFunc_Reduceat
atual para suportar dois conjuntos de inds, se isso fornecer benefícios. Se realmente virmos a vantagem de suportar o caso de uso do tipo acumular com eficiência, também não será difícil fazer isso.
Marten propôs algo semelhante a isso em uma discussão semelhante de ~ 1
ano atrás, mas ele também mencionou a possibilidade de adicionar uma opção de 'etapa':
http://numpy-discussion.10968.n7.nabble.com/Behavior-of-reduceat-td42667.html
Coisas que eu gosto (então, +1 se alguém estiver contando) em sua proposta:
Coisas que eu acho que são importantes para pensarmos bastante nesta nova função:
E do departamento de eliminação de bicicletas, eu gosto mais:
Jaime
Em quinta-feira, 13 de abril de 2017 às 13h47, Eric Wieser [email protected]
escrevi:
Eu talvez me sinta tentado a descontinuar np.ufunc.reduceat completamente - ele
parece mais útil ser capaz de especificar um conjunto de índices inicial e final, para
evite casos em que índices [i]> índices [i + 1]. Além disso, o nome sugere um
similaridade muito maior do que realmente existeO que eu proponho como uma substituição é np.piecewise_reduce, que basicamente
faz:def piecewise_reduce (func, arr, start_indices = None, end_indices = None, axis = -1, out = None):
se start_indices for None e end_indices for None:
start_indices = np.array ([0], dtype = np.intp)
end_indices = np.array (arr.shape [eixo], dtype = np.intp)
elif end_indices é Nenhum:
end_indices = np.empty_like (start_indices)
end_indices [: - 1] = start_indices [1:]
end_indices [-1] = arr.shape [eixo]
elif start_indices é Nenhum:
start_indices = np.empty_like (end_indices)
start_indices [1:] = end_indices
end_indices [0] = 0
outro:
declarar len (start_indices) == len (end_indices)if not out: sh = list(arr.shape) sh[axis] = len(end_indices) out = np.empty(shape=sh) # below assumes axis=0 for brevity here for i, (si, ei) in enumerate(zip(start_indices, end_indices)): func.reduce(arr[si:ei,...], out=alloc[i, ...], axis=axis) return out
Que tem as boas propriedades que:
- np.ufunc.reduce é o mesmo que np.piecewise_reduce (func, arr, 0,
len (arr))- np.ufunc.accumulate é o mesmo que `np.piecewise_reduce (func, arr,
np.zeros (len (arr)), np.arange (len (arr)))Agora, isso quer passar pelo maquinário__array_ufunc__? O máximo de
o que precisa ser tratado já deve estar coberto por func.reduce - o
único problema é a linha np.empty, que é um problema que np.concatenate
compartilhamentos.-
Você está recebendo isto porque está inscrito neste tópico.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/834#issuecomment-293867746 ou mudo
o segmento
https://github.com/notifications/unsubscribe-auth/ADMGdtjSCodONyu6gCpwofdBaJMCIKa-ks5rvgtrgaJpZM4ANcqc
.
-
(__ /)
(Oo)
(> <) Este es Conejo. Copia a Conejo en tu firma y ayúdale en sus planes
de dominación mundial.
Use 'iniciar' e 'parar'
Feito
Devemos fazer da 'etapa' uma opção
Parece um caso de uso bastante restrito
Faz sentido que as matrizes de índices transmitam ou devem ser 1D
Atualizada. > 1d é obviamente ruim, mas acho que devemos permitir 0d e transmissão, para casos como acumular.
Deve ser uma função np ou um método ufunc? (Acho que prefiro
como método)
Cada método ufunc é mais uma coisa para __array_ufunc__
manipular.
A principal motivação para reduceat
é evitar um loop em reduce
para velocidade máxima. Portanto, não estou totalmente certo de que um invólucro de um loop for sobre reduce
seria uma adição muito útil ao Numpy. Isso iria contra o propósito principal de reduceat
.
Além disso, a lógica para reduceat
existência e API, como uma rápida substituição vetorizada para um loop sobre reduce
, é limpa e útil. Eu não iria descontinuá-lo, mas sim corrigi-lo.
Com relação à velocidade de reduceat
, vamos considerar um exemplo simples, mas semelhante a alguns casos do mundo real que tenho em meu próprio código, onde uso reduceat
:
n = 10000
arr = np.random.random(n)
inds = np.random.randint(0, n, n//10)
inds.sort()
%timeit out = np.add.reduceat(arr, inds)
10000 loops, best of 3: 42.1 µs per loop
%timeit out = piecewise_reduce(np.add, arr, inds)
100 loops, best of 3: 6.03 ms per loop
Esta é uma diferença de tempo de mais de 100x e ilustra a importância de preservar a eficiência de reduceat
.
Em resumo, eu priorizaria consertar reduceat
em vez de introduzir novas funções.
Ter um start_indices
e end_indices
, embora útil em alguns casos, é freqüentemente redundante e eu consideraria uma possível adição, mas não como uma correção para a inconsistente reduceat
atual comportamento.
Não acho que permitir que os índices de início e parada venham de matrizes diferentes
faria uma grande diferença para a eficiência se implementado no C.
Em 13 de abril de 2017 às 23:40, divenex [email protected] escreveu:
A principal motivação para a redução é evitar um ciclo de redução para
velocidade máxima. Portanto, não tenho certeza se um invólucro de um loop for
reduzir seria uma adição muito útil ao Numpy. Iria contra
reduzir o objetivo principal.Além disso, a lógica para reduzir a existência e API, como um rápido vetorizado
a substituição de um loop em vez de reduzir é limpa e útil. Eu não faria
descontinue-o, mas sim conserte.Em relação à velocidade de redução, vamos considerar um exemplo simples, mas semelhante a
alguns casos do mundo real que tenho em meu próprio código, onde uso reduceat:n = 10000
arr = np.random.random (n)
inds = np.random.randint (0, n, n // 10)
inds.sort ()
% timeit out = np.add.reduceat (arr, inds) 10000 loops, melhor de 3: 42,1 µs por loop
% timeit out = piecewise_reduce (np.add, arr, inds) 100 loops, melhor de 3: 6,03 ms por loopEsta é uma diferença de tempo de mais de 100x e ilustra a importância
de preservar a eficiência da redução.Em resumo, eu priorizaria corrigir a redução em vez de introduzir novos
funções.Ter start_indices e end_indices, embora útil em alguns casos, é
muitas vezes redundante e eu veria isso como uma possível adição, mas não como uma correção
para o comportamento inconsistente de redução atual.-
Você está recebendo isso porque comentou.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/numpy/numpy/issues/834#issuecomment-293898215 ou mudo
o segmento
https://github.com/notifications/unsubscribe-auth/AAEz6xPex0fo2y_MqVHbNP5YNkJ0CBJrks5rviW-gaJpZM4ANcqc
.
Esta é uma diferença de tempo de mais de 100x e ilustra a importância de preservar a eficiência da redução.
Obrigado por isso - acho que subestimei o overhead associado ao primeiro estágio de uma chamada de reduce
(isso só acontece uma vez para reduceat
).
Não é um argumento contra uma função livre, mas certamente um argumento contra implementá-la em python puro
mas não como uma correção para o comportamento inconsistente de redução atual.
O problema é que é complicado mudar o comportamento do código que existe há tanto tempo.
Outra extensão possível: quando indices[i] > indices[j]
, calcule o inverso:
for i, (si, ei) in enumerate(zip(start, stop)):
if si >= ei:
func.reduce(arr[si:ei,...], out=out[i, ...], axis=axis)
else:
func.reduce(arr[ei:si,...], out=out[i, ...], axis=axis)
func.inverse(func.identity, out[i, ...], out=out[i, ...])
Onde np.add.inverse = np.subtract
, np.multiply.inverse = np.true_divide
. Isso resulta na boa propriedade que
func.reduce(func.reduceat(x, inds_from_0)) == func.reduce(x))
Por exemplo
a = [1, 2, 3, 4]
inds = [0, 3, 1]
result = np.add.reduceat(a, inds) # [6, -5, 9] == [(1 + 2 + 3), -(3 + 2), (2 + 3 + 4)]
O problema é que é complicado mudar o comportamento do código que existe há tanto tempo.
Em parte, é por isso que, no segmento de e-mail, sugeri dar um significado especial a uma matriz 2-D de índices em que a dimensão extra é 2 ou 3: ela é então (efetivamente) interpretada como uma pilha de fatias. Mas eu percebo que isso também é um pouco confuso e, claro, pode-se também ter um método reduce_by_slice
, slicereduce
ou reduceslice
.
ps Eu acho que qualquer coisa que funcione em muitos ufuncs deve ser um método, para que possa ser passado por __array_ufunc__
e ser sobrescrito.
Na verdade, uma sugestão diferente que eu acho muito melhor: em vez de salvar reduceat
, por que não adicionar um argumento slice
(ou start
, stop
, step
) a ufunc.reduce
!? Como @ eric-wieser observou, qualquer implementação significa que podemos simplesmente descontinuar reduceat
completo, como seria
add.reduce(array, slice=slice(indices[:-1], indices[1:])
(onde agora estamos livres para fazer o comportamento corresponder ao que é esperado para uma fatia vazia)
Aqui, seria transmitido a fatia se fosse 0-d, e poderia até considerar a passagem de tuplas de fatias se uma tupla de eixos fosse usada.
EDITAR: fez o slice(indices[:-1], indices[1:])
para permitir a extensão de uma tupla de fatias ( slice
pode conter dados arbitrários, então isso funcionaria bem).
Eu ainda encontraria uma correção para reduceat
, para torná-lo uma versão 100% vetorizada adequada de reduce
, a solução de design mais lógica. Alternativamente, para evitar quebrar o código (mas veja abaixo), um método equivalente chamado reducebins
pode ser criado, que é simplesmente uma versão corrigida de reduceat
. Na verdade, concordo com @ eric-wieser que a nomenclatura de reduceat
transmite mais conexão com a função at
que existe.
Eu entendo a necessidade de não quebrar o código. Mas devo dizer que acho difícil imaginar que muito código dependesse do comportamento antigo, visto que simplesmente não fazia sentido lógico, e eu simplesmente o chamaria de um bug antigo. Eu esperaria que o código usando reduceat
apenas garantisse que indices
não fosse duplicado, para evitar um resultado sem sentido de reduceat
, ou corrigisse a saída como fiz usando out[:-1] *= np.diff(indices) > 0
. É claro que eu estaria interessado em um caso de usuário em que o comportamento / bug antigo foi usado como pretendido.
Não estou totalmente convencido sobre a solução @mhvk slice
porque ela introduz um uso não padrão para a construção slice
. Além disso, seria inconsistente com a ideia de design atual de reduce
, que é _ "reduzir a dimensão de a em um, aplicando ufunc ao longo de um eixo." _
Também não vejo um caso de usuário convincente para os índices start
e end
. Na verdade, eu vejo a lógica de design legal do método atual reduceat
conceitualmente semelhante a np.histogram
, onde bins
, que _ "define as bordas do compartimento", _ são substituídos por indices
, que também representam as bordas das caixas, mas em espaço de índice em vez de valor. E reduceat
aplica uma função aos elementos contidos dentro de cada par de bordas de caixas. O histograma é uma construção extremamente popular, mas não precisa, e no Numpy não inclui, uma opção para passar dois vetores das bordas esquerda e direita. Pela mesma razão, duvido que haja uma forte necessidade de ambas as arestas em reduceat
ou de sua substituição.
A principal motivação para a redução é evitar uma redução em loop para obter a velocidade máxima. Portanto, não estou totalmente certo de que um invólucro de um laço for sobre redução seria uma adição muito útil ao Numpy. Isso iria contra a redução do objetivo principal.
Eu concordo com @divenex aqui. O fato de reduceat
exigir que os índices sejam classificados e sobrepostos é uma restrição razoável para garantir que o loop possa ser calculado de maneira eficiente em cache com uma única passagem sobre os dados. Se você deseja compartimentos sobrepostos, quase certamente existem maneiras melhores de calcular a operação desejada (por exemplo, agregações de janela contínua).
Também concordo que a solução mais limpa é definir um novo método como reducebins
com uma API fixa (e descontinuar reduceat
) e não tentar espremê-lo em reduce
que já faz algo diferente.
Olá a todos,
Eu quero cortar à raiz a discussão de que isso é um bug. Este é o comportamento documentado da docstring :
For i in ``range(len(indices))``, `reduceat` computes
``ufunc.reduce(a[indices[i]:indices[i+1]])``, which becomes the i-th
generalized "row" parallel to `axis` in the final result (i.e., in a
2-D array, for example, if `axis = 0`, it becomes the i-th row, but if
`axis = 1`, it becomes the i-th column). There are three exceptions to this:
* when ``i = len(indices) - 1`` (so for the last index),
``indices[i+1] = a.shape[axis]``.
* if ``indices[i] >= indices[i + 1]``, the i-th generalized "row" is
simply ``a[indices[i]]``.
* if ``indices[i] >= len(a)`` or ``indices[i] < 0``, an error is raised.
Como tal, me oponho a qualquer tentativa de mudar o comportamento de reduceat
.
Uma busca rápida no github mostra muitos, muitos usos da função. Todos aqui estão certos de que todos usam apenas índices estritamente crescentes?
Com relação ao comportamento de uma nova função, eu diria que, sem matrizes start / stop separadas, a funcionalidade é severamente prejudicada. Há muitas situações em que se desejaria medir valores em janelas sobrepostas que não são organizadas regularmente (portanto, as janelas dinâmicas não funcionariam). Por exemplo, regiões de interesse determinadas por algum método independente. E @divenex mostrou que a diferença de desempenho em relação à iteração Python pode ser enorme.
Há muitas situações em que se desejaria medir valores em janelas sobrepostas que não são organizadas regularmente (portanto, as janelas dinâmicas não funcionariam).
Sim, mas você não gostaria de usar um loop ingênuo como o implementado por reduceat
. Você gostaria de implementar seu próprio cálculo de janela contínua, armazenando resultados intermediários de alguma forma, para que isso possa ser feito em uma única passagem linear sobre os dados. Mas agora estamos falando sobre um algoritmo que é muito mais complicado do que reduceat
.
@shoyer Posso imaginar casos em que apenas algumas das ROIs se sobrepõem. Nesses casos, escrever um algoritmo personalizado seria um grande exagero. Não vamos esquecer que nossa base de usuários principal são os cientistas, que normalmente têm pouco tempo e precisam de uma solução "boa o suficiente", não do ótimo absoluto. Os fatores de baixa constante associados à complexidade de np.reduceat
significam que seria difícil ou impossível obter uma solução melhor com código Python puro - na maioria das vezes, o único código que os usuários estão dispostos a escrever.
@jni Claro, reduzir em grupos com inícios e reduceat
(que certamente queremos descontinuar, mesmo que nunca o removamos).
reduzir em grupos com inícios e paradas arbitrárias pode ser útil. Mas parece um aumento significativo no escopo para mim
Isso parece muito trivial para mim. No momento, temos um código que faz essencialmente ind1 = indices[i], ind2 = indices[i + 1]
. Mudar isso para usar dois arrays diferentes em vez do mesmo deve exigir muito pouco esforço.
E o comportamento de passagem única ao passar por intervalos contíguos deve ser quase exatamente tão rápido quanto agora - a única sobrecarga é mais um argumento para o nditer
Isso parece muito trivial para mim.
Exatamente. Além disso, é uma funcionalidade que os usuários têm com reduceat
(usando todos os outros índices), mas perderiam com uma nova função que não suporta sobreposição.
Além disso, uma forma de dois índices poderia emular o comportamento antigo (bizarro):
def reduceat(func, arr, inds):
deprecation_warning()
start = inds
stops = zeros(inds.shape)
stops[:-1] = start[1:]
stops[-1] = len(arr)
np.add(stops, 1, where=ends == starts, out=stops) # reintroduce the "bug" that we would have to keep
return reducebins(func, arr, starts, stops)
O que significa que não precisamos manter duas implementações muito semelhantes
Não sou fortemente contra os índices starts
e stops
para os novos reducebins
, embora ainda não consiga ver um exemplo óbvio de onde ambos são necessários. Parece generalizar np.histogram
adicionando bordas iniciais e finais bins
...
Em última análise, isso é bom, desde que o uso principal não seja afetado e ainda se possa chamar reducebins(arr, indices)
com uma única matriz de índices e sem penalidade de velocidade.
Claro que existem muitas situações em que é necessário operar em caixas não sobrepostas, mas, neste caso, geralmente esperaria que as caixas não fossem definidas apenas por pares de arestas. Uma função disponível para esse tipo de cenário é ndimage.labeled_comprehension de Scipy e as funções relacionadas, como ndimage.sum e assim por diante.
Mas isso parece bem diferente do escopo de reducebins
.
Então, qual seria um caso de uso natural para starts
e stops
em reducebins
?
Então, qual seria um caso de uso natural para partidas e paradas em redutoras?
Alcançável por outros meios, mas uma média móvel de comprimento k
seria reducebins(np,add, arr, arange(n-k), k + arange(n-k))
. Suspeito que, ignorando o custo de alocação dos índices, o desempenho seria comparável a uma abordagem as_strided
.
Exclusivamente, reducebins
permitiria uma média móvel de duração variável, o que não é possível com as_strided
Outro caso de uso - eliminar a ambiguidade entre incluir o fim ou o início na forma de um argumento.
Por exemplo:
a = np.arange(10)
reducebins(np.add, start=[2, 4, 6]) == [2 + 3, 4 + 5, 6 + 7 + 8 + 9] # what `reduceat` does
reducebins(np.add, stop=[2, 4, 6]) == [0 + 1, 2 + 3, 4 + 5] # also useful
Outro caso de uso - eliminar a ambiguidade entre incluir o fim ou o início na forma de um argumento.
Eu não entendo muito bem este. Você pode incluir o tensor de entrada aqui? Além disso: quais seriam os valores padrão para start
/ stop
?
De qualquer forma, não sou fortemente contra os argumentos separados, mas não é uma substituição tão isenta. Eu adoraria poder dizer "Não use reduceat, use reducebins", mas isso é (um pouco) mais difícil quando a interface parece diferente.
Na verdade, acabei de perceber que mesmo uma opção iniciar / parar não cobre o caso de uso de fatias vazias, que foi útil para mim no passado: quando minhas propriedades / rótulos correspondem a linhas em uma matriz esparsa de CSR, e uso os valores de indptr
para fazer a redução. Com reduceat
, posso ignorar as linhas vazias. Qualquer substituição exigirá contabilidade adicional. Portanto, seja qual for a substituição que você encontrar, deixe reduceat
ao redor.
In [2]: A = np.random.random((4000, 4000))
In [3]: B = sparse.csr_matrix((A > 0.8) * A)
In [9]: %timeit np.add.reduceat(B.data, B.indptr[:-1]) * (np.diff(B.indptr) > 1)
1000 loops, best of 3: 1.81 ms per loop
In [12]: %timeit B.sum(axis=1).A
100 loops, best of 3: 1.95 ms per loop
In [16]: %timeit np.maximum.reduceat(B.data, B.indptr[:-1]) * (np.diff(B.indptr) > 0)
1000 loops, best of 3: 1.8 ms per loop
In [20]: %timeit B.max(axis=1).A
100 loops, best of 3: 2.12 ms per loop
A propósito, o enigma da sequência vazia pode ser resolvido da mesma maneira que o Python o faz : fornecendo um valor inicial. Isso pode ser um escalar ou uma matriz da mesma forma que indices
.
sim, concordo que o primeiro foco deve ser resolver as fatias vazias
caso. No caso de início = fim, podemos ter uma maneira de definir a saída
elemento para a identidade, ou para não modificar o elemento de saída com um
especificado fora da matriz. O problema com o atual é que ele é sobrescrito
com dados irrelevantes
Estou totalmente de acordo com @shoyer sobre seu último comentário.
Vamos simplesmente definir out=ufunc.reducebins(a, inds)
como out[i]=ufunc.reduce(a[inds[i]:inds[i+1]])
para todos os i
exceto o último, e descontinuar reduceat
.
Os casos de uso atuais para os índices starts
e ends
parecem mais naturalmente e provavelmente mais eficientemente implementados com funções alternativas como as_strided
ou convoluções.
@shoyer :
Eu não entendo muito bem este. Você pode incluir o tensor de entrada aqui? Além disso: quais seriam os valores padrão para iniciar / parar?
Atualizado com a entrada. Veja a implementação de reduce_bins
no comentário que iniciou isso para os valores padrão. Eu adicionei uma docstring lá também. Essa implementação é completa, mas lenta (devido a ser python).
mas isso é (um pouco) mais difícil quando a interface parece diferente.
Quando apenas um argumento start
é passado, a interface é idêntica (ignorando o caso de identidade que nos propusemos corrigir em primeiro lugar). Essas três linhas significam a mesma coisa:
np.add.reduce_at(arr, inds)
reduce_bins(np.add, arr, inds)
reduce_bins(np.add, arr, start=inds)
(a distinção método / função não é algo que me interessa muito e não posso definir um novo método ufunc como um protótipo em python!)
@jni :
Na verdade, acabei de perceber que mesmo uma opção de iniciar / parar não cobre o caso de uso de fatias vazias, que foi útil para mim no passado
Você está errado, é verdade - exatamente da mesma maneira que ufunc.reduceat
já está. Também é possível simplesmente passando start[i] == end[i]
.
o enigma da sequência vazia pode ser resolvido ... fornecendo um valor inicial.
Sim, já cobrimos isso e ufunc.reduce
já faz isso preenchendo com ufunc.identity
. Isso não é difícil de adicionar ao ufunc.reduecat
, especialmente se # 8952 for mesclado. Mas, como você mesmo disse, o comportamento atual é _documentado_, então provavelmente não devemos mudá-lo.
@divenex
Vamos simplesmente definir out = ufunc.reducebins (a, inds) como out [i] = ufunc.reduce (a [inds [i]: inds [i + 1]]) para todos os i exceto o último
Então len(out) == len(inds) - 1
? Isso é diferente do comportamento atual de reduceat
, então o argumento de @shoyer sobre a troca é mais forte aqui
Todos: analisei comentários anteriores e removi as respostas de e-mail citadas, pois estavam dificultando a leitura desta discussão
@ eric-wieser bom ponto. Na minha frase acima, eu quis dizer que, para o último índice, o comportamento de reducebins
seria diferente do atual reduceat
. No entanto, nesse caso, não tenho certeza de qual deveria ser o valor, pois o último valor formalmente não faz sentido.
Ignorando as questões de compatibilidade, a saída de reducebins
(em 1D) deve ter o tamanho inds.size-1
, pela mesma razão que np.diff(a)
tem o tamanho a.size-1
e np.histogram(a, bins)
tem o tamanho bins.size-1
. No entanto, isso iria contra o desejo de ter um substituto imediato para reduceat
.
Não acho que haja um argumento convincente de que a.size-1
seja a resposta certa - incluir o índice 0
e / ou o índice n
parece um comportamento bastante razoável também. Todos eles parecem úteis em algumas circunstâncias, mas acho que é muito importante ter uma queda na reposição.
Há também outro argumento para stop
/ start
escondido aqui - permite que você crie o comportamento semelhante a diff
se quiser, com muito pouco custo, enquanto mantém o Comportamento de reduceat
:
a = np.arange(10)
inds = [2, 4, 6]
reduce_bins(a, start=inds[:-1], stop=inds[1:]) # [2 + 3, 4 + 5]
# or less efficiently:
reduce_at(a, inds)[:-1}
reduce_bins(a, start=inds)[:-1]
reduce_bins(a, stop=inds)[1:]
@ eric-wieser Eu aceitaria start
e stop
argumentos obrigatórios , mas não gosto de tornar um deles opcional. Não é óbvio que fornecer apenas start
signifique out[i] = func.reduce(arr[start[i]:start[i+1]])
vez de out[i] = func.reduce(arr[start[i]:])
, que é o que eu teria imaginado.
Minha API preferida para reducebins
é como reduceat
mas sem as "exceções" confusas observadas na docstring . Ou seja, apenas:
Para
i in range(len(indices))
, reduceat calculaufunc.reduce(a[indices[i]:indices[i+1]])
, que se torna a i-ésima "linha" generalizada paralela ao eixo no resultado final (ou seja, em uma matriz 2-D, por exemplo, se eixo = 0, torna-se a i-ésima linha, mas se o eixo = 1, torna-se a i-ésima coluna).
Eu poderia ir de qualquer maneira na terceira "exceção" que requer índices não negativos ( 0 <= indices[i] <= a.shape[axis]
), que considero mais uma verificação de sanidade do que uma exceção. Mas possivelmente esse também poderia ir - posso ver como os índices negativos podem ser úteis para alguém, e não é difícil fazer as contas para normalizar esses índices.
Não adicionar automaticamente um índice no final implica que o resultado deve ter o comprimento len(a)-1
, como o resultado de np.histogram
.
@jni Você pode dar um exemplo do que realmente deseja calcular a partir de matrizes encontradas em matrizes esparsas? De preferência com um exemplo concreto com números não aleatórios e autocontidos (sem depender de scipy.sparse).
Não é óbvio que fornecer apenas start signifique out [i] = func.reduce (arr [start [i]: start [i + 1]]) em vez de out [i] = func.reduce (arr [start [i] :]), que é o que eu teria imaginado.
A leitura que eu pretendia é que "Cada compartimento começa nessas posições", com a implicação de que todos os compartimentos são contíguos, a menos que seja explicitamente especificado de outra forma. Talvez eu deva tentar esboçar uma docstring mais completa. Acho que posso ver um forte argumento para proibir passar nenhum dos argumentos, então vou remover isso de minha função propor.
que requer índices não negativos (0 <= índices [i] <a.shape [eixo])
Observe que também há um bug aqui (# 835) - o limite superior deve ser inclusivo, uma vez que se trata de fatias.
Observe que também há um bug aqui - o limite superior deve ser inclusivo, já que se trata de fatias.
Corrigido, obrigado.
Não na função reduceat
si, você não tem;)
Acontece que :\doc\neps\groupby_additions.rst
contém uma proposta (inferior da IMO) para uma função reduceby
.
Comentários muito úteis
A principal motivação para
reduceat
é evitar um loop emreduce
para velocidade máxima. Portanto, não estou totalmente certo de que um invólucro de um loop for sobrereduce
seria uma adição muito útil ao Numpy. Isso iria contra o propósito principal dereduceat
.Além disso, a lógica para
reduceat
existência e API, como uma rápida substituição vetorizada para um loop sobrereduce
, é limpa e útil. Eu não iria descontinuá-lo, mas sim corrigi-lo.Com relação à velocidade de
reduceat
, vamos considerar um exemplo simples, mas semelhante a alguns casos do mundo real que tenho em meu próprio código, onde usoreduceat
:Esta é uma diferença de tempo de mais de 100x e ilustra a importância de preservar a eficiência de
reduceat
.Em resumo, eu priorizaria consertar
reduceat
em vez de introduzir novas funções.Ter um
start_indices
eend_indices
, embora útil em alguns casos, é freqüentemente redundante e eu consideraria uma possível adição, mas não como uma correção para a inconsistentereduceat
atual comportamento.