_Billete original http://projects.scipy.org/numpy/ticket/236 el 2006-08-07 por el usuario de trac martin_wiechert, asignado 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 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:
reduceat
para no establecer ningún valor en la salida donde end - start == 0
end - start == 0
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:
Cosas en las que creo que es importante pensar detenidamente para esta nueva función:
Y del departamento de desguace de bicicletas, me gusta más:
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 actualmenteLo 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 bucleEsta 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 calculaufunc.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
.
Comentario más útil
La principal motivación para
reduceat
es evitar un bucle superior areduce
para la velocidad máxima. Por lo tanto, no estoy del todo seguro de que un contenedor de un bucle for sobrereduce
sea una adición muy útil a Numpy. Iría en contra del propósito principal dereduceat
.Además, la lógica para
reduceat
existencia y API, como un reemplazo vectorizado rápido para un bucle sobrereduce
, 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 usoreduceat
: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
yend_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 actualreduceat
inconsistente comportamiento.