Numpy: reduceat cornercase (Trac # 236)

Criado em 19 out. 2012  ·  49Comentários  ·  Fonte: numpy/numpy

_Tíquete original http://projects.scipy.org/numpy/ticket/236 em 2006-08-07 pelo usuário trac martin_wiechert, atribuído a unknown._

.reduceat não controla índices repetidos corretamente. Quando um índice é repetido, o elemento neutro da operação deve ser retornado. No exemplo abaixo [0, 10], não [1, 10], é esperado.

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])
01 - Enhancement 23 - Wish List numpy.core

Comentários muito úteis

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.

Todos 49 comentários

_ @ 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:

  • uma opção para reduceat para não definir nenhum valor na saída onde end - start == 0
  • uma opção para definir a saída para um determinado valor fixo onde end - start == 0
  • um parâmetro 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:

  • Criar uma nova função, em vez de tentar salvar o existente
    1.
  • Tornar os argumentos dos índices inicial e final específicos, em vez de
    descobrindo-os magicamente a partir de uma matriz multidimensional.
  • Os padrões para os índices Nenhum são muito claros.

Coisas que eu acho que são importantes para pensarmos bastante nesta nova função:

  • Devemos fazer da 'etapa' uma opção? (Eu diria que sim)
  • Faz sentido que as matrizes de índices transmitam, ou eles devem
    ser 1D?
  • Deve ser uma função np ou um método ufunc? (Acho que prefiro
    como método)

E do departamento de eliminação de bicicletas, eu gosto mais:

  • Dê a ele um nome mais memorável, mas não tenho nenhuma proposta.
  • Use 'start' e 'stop' (e 'step' se decidirmos ir para ele) para
    consistência com np.arange e fatia do Python.
  • Eliminando os _indices dos nomes kwarg.

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 existe

O 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 loop

Esta é 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 calcula ufunc.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 .

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

navytux picture navytux  ·  4Comentários

qualiaa picture qualiaa  ·  3Comentários

marcocaccin picture marcocaccin  ·  4Comentários

astrofrog picture astrofrog  ·  4Comentários

amuresan picture amuresan  ·  4Comentários