Numpy: caja de esquina reductora (Trac # 236)

Creado en 19 oct. 2012  ·  49Comentarios  ·  Fuente: numpy/numpy

_Billete original http://projects.scipy.org/numpy/ticket/236 el 2006-08-07 por el usuario de trac martin_wiechert, asignado a unknown._

.reduceat no maneja índices repetidos correctamente. Cuando se repite un índice, se debe devolver el elemento neutral de la operación. En el siguiente ejemplo, se espera [0, 10], no [1, 10].

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

Comentario más útil

La principal motivación para reduceat es evitar un bucle superior a reduce para la velocidad máxima. Por lo tanto, no estoy del todo seguro de que un contenedor de un bucle for sobre reduce sea ​​una adición muy útil a Numpy. Iría en contra del propósito principal de reduceat .

Además, la lógica para reduceat existencia y API, como un reemplazo vectorizado rápido para un bucle sobre reduce , es limpia y útil. No lo desaprobaría, sino que lo arreglaría.

Con respecto a la velocidad de reduceat , consideremos un ejemplo simple, pero similar a algunos casos del mundo real que tengo en mi propio código, donde 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 es una diferencia de tiempo de más de 100x e ilustra la importancia de preservar la eficiencia reduceat .

En resumen, daría prioridad a arreglar reduceat sobre la introducción de nuevas funciones.

Tener un start_indices y end_indices , aunque útil en algunos casos, a menudo es redundante y lo vería como una posible adición, pero no como una solución para el actual reduceat inconsistente comportamiento.

Todos 49 comentarios

_ @ teoliphant escribió el 2006-08-08_

Desafortunadamente, quizás, el método reduceat de NumPy sigue el comportamiento del método reduceat de Numeric para este caso de esquina.

No existe la posibilidad de devolver el elemento "identidad" de la operación en casos de igualdad de índice. El comportamiento definido es devolver el elemento dado por el primer índice si el segmento devuelve una secuencia vacía. Por lo tanto, el comportamiento documentado y real de reduceat en este caso es construir

[a [1], agregar.reducir (a [1:])]

Esta es una solicitud de función.

_trac usuario martin_wiechert escribió el 2006-08-08_

también vea el boleto # 835

Hito cambiado a 1.1 por @alberts el 2007-05-12

Hito cambiado a Unscheduled por @cournape el 2009-03-02

Creo que esto está estrechamente relacionado con # 835: si uno de los índices es len(a) , reduceat no puede generar el elemento en ese índice, que es necesario si aparece el índice len(a) o se repite al final de los índices.

Algunas soluciones:

  • una opción para reduceat para no establecer ningún valor en la salida donde end - start == 0
  • una opción para establecer la salida a un valor fijo dado donde end - start == 0
  • un parámetro where , como en ufunc() , que enmascara qué salidas deben calcularse en absoluto.

¿Se ha reflexionado más sobre este tema? Me interesaría tener la opción de establecer la salida en el valor de identidad (si existe) donde end - start == 0.

Apoyo firmemente el cambio del comportamiento de reduceat como se sugiere en este problema abierto de larga data. Parece un error claro o un error de diseño obvio que dificulta la utilidad de esta gran construcción de Numpy.

reduceat debería comportarse de forma coherente para todos los índices. Es decir, para cada índice i, ufunc.reduceat(a, indices) debería devolver ufunc.reduce(a[indices[i]:indices[i+1]]) .

Esto también debería ser cierto para el caso indices[i] == indices[i+1] . No veo ninguna razón sensata por la cual, en este caso, reduceat debería devolver a[indices[i]] lugar de ufunc.reduce(a[indices[i]:indices[i+1]]) .

Vea también AQUÍ un comentario similar del creador de Pandas , Wes McKinney .

Vaya, esto es realmente terrible y está roto.
.
Necesitaríamos un poco de discusión sobre la lista de correo, pero al menos yo estaría
totalmente a favor de convertir ese problema en FutureWarning en la próxima versión
y corregir el comportamiento unas cuantas versiones más tarde. Necesitaríamos que alguien tomara el
Lidere el inicio de esa discusión y la redacción del parche. ¿Quizás eres tú?

Gracias por la respuesta de apoyo. Puedo iniciar una discusión si esto ayuda, pero desafortunadamente no estoy preparado para parchear el código C.

¿Qué pretendes para ufuncs sin identidad, como np.maximum?

Para tales funciones, una reducción vacía debería ser un error, como ya lo es
cuando usa .reduce () en lugar de .reduceat ().

De hecho, el comportamiento debería estar impulsado por la coherencia con ufunc.reduce(a[indices[i]:indices[i+1]]) , que es lo que todo usuario esperaría. Por tanto, esto no requiere nuevas decisiones de diseño. Realmente me parece una corrección de errores de larga data. A menos que alguien pueda justificar el comportamiento inconsistente actual.

@njsmith No puedo suscribirme a la lista Numpy. Envié mi dirección aquí https://mail.scipy.org/mailman/listinfo/numpy-discussion pero nunca recibo ningún "correo electrónico solicitando confirmación". No estoy seguro de si se necesitan requisitos especiales para suscribirse ...

@divenex : ¿

Una versión de reduceat que sea consistente con ufunc.reduce(a[indices[i]:indices[i+1]]) sería realmente agradable. ¡Sería mucho más útil! Ya sea un argumento para seleccionar el comportamiento o una nueva función ( reduce_intervals ? reduce_segments ? ...?) Evitaría romper la incompatibilidad hacia atrás.

Tal vez estaría tentado a desaprobar np.ufunc.reduceat juntos; parece más útil poder especificar un conjunto de índices de inicio y finalización, para evitar casos en los que indices[i] > indices[i+1] . Además, el nombre at sugiere una similitud mucho mayor con at que existe actualmente

Lo que propondría como reemplazo es np.piecewise_reduce np.reducebins , posiblemente python puro, que básicamente hace:

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 tiene las bonitas propiedades que:

  • np.add.reduce(arr) es lo mismo que np.piecewise_reduce(np.add, arr, 0, len(arr))
  • np.add.reduceat(arr, inds) es lo mismo que np.piecewise_reduce(np.add, arr, inds)
  • np.add.accumulate(arr) es lo mismo que np.piecewise_reduce(np.add, arr, 0, np.arange(len(arr)))

Ahora, ¿esto quiere pasar por la maquinaria __array_ufunc__ ? La mayor parte de lo que debe manejarse ya debería estar cubierto por func.reduce ; el único problema es la línea np.empty , que es un problema que comparte np.concatenate .

Me parece una buena solución desde la perspectiva de la API. Incluso sería suficiente poder especificar dos conjuntos de índices a reduceat . ¿Desde una perspectiva de implementación? Bueno, no es muy difícil cambiar el PyUFunc_Reduceat actual para admitir tener dos conjuntos de índices, si eso proporciona un beneficio. Si realmente vemos la ventaja de respaldar el caso de uso de tipo acumulado de manera eficiente, tampoco sería difícil hacerlo.

Marten propuso algo similar a esto en una discusión similar de ~ 1
hace un año, pero también mencionó la posibilidad de agregar una opción de 'paso':

http://numpy-discussion.10968.n7.nabble.com/Behavior-of-reduceat-td42667.html

Cosas que me gustan (así que +1 si alguien está contando) de su propuesta:

  • Crear una nueva función, en lugar de intentar salvar la existente
    uno.
  • Hacer que los argumentos de los índices inicial y final sean específicos, en lugar de
    mágicamente descifrarlos a partir de una matriz multidimensional.
  • Los valores predeterminados de los índices Ninguno son muy claros.

Cosas en las que creo que es importante pensar detenidamente para esta nueva función:

  • ¿Deberíamos hacer del 'paso' una opción? (Yo diría que sí)
  • ¿Tiene sentido que las matrices de índices se difundan, o deben
    ser 1D?
  • ¿Debería ser una función np o un método ufunc? (Creo que lo prefiero
    como método)

Y del departamento de desguace de bicicletas, me gusta más:

  • Dale un nombre más memorable, pero no tengo ninguna propuesta.
  • Utilice 'iniciar' y 'detener' (y 'paso' si decidimos hacerlo) para
    coherencia con np.arange y el segmento de Python.
  • Eliminando los índices _ de los nombres de los kwarg.

Jaime

El jueves 13 de abril de 2017 a las 1:47 p.m., Eric Wieser [email protected]
escribió:

Quizás estaría tentado a desaprobar np.ufunc.reduceat todos juntos,
parece más útil poder especificar un conjunto de índices de inicio y final, para
evite los casos en los que índices [i]> índices [i + 1]. Además, el nombre sugiere un
similitud mucho mayor aat` de lo que existe actualmente

Lo que propondría como reemplazo es np.piecewise_reduce, que básicamente
hace:

def piecewise_reduce (func, arr, start_indices = None, end_indices = None, axis = -1, out = None):
si start_indices es None y end_indices es None:
start_indices = np.array ([0], dtype = np.intp)
end_indices = np.array (arr.shape [eje], dtype = np.intp)
elif end_indices es None:
indices_finales = np.empty_like (indices_inicio)
índices_finales [: - 1] = índices_inicio [1:]
end_indices [-1] = arr.shape [axis]
elif start_indices es None:
start_indices = np.empty_like (end_indices)
índices_inicio [1:] = índices_fin
end_indices [0] = 0
más:
afirmar len (indices_inicio) == len (indices_final)

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 tiene las bonitas propiedades que:

  • np.ufunc.reduce es lo mismo que np.piecewise_reduce (func, arr, 0,
    len (arr))
  • np.ufunc.accumulate es lo mismo que `np.piecewise_reduce (func, arr,
    np.zeros (len (arr)), np.arange (len (arr)))

Ahora, ¿esto quiere pasar por la maquinaria__array_ufunc__? La mayoría de
lo que necesita ser manejado ya debería estar cubierto por func.reduce - el
el único problema es la línea np.empty, que es un problema que np.concatenate
Comparte.

-
Recibes esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/834#issuecomment-293867746 , o silenciar
la amenaza
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.

Utilice 'iniciar' y 'detener'

Hecho

¿Deberíamos hacer del 'paso' una opción

Parece un caso de uso bastante limitado

¿Tiene sentido que las matrices de índices se transmitan o deben ser 1D?

Actualizado. > 1d es obviamente malo, pero creo que deberíamos permitir 0d y transmisión, para casos como acumular.

¿Debería ser una función np o un método ufunc? (Creo que lo prefiero
como método)

Cada método ufunc es una cosa más que __array_ufunc__ debe manejar.

La principal motivación para reduceat es evitar un bucle superior a reduce para la velocidad máxima. Por lo tanto, no estoy del todo seguro de que un contenedor de un bucle for sobre reduce sea ​​una adición muy útil a Numpy. Iría en contra del propósito principal de reduceat .

Además, la lógica para reduceat existencia y API, como un reemplazo vectorizado rápido para un bucle sobre reduce , es limpia y útil. No lo desaprobaría, sino que lo arreglaría.

Con respecto a la velocidad de reduceat , consideremos un ejemplo simple, pero similar a algunos casos del mundo real que tengo en mi propio código, donde 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 es una diferencia de tiempo de más de 100x e ilustra la importancia de preservar la eficiencia reduceat .

En resumen, daría prioridad a arreglar reduceat sobre la introducción de nuevas funciones.

Tener un start_indices y end_indices , aunque útil en algunos casos, a menudo es redundante y lo vería como una posible adición, pero no como una solución para el actual reduceat inconsistente comportamiento.

No creo que permitir que los índices de inicio y parada provengan de diferentes matrices
haría una gran diferencia en la eficiencia si se implementara en el C.

El 13 de abril de 2017 a las 23:40, divenex [email protected] escribió:

La principal motivación para reducir es evitar un bucle sobre reducir para
velocidad máxima. Así que no estoy del todo seguro de que una envoltura de un bucle for
reducir sería una adición muy útil a Numpy. Iría en contra
reducir en el propósito principal.

Además, la lógica para la reducción de la existencia y la API, como un rápido vectorizado
Reemplazo de un bucle sobre reducir, es limpio y útil. Yo no lo haría
desaprobarlo, sino arreglarlo.

Con respecto a la reducción a la velocidad, consideremos un ejemplo simple, pero similar a
algunos casos del mundo real que tengo en mi propio código, donde 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 bucles, mejor de 3: 42,1 µs por bucle
% timeit out = piecewise_reduce (np.add, arr, inds) 100 bucles, lo mejor de 3: 6,03 ms por bucle

Esta es una diferencia de tiempo de más de 100x e ilustra la importancia
de preservar la eficiencia de reducción.

En resumen, daría prioridad a corregir la reducción sobre la introducción de nuevos
funciones.

Tener start_indices y end_indices, aunque es útil en algunos casos, es
a menudo redundante y lo vería como una posible adición, pero no como una solución
para la reducción actual de un comportamiento inconsistente.

-
Estás recibiendo esto porque comentaste.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/834#issuecomment-293898215 , o silenciar
la amenaza
https://github.com/notifications/unsubscribe-auth/AAEz6xPex0fo2y_MqVHbNP5YNkJ0CBJrks5rviW-gaJpZM4ANcqc
.

Esta es una diferencia de tiempo de más de 100x e ilustra la importancia de preservar la eficiencia de reducción.

Gracias por eso. Supongo que subestimé los gastos generales asociados con la primera etapa de una llamada reduce (eso solo sucede una vez para reduceat ).

No es un argumento en contra de una función libre, pero ciertamente un argumento en contra de implementarla en Python puro

pero no como una solución para el comportamiento inconsistente de reducción actual.

El problema es que es complicado cambiar el comportamiento del código que ha existido durante tanto tiempo.


Otra posible extensión: cuando indices[i] > indices[j] , calcule la inversa:

    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, ...])

