Numpy: réduire la majuscule (Trac # 236)

Créé le 19 oct. 2012  ·  49Commentaires  ·  Source: numpy/numpy

_Billet original http://projects.scipy.org/numpy/ticket/236 le 07/08/2006 par l'utilisateur trac martin_wiechert, attribué à unknown._

.reduceat ne gère pas correctement les index répétés. Lorsqu'un index est répété, l'élément neutre de l'opération doit être renvoyé. Dans l'exemple ci-dessous, [0, 10], et non [1, 10], est attendu.

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

Commentaire le plus utile

La motivation principale de reduceat est d'éviter une boucle de plus de reduce pour une vitesse maximale. Je ne suis donc pas tout à fait sûr qu'un wrapper d'une boucle for sur reduce serait un ajout très utile à Numpy. Cela irait à l'encontre reduceat objectif principal de

De plus, la logique reduceat existence de reduce , est propre et utile. Je ne le déprécierais pas, mais le réparerais plutôt.

En ce qui concerne la vitesse de reduceat , considérons un exemple simple, mais similaire à certains cas du monde réel que j'ai dans mon propre code, où j'utilise 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

Il s'agit d'un décalage horaire de plus de 100x et illustre l'importance de préserver l'efficacité de reduceat .

En résumé, je donnerais la priorité à la correction de reduceat plutôt qu'à l'introduction de nouvelles fonctions.

Avoir un start_indices et end_indices , bien que utile dans certains cas, est souvent redondant et je le verrais comme un ajout possible, mais pas comme un correctif pour l'actuel reduceat incohérent comportement.

Tous les 49 commentaires

_ @ teoliphant écrit le

Malheureusement, peut-être, la méthode reduceat de NumPy suit le comportement de la méthode reduceat de Numeric pour ce cas d'angle.

Il n'y a aucune possibilité de renvoyer l'élément "identité" de l'opération en cas d'égalité d'index. Le comportement défini est de renvoyer l'élément donné par le premier index si la tranche renvoie une séquence vide. Par conséquent, le comportement documenté et réel de reduceat dans ce cas est de construire

[a [1], add.reduce (a [1:])]

Ceci est une demande de fonctionnalité.

_trac user martin_wiechert rédigé le 2006-08-08_

voir aussi le ticket # 835

Jalon changé en 1.1 par @alberts le 2007-05-12

Jalon changé en Unscheduled par @cournape le

Je pense que cela est étroitement lié à # 835: si l'un des indices est len(a) , reduceat ne peut pas sortir l'élément à cet index, ce qui est nécessaire si l'index len(a) apparaît ou se répète à la fin des indices.

Quelques solutions:

  • une option à reduceat pour ne définir aucune valeur dans la sortie où end - start == 0
  • une option pour définir la sortie sur une valeur fixe donnée où end - start == 0
  • un paramètre where , comme dans ufunc() , qui masque quelles sorties doivent être calculées.

Y a-t-il eu plus de réflexion sur cette question? Je serais intéressé à avoir la possibilité de définir la sortie sur la valeur d'identité (si elle existe) où end - start == 0.

Je soutiens fermement le changement de comportement de reduceat comme suggéré dans ce numéro ouvert de longue date. Cela ressemble à un bug clair ou à une erreur de conception évidente qui entrave l'utilité de cette excellente construction Numpy.

reduceat doit se comporter de manière cohérente pour tous les indices. À savoir, pour chaque index i, ufunc.reduceat(a, indices) doit retourner ufunc.reduce(a[indices[i]:indices[i+1]]) .

Cela devrait également être vrai pour le cas indices[i] == indices[i+1] . Je ne vois aucune raison valable pour laquelle, dans ce cas, reduceat devrait retourner a[indices[i]] au lieu de ufunc.reduce(a[indices[i]:indices[i+1]]) .

Voir aussi ICI un commentaire similaire du créateur de Pandas Wes McKinney .

Wow, c'est en effet terrible et cassé.
.
Nous aurions besoin d'une discussion sur la liste de diffusion, mais je serais au moins
totalement en faveur de faire de ce problème un FutureWarning dans la prochaine version
et corriger le comportement quelques versions plus tard. Nous aurions besoin de quelqu'un pour prendre le
mener cette discussion et rédiger le patch. C'est peut-être toi?

Merci pour la réponse de soutien. Je peux commencer une discussion si cela aide, mais je ne suis malheureusement pas prêt à corriger le code C.

Que prévoyez-vous pour les ufuncs sans identité, comme np.maximum?

Pour de telles fonctions, une réduction vide devrait être une erreur, comme c'est déjà le cas
lorsque vous utilisez .reduce () au lieu de .reduceat ().

En effet, le comportement doit être piloté par la cohérence avec ufunc.reduce(a[indices[i]:indices[i+1]]) , ce à quoi chaque utilisateur s'attend. Cela ne nécessite donc pas de nouvelles décisions de conception. Cela ressemble vraiment à une correction de bogue de longue date pour moi. À moins que quiconque ne puisse justifier le comportement incohérent actuel.

@njsmith Je ne https://mail.scipy.org/mailman/listinfo/numpy-discussion mais je ne reçois jamais de "mail demandant une confirmation". Je ne sais pas si l'on a besoin d'exigences particulières pour s'abonner ...

@divenex : avez-vous vérifié votre dossier spam? (J'oublie toujours de faire ça ...) Sinon, je ne suis pas sûr de ce qui pourrait mal tourner. Il ne devrait certainement pas y avoir d'exigences particulières pour s'abonner au-delà de «a une adresse e-mail». Si vous ne parvenez toujours pas à le faire fonctionner, parlez-en et nous essaierons de retrouver l'administrateur système concerné ... Nous voulons vraiment savoir s'il est cassé.

Une version de reduceat cohérente avec ufunc.reduce(a[indices[i]:indices[i+1]]) serait vraiment, vraiment sympa. Ce serait tellement plus utile! Soit un argument pour sélectionner le comportement, soit une nouvelle fonction ( reduce_intervals ? reduce_segments ? ...?) Éviterait de casser l'incompatibilité vers l'arrière.

Je serais peut-être tenté de déprécier np.ufunc.reduceat alltogether - il semble plus utile de pouvoir spécifier un ensemble d'indices de début et de fin, pour éviter les cas où indices[i] > indices[i+1] . De plus, le nom at suggère une bien plus grande similitude avec at qu'il n'en existe actuellement

Ce que je proposerais en remplacement est np.piecewise_reduce np.reducebins , peut-être pur-python, ce qui fait essentiellement:

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

Qui a les belles propriétés que:

  • np.add.reduce(arr) équivaut à np.piecewise_reduce(np.add, arr, 0, len(arr))
  • np.add.reduceat(arr, inds) équivaut à np.piecewise_reduce(np.add, arr, inds)
  • np.add.accumulate(arr) équivaut à np.piecewise_reduce(np.add, arr, 0, np.arange(len(arr)))

Maintenant, cela veut-il passer par la machinerie __array_ufunc__ ? La plupart de ce qui doit être géré devrait déjà être couvert par func.reduce - le seul problème est la ligne np.empty , qui est un problème que np.concatenate partage.

Cela me semble être une bonne solution du point de vue de l'API. Même être capable de spécifier deux ensembles d'indices à reduceat suffirait. Du point de vue de la mise en œuvre? Eh bien, il n'est pas très difficile de changer le PyUFunc_Reduceat actuel pour prendre en charge deux ensembles d'inds, si cela offre un avantage. Si nous voyons vraiment l'avantage de prendre en charge efficacement le cas d'utilisation de type accumulation, ce ne serait pas difficile à faire non plus.

Marten a proposé quelque chose de similaire dans une discussion similaire de ~ 1
il y a un an, mais il a également évoqué la possibilité d'ajouter une option «step»:

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

Ce que j'aime (donc +1 si quelqu'un compte) de votre proposition:

  • Créer une nouvelle fonction, plutôt que d'essayer de récupérer l'existant
    une.
  • Rendre les arguments d'indices de début et de fin spécifiques, plutôt que
    les trouver comme par magie à partir d'un tableau multidimensionnel.
  • Les valeurs par défaut des indices None sont très soignées.

Les choses auxquelles je pense qu'il est important de réfléchir sérieusement pour cette nouvelle fonction:

  • Devrions-nous faire de «l'étape» une option? (Je dirais oui)
  • Est-il logique que les tableaux d'indices diffusent ou doivent-ils
    être 1D?
  • Cela devrait-il être une fonction np ou une méthode ufunc? (Je pense que je le préfère
    comme méthode)

Et du département de délestage de vélos, j'aime mieux:

  • Donnez-lui un nom plus mémorable, mais je n'ai aucune proposition.
  • Utilisez 'start' et 'stop' (et 'step' si nous décidons d'y aller) pour
    cohérence avec np.arange et la tranche de Python.
  • Suppression des _indices des noms kwarg.

Jaime

Le jeu.13 avril 2017 à 13:47, Eric Wieser [email protected]
a écrit:

Je serais peut-être tenté de désapprouver np.ufunc.reduceat alltogether - il
semble plus utile pour pouvoir spécifier un ensemble d'indices de début et de fin, pour
éviter les cas où indices [i]> indices [i + 1]. En outre, le nom suggère un
similitude beaucoup plus grande que celle qui existe actuellement

Ce que je proposerais en remplacement est np.piecewise_reduce, qui fondamentalement
Est-ce que:

def par morceaux_reduce (func, arr, start_indices = None, end_indices = None, axis = -1, out = None):
si start_indices vaut None et end_indices vaut None:
start_indices = np.array ([0], dtype = np.intp)
end_indices = np.array (arr.shape [axe], dtype = np.intp)
elif end_indices est None:
end_indices = np.empty_like (start_indices)
end_indices [: - 1] = start_indices [1:]
end_indices [-1] = arr.shape [axe]
elif start_indices est None:
start_indices = np.empty_like (end_indices)
start_indices [1:] = end_indices
end_indices [0] = 0
autre:
assert 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

Qui a les belles propriétés que:

  • np.ufunc.reduce est identique à np.piecewise_reduce (func, arr, 0,
    len (arr))
  • np.ufunc.accumulate est identique à `np.piecewise_reduce (func, arr,
    np.zeros (len (arr)), np.arange (len (arr)))

Maintenant, est-ce que cela veut passer par la machinerie__array_ufunc__? La plupart de
ce qui doit être géré doit déjà être couvert par func.reduce - le
le seul problème est la ligne np.empty, qui est un problème que np.concatenate
actions.

-
Vous recevez ceci parce que vous êtes abonné à ce fil.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/834#issuecomment-293867746 , ou muet
le fil
https://github.com/notifications/unsubscribe-auth/ADMGdtjSCodONyu6gCpwofdBaJMCIKa-ks5rvgtrgaJpZM4ANcqc
.

-
(__ /)
(Oo)
(> <) Este es Conejo. Copia a Conejo en tu firma y ayúdale en sus avions
de dominación mundial.

Utilisez 'start' et 'stop'

Terminé

Devrions-nous faire de `` l'étape '' une option

Cela semble être un cas d'utilisation assez étroit

Est-il judicieux que les tableaux d'indices soient diffusés ou doivent-ils être 1D

Mis à jour. > 1d est évidemment mauvais, mais je pense que nous devrions autoriser 0d et la diffusion, pour des cas comme l'accumulation.

Cela devrait-il être une fonction np ou une méthode ufunc? (Je pense que je le préfère
comme méthode)

Chaque méthode ufunc est une chose de plus pour __array_ufunc__ à gérer.

La motivation principale de reduceat est d'éviter une boucle de plus de reduce pour une vitesse maximale. Je ne suis donc pas tout à fait sûr qu'un wrapper d'une boucle for sur reduce serait un ajout très utile à Numpy. Cela irait à l'encontre reduceat objectif principal de

De plus, la logique reduceat existence de reduce , est propre et utile. Je ne le déprécierais pas, mais le réparerais plutôt.

En ce qui concerne la vitesse de reduceat , considérons un exemple simple, mais similaire à certains cas du monde réel que j'ai dans mon propre code, où j'utilise 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

Il s'agit d'un décalage horaire de plus de 100x et illustre l'importance de préserver l'efficacité de reduceat .

En résumé, je donnerais la priorité à la correction de reduceat plutôt qu'à l'introduction de nouvelles fonctions.

Avoir un start_indices et end_indices , bien que utile dans certains cas, est souvent redondant et je le verrais comme un ajout possible, mais pas comme un correctif pour l'actuel reduceat incohérent comportement.

Je ne pense pas que les index de démarrage et d'arrêt proviennent de différents tableaux
ferait une grande différence en termes d'efficacité s'il était mis en œuvre dans le C.

Le 13 avril 2017 à 23h40, divenex [email protected] a écrit:

La principale motivation pour reduceat est d'éviter une boucle sur
vitesse maximum. Donc, je ne suis pas tout à fait sûr que le wrapper d'une boucle for over
réduire serait un ajout très utile à Numpy. Ça irait contre
réduire l'objectif principal.

De plus la logique pour réduire l'existence et l'API, en tant que vecteur rapide
remplacement pour une boucle sur réduire, est propre et utile. je ne voudrais pas
dépréciez-le, mais corrigez-le plutôt.

En ce qui concerne la vitesse de réduction, considérons un exemple simple, mais similaire à
quelques cas du monde réel que j'ai dans mon propre code, où j'utilise reduceat:

n = 10 000
arr = np.random.random (n)
inds = np.random.randint (0, n, n // 10)
inds.sort ()
% timeit out = np.add.reduceat (arr, inds) 10000 boucles, meilleur de 3: 42,1 µs par boucle
% timeit out = par morceaux_reduce (np.add, arr, inds) 100 boucles, meilleur de 3: 6,03 ms par boucle

Il s'agit d'un décalage horaire de plus de 100x et illustre l'importance
de préserver l'efficacité de la réduction.

En résumé, je donnerais la priorité à la correction de la réduction par rapport à l'introduction de nouveaux
les fonctions.

Avoir un start_indices et end_indices, bien que utile dans certains cas, est
souvent redondant et je le verrais comme un ajout possible, mais pas comme un correctif
pour la réduction actuelle des comportements incohérents.

-
Vous recevez ceci parce que vous avez commenté.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/834#issuecomment-293898215 , ou muet
le fil
https://github.com/notifications/unsubscribe-auth/AAEz6xPex0fo2y_MqVHbNP5YNkJ0CBJrks5rviW-gaJpZM4ANcqc
.

Il s'agit d'une différence de temps de plus de 100x et illustre l'importance de préserver l'efficacité de la réduction.

Merci pour cela - je suppose que j'ai sous-estimé les frais généraux associés à la première étape d'un appel reduce (cela n'arrive qu'une seule fois pour reduceat ).

Pas un argument contre une fonction libre, mais certainement un argument contre son implémentation en python pur

mais pas comme un correctif pour le comportement incohérent de réduction actuel.

Le problème est qu'il est difficile de changer le comportement du code qui existe depuis si longtemps.


Autre extension possible: quand indices[i] > indices[j] , calculez l'inverse:

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

np.add.inverse = np.subtract , np.multiply.inverse = np.true_divide . Il en résulte la belle propriété qui

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

Par exemple

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

Le problème est qu'il est difficile de changer le comportement du code qui existe depuis si longtemps.

C'est en partie pourquoi, dans le fil de discussion, j'ai suggéré de donner une signification particulière à un tableau d'indices en 2D dans lequel la dimension supplémentaire est 2 ou 3: il est alors (effectivement) interprété comme une pile de tranches. Mais je me rends compte que c'est aussi un peu compliqué et bien sûr, on pourrait aussi bien avoir une méthode reduce_by_slice , slicereduce ou reduceslice .

ps Je pense que tout ce qui fonctionne sur de nombreux ufuncs devrait être une méthode, de sorte qu'il puisse être passé par __array_ufunc__ et être remplacé.

En fait, une suggestion différente que je pense est bien meilleure: plutôt que de récupérer reduceat , pourquoi ne pas ajouter un argument slice (ou start , stop , step ) à ufunc.reduce !? Comme @ eric-wieser l'a noté, une telle implémentation signifie que nous pouvons simplement désapprouver reduceat , comme ce serait juste

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

(où maintenant nous sommes libres de faire correspondre le comportement à ce qui est attendu pour une tranche vide)

Ici, on diffuserait la tranche si elle était 0-d, et on pourrait même envisager de passer en tuples de tranches si un tuple d'axes était utilisé.

EDIT: faites ce qui précède slice(indices[:-1], indices[1:]) pour permettre l'extension à un tuple de tranches ( slice peut contenir des données arbitraires, donc cela fonctionnerait bien).

Je trouverais toujours une solution à reduceat , pour en faire une bonne version 100% vectorisée de reduce , la solution de conception la plus logique. Alternativement, pour éviter de casser le code (mais voir ci-dessous), une méthode équivalente nommée comme reducebins pourrait être créée, qui est simplement une version corrigée de reduceat . En fait, je suis d'accord avec @ eric-wieser que la dénomination de reduceat donne plus de connexion à la fonction at qu'il n'y en a.

Je comprends la nécessité de ne pas casser le code. Mais je dois dire que j'ai du mal à imaginer qu'une grande partie du code dépendait de l'ancien comportement, étant donné que cela n'avait tout simplement pas de sens logique, et je l'appellerais simplement un bogue de longue date. Je m'attendrais à ce que le code utilisant reduceat vérifie simplement que indices n'est pas dupliqué, pour éviter un résultat absurde de reduceat , ou corrige la sortie comme je l'ai fait en utilisant out[:-1] *= np.diff(indices) > 0 . Bien sûr, je serais intéressé par un cas d'utilisateur où l'ancien comportement / bogue a été utilisé comme prévu.

Je ne suis pas entièrement convaincu de la solution @mhvk slice car elle introduit une utilisation non standard pour la construction slice . De plus, cela serait incompatible avec l'idée de conception actuelle de reduce , qui consiste à _ "réduire la dimension de a de un, en appliquant ufunc le long d'un axe." _

Je ne vois pas non plus de cas d'utilisation convaincant pour les indices start et end . En fait, je vois la belle logique de conception de la méthode actuelle reduceat conceptuellement similaire à np.histogram , où bins , qui _ "définit les bords du bac", _ sont remplacés par indices , qui représentent également les bords des bins, mais dans l'espace d'index plutôt que dans la valeur. Et reduceat applique une fonction aux éléments contenus à l'intérieur de chaque paire d'arêtes bins. L'histogramme est une construction extrêmement populaire, mais il n'a pas besoin, et dans Numpy n'inclut pas, une option pour passer deux vecteurs d'arêtes gauche et droite. Pour la même raison, je doute qu'il y ait un besoin fort pour les deux bords dans reduceat ou son remplacement.

La motivation principale de la réduction est d'éviter une boucle de réduction pour une vitesse maximale. Je ne suis donc pas tout à fait sûr qu'un wrapper d'une boucle for over reduction serait un ajout très utile à Numpy. Cela irait à l'encontre de la réduction de l'objectif principal.

Je suis d'accord avec @divenex ici. Le fait que reduceat nécessite que les index soient triés et se chevauchent est une contrainte raisonnable pour garantir que la boucle peut être calculée de manière efficace en cache avec un seul passage sur les données. Si vous souhaitez que les bins se chevauchent, il existe certainement de meilleures façons de calculer l'opération souhaitée (par exemple, des agrégations de fenêtres déroulantes).

Je conviens également que la solution la plus propre est de définir une nouvelle méthode telle que reducebins avec une API fixe (et de déconseiller reduceat ), et de ne pas essayer de la presser dans reduce qui fait déjà quelque chose de différent.

Salut à tous,

Je veux étouffer la discussion sur le fait qu'il s'agit d'un bug. Il s'agit du comportement documenté de la 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.

En tant que tel, je m'oppose à toute tentative de modification du comportement de reduceat .

Une recherche rapide sur github montre de très nombreuses utilisations de la fonction. Est-ce que tout le monde ici est certain de n'utiliser que des indices strictement croissants?

En ce qui concerne le comportement d'une nouvelle fonction, je dirais que sans tableaux de démarrage / arrêt séparés, la fonctionnalité est gravement entravée. Il existe de nombreuses situations où l'on voudrait mesurer des valeurs dans des fenêtres qui se chevauchent qui ne sont pas régulièrement mises en réseau (les fenêtres déroulantes ne fonctionneraient donc pas). Par exemple, les régions d'intérêt déterminées par une méthode indépendante. Et @divenex a montré que la différence de performances par rapport à l'itération Python peut être énorme.

Il existe de nombreuses situations où l'on voudrait mesurer des valeurs dans des fenêtres qui se chevauchent qui ne sont pas régulièrement mises en réseau (les fenêtres déroulantes ne fonctionneraient donc pas).

Oui, mais vous ne voudriez pas utiliser une boucle naïve telle que celle implémentée par reduceat . Vous voudriez implémenter votre propre calcul de fenêtre glissante stockant les résultats intermédiaires d'une manière ou d'une autre afin que cela puisse être fait en un seul passage linéaire sur les données. Mais maintenant, nous parlons d'un algorithme beaucoup plus compliqué que reduceat .

@shoyer Je peux imaginer des cas où seuls certains des retours sur investissement se chevauchent. Dans de tels cas, écrire un algorithme personnalisé serait une exagération énorme. N'oublions pas que notre base d'utilisateurs principale est constituée de scientifiques, qui manquent généralement de temps et ont besoin d'une solution «assez bonne», et non de l'optimum absolu. Les faibles facteurs constants associés à la complexité de np.reduceat signifient qu'il serait difficile, voire impossible, d'obtenir une meilleure solution avec du code Python pur - le plus souvent, le seul code que les utilisateurs sont prêts à écrire.

@jni Bien sûr, la réduction en groupes avec des démarrages et des arrêts arbitraires pourrait être utile. Mais cela me semble être une augmentation significative de la portée, et quelque chose de mieux adapté à une autre méthode plutôt qu'un remplacement de reduceat (que nous voulons certainement déconseiller, même si nous ne le supprimons jamais).

la réduction en groupes avec des démarrages et des arrêts arbitraires pourrait être utile. Mais cela me semble être une augmentation significative de la portée

Cela me semble très trivial. À l'heure actuelle, nous avons du code qui fait essentiellement ind1 = indices[i], ind2 = indices[i + 1] . Changer cela pour utiliser deux tableaux différents au lieu du même ne devrait demander que très peu d'effort.

Et le comportement en un seul passage lorsqu'il est passé dans des plages contiguës devrait être presque exactement aussi rapide qu'il l'est actuellement - le seul surcoût est un argument de plus pour le nditer

Cela me semble très trivial.

Exactement. De plus, c'est une fonctionnalité que les utilisateurs ont avec reduceat (en utilisant tous les autres index), mais perdrait avec une nouvelle fonction qui ne prend pas en charge le chevauchement.

De plus, une forme à deux index pourrait émuler l'ancien comportement (bizarre):

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)

Cela signifie que nous n'avons pas besoin de maintenir deux implémentations très similaires

Je ne suis pas fortement contre les indices starts et stops pour les nouveaux reducebins , bien que je ne puisse toujours pas voir un exemple évident où ils sont tous les deux nécessaires. C'est comme généraliser np.histogram en ajoutant des arêtes de début et de fin bins ...

En fin de compte, cela convient tant que l'utilisation principale n'est pas affectée et que l'on peut toujours appeler reducebins(arr, indices) avec un seul tableau d'indices et sans pénalité de vitesse.

Bien sûr, il existe de nombreuses situations où l'on doit opérer sur des bacs non chevauchants, mais dans ce cas, je m'attendrais généralement à ce que les bacs ne soient pas définis uniquement par des paires d'arêtes. Une des fonctions disponibles pour ce genre de scénario est ndimage.labeled_comprehension de Scipy, et les fonctions associées comme ndimage.sum et ainsi de suite.

Mais cela semble assez différent de la portée de reducebins .

Alors, quel serait un cas d'utilisation naturel pour starts et stops dans reducebins ?

Alors, quel serait un cas d'utilisation naturel pour les démarrages et les arrêts dans les réducteurs?

Réalisable par d'autres moyens, mais une moyenne mobile de longueur k serait reducebins(np,add, arr, arange(n-k), k + arange(n-k)) . Je soupçonne qu'en ignorant le coût d'allocation des indices, les performances seraient comparables à une approche as_strided .

Uniquement, reducebins permettrait une moyenne mobile de durée variable, ce qui n'est pas possible avec as_strided

Un autre cas d'utilisation - sans ambiguïté entre l'inclusion de la fin ou du début sous la forme à un argument.

Par exemple:

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

Un autre cas d'utilisation - sans ambiguïté entre l'inclusion de la fin ou du début sous la forme à un argument.

Je ne comprends pas très bien celui-ci. Pouvez-vous inclure le tenseur d'entrée ici? Aussi: quelles seraient les valeurs par défaut pour start / stop ?

Quoi qu'il en soit, je ne suis pas fermement contre les arguments séparés, mais ce n'est pas aussi propre qu'un remplacement. J'adorerais pouvoir dire "N'utilisez pas reduceat, utilisez plutôt reductionbins" mais c'est (légèrement) plus difficile quand l'interface est différente.

En fait, je viens de réaliser que même une option start / stop ne couvre pas le cas d'utilisation des tranches vides, ce qui m'a été utile dans le passé: quand mes propriétés / étiquettes correspondent à des lignes dans une matrice creuse CSR, et j'utilise les valeurs de indptr pour faire la réduction. Avec reduceat , je peux ignorer les lignes vides. Tout remplacement nécessitera une comptabilité supplémentaire. Donc, quel que soit le remplacement que vous proposez, veuillez laisser reduceat autour.

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

Incidemment, l'énigme de la séquence vide peut être résolue de la même manière que Python le fait : en fournissant une valeur initiale. Cela peut être un scalaire ou un tableau de la même forme que indices .

oui, je suis d'accord que le premier objectif doit être de résoudre les tranches vides
Cas. Dans le cas de start = end, nous pouvons soit avoir un moyen de définir la sortie
élément à l'identité, ou pour ne pas modifier l'élément de sortie avec un
spécifié hors tableau. Le problème avec le courant est qu'il est écrasé
avec des données non pertinentes

Je suis entièrement avec @shoyer à propos de son dernier commentaire.

Définissons simplement out=ufunc.reducebins(a, inds) comme out[i]=ufunc.reduce(a[inds[i]:inds[i+1]]) pour tout i sauf le dernier, et déprécions reduceat .

Les cas d'utilisation actuels des indices starts et ends semblent plus naturellement et probablement plus efficacement implémentés avec des fonctions alternatives comme as_strided ou des convolutions.

@shoyer :

Je ne comprends pas très bien celui-ci. Pouvez-vous inclure le tenseur d'entrée ici? Aussi: quelles seraient les valeurs par défaut pour démarrer / arrêter?

Mis à jour avec l'entrée. Voir l'implémentation de reduce_bins dans le commentaire qui a commencé ceci pour les valeurs par défaut. J'ai également ajouté un docstring là-bas. Cette implémentation est complète mais lente (car elle est python).

mais c'est (légèrement) plus difficile lorsque l'interface est différente.

Quand un seul argument start est passé, l'interface est identique (en ignorant le cas d'identité que nous avons décidé de corriger en premier lieu). Ces trois lignes signifient la même chose:

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

(la distinction méthode / fonction n'est pas quelque chose qui me préoccupe trop, et je ne peux pas définir une nouvelle méthode ufunc comme un prototype en python!)


@jni :

En fait, je viens de réaliser que même une option start / stop ne couvre pas le cas d'utilisation des tranches vides, ce qui m'a été utile dans le passé

Vous vous trompez, c'est le cas - exactement de la même manière que ufunc.reduceat déjà. C'est aussi possible simplement en passant start[i] == end[i] .

l'énigme de la séquence vide peut être résolue ... en fournissant une valeur initiale.

Oui, nous avons déjà couvert cela, et ufunc.reduce déjà en remplissant avec ufunc.identity . Ce n'est pas difficile à ajouter au ufunc.reduecat existant, surtout si # 8952 est fusionné. Mais comme vous l'avez dit vous-même, le comportement actuel est _documenté_, nous ne devrions donc probablement pas le changer.


@divenex

Définissons simplement out = ufunc.reducebins (a, inds) comme out [i] = ufunc.reduce (a [inds [i]: inds [i + 1]]) pour tout i sauf le dernier

Alors len(out) == len(inds) - 1 ? Ceci est différent du comportement actuel de reduceat , donc l'argument de @shoyer sur le changement est plus fort ici


Tous: J'ai parcouru les commentaires précédents et supprimé les réponses aux e-mails cités, car ils rendaient cette discussion difficile à lire

@ eric-wieser bon point. Dans ma phrase ci-dessus, je voulais dire que pour le dernier index, le comportement de reducebins serait différent de celui du reduceat actuel. Cependant, dans ce cas, je ne sais pas quelle devrait être la valeur, car la dernière valeur n'a formellement aucun sens.

En ignorant les problèmes de compatibilité, la sortie de reducebins (en 1D) devrait avoir la taille inds.size-1 , pour la même raison que np.diff(a) a la taille a.size-1 et np.histogram(a, bins) a la taille bins.size-1 . Cependant, cela irait à l'encontre du désir d'avoir un remplacement instantané pour reduceat .

Je ne pense pas qu'il y ait un argument convaincant selon lequel a.size-1 est la bonne réponse - y compris l'index 0 et / ou l'index n semble également être un comportement assez raisonnable. Tous semblent pratiques dans certaines circonstances, mais je pense qu'il est très important d'avoir une baisse de remplacement.

Il y a aussi un autre argument pour stop / start cache ici - il vous permet de créer le comportement semblable à diff si vous le souhaitez, avec très peu de frais, tout en conservant le reduceat comportement:

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 Je serais d'accord avec les arguments start et stop , mais je n'aime pas en faire un optionnel. Il n'est pas évident que fournir seulement start signifie out[i] = func.reduce(arr[start[i]:start[i+1]]) plutôt que out[i] = func.reduce(arr[start[i]:]) , ce que j'aurais deviné.

Mon API préférée pour reducebins est comme reduceat mais sans les "exceptions" déroutantes notées dans la docstring . À savoir, juste:

Pour i in range(len(indices)) , reduceat calcule ufunc.reduce(a[indices[i]:indices[i+1]]) , qui devient la i-ième «ligne» généralisée parallèle à l'axe dans le résultat final (c'est-à-dire dans un tableau 2D, par exemple, si axis = 0, il devient la i-ème ligne, mais si axis = 1, il devient la i-ème colonne).

Je pourrais aller dans les deux sens sur la troisième "exception" qui nécessite des indices non négatifs ( 0 <= indices[i] <= a.shape[axis] ), que je considère plus comme une vérification de cohérence que comme une exception. Mais peut-être que l'on pourrait y aller aussi - je peux voir à quel point les indices négatifs pourraient être utiles à quelqu'un, et il n'est pas difficile de faire le calcul pour normaliser de tels indices.

Ne pas ajouter automatiquement un index à la fin implique que le résultat doit avoir une longueur len(a)-1 , comme le résultat de np.histogram .

@jni Pouvez-vous donner un exemple de ce que vous voulez réellement calculer à partir de tableaux trouvés dans des matrices clairsemées? De préférence avec un exemple concret avec des nombres non aléatoires, et autonome (sans dépendre de scipy.sparse).

Il n'est pas évident que fournir uniquement start signifie out [i] = func.reduce (arr [start [i]: start [i + 1]]) plutôt que out [i] = func.reduce (arr [start [i] :]), ce que j'aurais deviné.

La lecture que je recherchais est que "Chaque bac commence à ces positions", avec l'implication que tous les bacs sont contigus, sauf indication contraire explicite. Je devrais peut-être essayer de rédiger une docstring plus complète. Je pense que je peux voir un argument fort pour interdire de passer aucun des arguments, donc je vais supprimer cela de ma fonction de proposition.

qui nécessite des indices non négatifs (0 <= indices [i] <a.shape [axis])

Notez qu'il y a aussi un bogue ici (# 835) - la limite supérieure devrait être inclusive, car ce sont des tranches.

Notez qu'il y a aussi un bogue ici - la limite supérieure doit être inclusive, car ce sont des tranches.

Fixé, merci.

Pas dans la fonction reduceat elle-même, vous ne l'avez pas;)

Il s'avère que :\doc\neps\groupby_additions.rst contient une proposition (inférieure à l'OMI) pour une fonction reduceby .

Cette page vous a été utile?
0 / 5 - 0 notes