Donde np.add.inverse = np.subtract , np.multiply.inverse = np.true_divide . Esto da como resultado la bonita propiedad que

func.reduce(func.reduceat(x, inds_from_0)) == func.reduce(x))

Por ejemplo

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)]

El problema es que es complicado cambiar el comportamiento del código que ha existido durante tanto tiempo.

En parte, esta es la razón por la que en el hilo del correo electrónico sugerí dar un significado especial a una matriz de índices 2-D en la que la dimensión adicional es 2 o 3: luego se interpreta (efectivamente) como una pila de porciones. Pero me doy cuenta de que esto también es algo complicado y, por supuesto, también se podría tener un método reduce_by_slice , slicereduce o reduceslice .

ps Creo que cualquier cosa que funcione en muchos ufuncs debería ser un método, de modo que se pueda pasar a través de __array_ufunc__ y anularlo.

En realidad, una sugerencia diferente que creo que es mucho mejor: en lugar de salvar reduceat , ¿por qué no agregar un argumento slice (o start , stop , step ) a ufunc.reduce !? Como señaló @ eric-wieser, cualquier implementación de este tipo significa que podemos desaprobar reduceat completo, ya que sería

add.reduce(array, slice=slice(indices[:-1], indices[1:])

(donde ahora somos libres de hacer que el comportamiento coincida con lo que se espera para un segmento vacío)

Aquí, uno transmitiría el segmento si fuera 0-d, e incluso podría considerar pasar tuplas de segmentos si se usara una tupla de ejes.

EDITAR: hizo lo anterior slice(indices[:-1], indices[1:]) para permitir la extensión a una tupla de cortes ( slice puede contener datos arbitrarios, por lo que esto funcionaría bien).

Todavía encontraría una solución a reduceat , para convertirla en una versión 100% vectorizada adecuada de reduce , la solución de diseño más lógica. Alternativamente, para evitar romper el código (pero ver más abajo), se podría crear un método equivalente llamado reducebins , que es simplemente una versión corregida de reduceat . De hecho, estoy de acuerdo con @ eric-wieser en que el nombre de reduceat transmite más conexión con la función at que la que existe.

Entiendo la necesidad de no romper el código. Pero debo decir que me resulta difícil imaginar que gran parte del código dependiera del comportamiento anterior, dado que simplemente no tenía sentido lógico, y simplemente lo llamaría un error de larga data. Esperaría que el código usando reduceat solo se asegurara de que indices no se duplicaran, para evitar un resultado sin sentido de reduceat , o arreglara la salida como lo hice usando out[:-1] *= np.diff(indices) > 0 . Por supuesto, me interesaría un caso de usuario en el que el comportamiento / error anterior se usara según lo previsto.

No estoy completamente convencido de la solución @mhvk slice porque introduce un uso no estándar para la construcción slice . Además, sería inconsistente con la idea de diseño actual de reduce , que es _ "reducir la dimensión de a en uno, aplicando ufunc a lo largo de un eje". _

Tampoco veo un caso de usuario convincente para los índices start y end . De hecho, veo la lógica de diseño agradable del método actual reduceat conceptualmente similar a np.histogram , donde bins , que _ "define los bordes del contenedor", _ se reemplazan por indices , que también representan los bordes de los contenedores, pero en el espacio de índice en lugar de en el valor. Y reduceat aplica una función a los elementos contenidos dentro de cada par de bordes de bins. El histograma es una construcción extremadamente popular, pero no necesita, y en Numpy no lo incluye, una opción para pasar dos vectores de bordes izquierdo y derecho. Por la misma razón, dudo que exista una gran necesidad de ambos bordes en reduceat o su reemplazo.

La principal motivación de la reducción es evitar un bucle sobre la reducción para la máxima velocidad. Por lo tanto, no estoy del todo seguro de que una envoltura de un bucle for sobre reducir sea una adición muy útil a Numpy. Iría contra el objetivo principal de reducir.

Estoy de acuerdo con @divenex aquí. El hecho de que reduceat requiera que los índices se ordenen y se superpongan es una restricción razonable para garantizar que el bucle se pueda calcular en caché de manera eficiente con una sola pasada sobre los datos. Si desea bins superpuestos, es casi seguro que existen mejores formas de calcular la operación deseada (por ejemplo, agregaciones de ventanas móviles).

También estoy de acuerdo en que la solución más limpia es definir un nuevo método como reducebins con una API fija (y desaprobar reduceat ), y no intentar exprimirlo en reduce que ya hace algo diferente.

Hola a todos,

Quiero cortar de raíz la discusión de que esto es un error. Este es el comportamiento documentado de la cadena de documentos :

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 opongo a cualquier intento de cambiar el comportamiento de reduceat .

Una búsqueda rápida en github muestra muchos usos de la función. ¿Están todos aquí seguros de que todos usan solo índices estrictamente crecientes?

Con respecto al comportamiento de una nueva función, yo diría que sin matrices de inicio / parada separadas, la funcionalidad se ve seriamente obstaculizada. Hay muchas situaciones en las que uno querría medir valores en ventanas superpuestas que no están organizadas regularmente (por lo que las ventanas móviles no funcionarían). Por ejemplo, regiones de interés determinadas por algún método independiente. Y @divenex ha demostrado que la diferencia de rendimiento sobre la iteración de Python puede ser enorme.

Hay muchas situaciones en las que uno querría medir valores en ventanas superpuestas que no están organizadas regularmente (por lo que las ventanas móviles no funcionarían).

Sí, pero no querrá utilizar un bucle ingenuo como el implementado por reduceat . Debería implementar su propio cálculo de ventana móvil almacenando resultados intermedios de alguna manera para que pueda hacerse en una sola pasada lineal sobre los datos. Pero ahora estamos hablando de un algoritmo que es mucho más complicado que reduceat .

@shoyer Puedo imaginar casos en los que solo algunos de los ROI se superponen. En tales casos, escribir un algoritmo personalizado sería una exageración. No olvidemos que nuestra principal base de usuarios son los científicos, que suelen tener poco tiempo y necesitan una solución "suficientemente buena", no la óptima. Los factores constantes bajos asociados con la complejidad de np.reduceat significan que sería difícil o imposible obtener una mejor solución con código Python puro; la mayoría de las veces, el único código que los usuarios están dispuestos a escribir.

@jni Claro, reducir en grupos con arranques y paradas arbitrarios podría ser útil. Pero me parece un aumento significativo en el alcance, y algo más adecuado para otro método en lugar de un reemplazo de reduceat (que ciertamente queremos desaprobar, incluso si nunca lo eliminamos).

reducir en grupos con arranques y paradas arbitrarios podría ser útil. Pero me parece un aumento significativo del alcance.

Esto me parece muy trivial. En este momento, tenemos un código que hace esencialmente ind1 = indices[i], ind2 = indices[i + 1] . Cambiar eso para usar dos matrices diferentes en lugar de la misma debería requerir muy poco esfuerzo.

Y el comportamiento de un solo paso cuando se pasan rangos contiguos debería ser casi tan rápido como lo es ahora; la única sobrecarga es un argumento más para el nditer

Esto me parece muy trivial.

Exactamente. Además, es una funcionalidad que los usuarios tienen con reduceat (al usar cualquier otro índice), pero que perderían con una nueva función que no admite superposición.

Además, una forma de dos índices podría emular el antiguo (extraño) comportamiento:

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)

Lo que significa que no necesitamos mantener dos implementaciones muy similares

No estoy muy en contra de los índices starts y stops para los nuevos reducebins , aunque todavía no puedo ver un ejemplo obvio en el que se necesiten ambos. Se siente como generalizar np.histogram agregando bordes iniciales y finales bins ...

En última instancia, esto está bien siempre y cuando el uso principal no se vea afectado y aún se pueda llamar a reducebins(arr, indices) con una única matriz de índices y sin penalización de velocidad.

Por supuesto, hay muchas situaciones en las que es necesario operar en contenedores que no se superponen, pero en este caso, por lo general, esperaría que los contenedores no estén definidos solo por pares de bordes. Una función disponible para este tipo de escenario es ndimage.la label_comprehension de Scipy , y las funciones relacionadas como ndimage.sum, etc.

Pero esto parece bastante diferente del alcance de reducebins .

Entonces, ¿cuál sería un caso de uso natural para starts y stops en reducebins ?

Entonces, ¿cuál sería un caso de uso natural para arranques y paradas en contenedores reductores?

Alcanzable por otros medios, pero un promedio móvil de longitud k sería reducebins(np,add, arr, arange(n-k), k + arange(n-k)) . Sospecho que ignorando el costo de asignar los índices, el rendimiento sería comparable a un enfoque de as_strided .

Excepcionalmente, reducebins permitiría un promedio móvil de duración variable, lo cual no es posible con as_strided

Otro caso de uso: eliminar la ambigüedad entre incluir el final o el comienzo en la forma de un argumento.

Por ejemplo:

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

Otro caso de uso: eliminar la ambigüedad entre incluir el final o el comienzo en la forma de un argumento.

No entiendo bien este. ¿Puedes incluir el tensor de entrada aquí? Además: ¿cuáles serían los valores predeterminados para start / stop ?

De todos modos, no estoy fuertemente en contra de los argumentos separados, pero no es un reemplazo tan limpio. Me encantaría poder decir "No use reduceat, use reducebins en su lugar", pero eso es (un poco) más difícil cuando la interfaz se ve diferente.

En realidad, me acabo de dar cuenta de que incluso una opción de inicio / parada no cubre el caso de uso de los cortes vacíos, que es uno que me ha sido útil en el pasado: cuando mis propiedades / etiquetas corresponden a filas en una matriz dispersa de CSR, y utilizo los valores de indptr para hacer la reducción. Con reduceat , puedo ignorar las filas vacías. Cualquier reemplazo requerirá una contabilidad adicional. Entonces, sea cual sea el reemplazo que se le ocurra, deje reduceat alrededor.

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

Por cierto, el enigma de la secuencia vacía se puede resolver de la misma manera que lo hace Python : proporcionando un valor inicial. Podría ser un escalar o una matriz de la misma forma que indices .

sí, estoy de acuerdo en que el primer enfoque debe estar en resolver los cortes vacíos
caso. En el caso de inicio = fin, podemos tener una forma de configurar la salida
elemento a la identidad, o no modificar el elemento de salida con un
matriz especificada. El problema con la corriente es que se sobrescribe
con datos irrelevantes

Estoy totalmente con @shoyer sobre su último comentario.

Simplemente definamos out=ufunc.reducebins(a, inds) como out[i]=ufunc.reduce(a[inds[i]:inds[i+1]]) para todos los i excepto el último, y desaprovechemos reduceat .

Los casos de uso actuales para los índices starts y ends parecen más natural y probablemente implementados de manera más eficiente con funciones alternativas como as_strided o convoluciones.

@shoyer :

No entiendo bien este. ¿Puedes incluir el tensor de entrada aquí? Además: ¿cuáles serían los valores predeterminados para iniciar / detener?

Actualizado con la entrada. Vea la implementación de reduce_bins en el comentario que inició esto para los valores predeterminados. También agregué una cadena de documentación. Esa implementación es completa pero lenta (debido a que es Python).

pero eso es (un poco) más difícil cuando la interfaz se ve diferente.

Cuando solo se pasa uno de los argumentos start , la interfaz es idéntica (ignorando el caso de identidad que nos propusimos arreglar en primer lugar). Estas tres líneas significan lo mismo:

np.add.reduce_at(arr, inds)
reduce_bins(np.add, arr, inds)
reduce_bins(np.add, arr, start=inds)

(¡La distinción método / función no es algo que me importe demasiado, y no puedo definir un nuevo método ufunc como un prototipo en Python!)


@jni :

En realidad, me acabo de dar cuenta de que incluso una opción de inicio / parada no cubre el caso de uso de los cortes vacíos, que es uno que me ha sido útil en el pasado.

Estás equivocado, lo hace, exactamente de la misma manera que ufunc.reduceat ya lo hace. También es posible simplemente pasando start[i] == end[i] .

el enigma de la secuencia vacía se puede resolver ... proporcionando un valor inicial.

Sí, ya hemos cubierto esto, y ufunc.reduce ya lo hace al completar con ufunc.identity . Esto no es difícil de agregar a los ufunc.reduecat , especialmente si se fusiona # 8952. Pero como usted mismo dijo, el comportamiento actual está _documentado_, por lo que probablemente no deberíamos cambiarlo.


@divenex

Simplemente definamos out = ufunc.reducebins (a, inds) como out [i] = ufunc.reduce (a [inds [i]: inds [i + 1]]) para todos los i menos el último

Entonces len(out) == len(inds) - 1 ? Esto es diferente al comportamiento actual de reduceat , por lo que el argumento de


Todos: revisé comentarios anteriores y eliminé las respuestas de correo electrónico citadas, ya que dificultaban la lectura de esta discusión

@ eric-wieser buen punto. En mi oración anterior quise decir que para el último índice el comportamiento de reducebins sería diferente al actual reduceat . Sin embargo, en ese caso, no estoy seguro de cuál debería ser el valor, ya que el último valor formalmente no tiene sentido.

Ignorando los problemas de compatibilidad, la salida de reducebins (en 1D) debería tener el tamaño inds.size-1 , por la misma razón que np.diff(a) tiene el tamaño a.size-1 y np.histogram(a, bins) tiene el tamaño bins.size-1 . Sin embargo, esto iría en contra del deseo de tener un reemplazo directo por reduceat .

No creo que haya un argumento convincente de que a.size-1 sea ​​la respuesta correcta, incluido el índice 0 y / o el índice n parece un comportamiento bastante razonable. Todos ellos parecen útiles en algunas circunstancias, pero creo que es muy importante tener un reemplazo adicional.

También hay otro argumento para stop / start escondido aquí: le permite construir el comportamiento similar a diff si lo desea, con muy poco costo, sin dejar de mantener el reduceat comportamiento:

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 Estaría bien con los argumentos start y stop requeridos , pero no me gusta que uno de ellos sea opcional. No es obvio que proporcionar solo start significa out[i] = func.reduce(arr[start[i]:start[i+1]]) lugar de out[i] = func.reduce(arr[start[i]:]) , que es lo que habría adivinado.

Mi API preferida para reducebins es como reduceat pero sin las confusas "excepciones" que se indican en la cadena de documentos . Es decir, solo:

Para i in range(len(indices)) , reduceat calcula ufunc.reduce(a[indices[i]:indices[i+1]]) , que se convierte en la i-ésima "fila" generalizada paralela al eje en el resultado final (es decir, en una matriz 2-D, por ejemplo, si axis = 0, se convierte en la i-ésima fila, pero si el eje = 1, se convierte en la i-ésima columna).

Podría ir de cualquier manera en la tercera "excepción" que requiere índices no negativos ( 0 <= indices[i] <= a.shape[axis] ), que considero más una verificación de cordura que una excepción. Pero posiblemente ese también podría ir: puedo ver cómo los índices negativos pueden ser útiles para alguien, y no es difícil hacer los cálculos para normalizar tales índices.

No agregar automáticamente un índice al final implica que el resultado debe tener una longitud len(a)-1 , como el resultado de np.histogram .

@jni ¿Puede dar un ejemplo de lo que realmente desea calcular a partir de matrices que se encuentran en matrices dispersas? Preferiblemente con un ejemplo concreto con números no aleatorios y autónomo (sin depender de scipy.sparse).

No es obvio que proporcionar solo start significa out [i] = func.reduce (arr [start [i]: start [i + 1]]) en lugar de out [i] = func.reduce (arr [start [i] :]), que es lo que habría adivinado.

La lectura que estaba buscando es que "Cada contenedor comienza en estas posiciones", con la implicación de que todos los contenedores son contiguos a menos que se especifique explícitamente lo contrario. Quizás debería intentar redactar una cadena de documentos más completa. Creo que puedo ver un argumento sólido para prohibir no pasar ninguno de los argumentos, así que lo eliminaré de mi función de propuesta.

que requiere índices no negativos (0 <= índices [i] <a.shape [eje])

Tenga en cuenta que también hay un error aquí (# 835): el límite superior debe ser inclusivo, ya que se trata de sectores.

Tenga en cuenta que también hay un error aquí: el límite superior debería ser inclusivo, ya que se trata de sectores.

Arreglado, gracias.

No en la función reduceat sí, no lo ha hecho;)

Resulta que :\doc\neps\groupby_additions.rst contiene una propuesta (IMO inferior) para una función reduceby .

¿Fue útil esta página
0 / 5 - 0 calificaciones