3

These two look like they should be very much equivalent and therefore what works for one should work for the other? So why does accumulate only work for maximum but not argmax?

EDIT: A natural follow-up question is then how does one go about creating an efficient argmax accumulate in the most pythonic/numpy-esque way?

RAY
  • 6,810
  • 6
  • 40
  • 67

2 Answers2

4

Because max is associative, but argmax is not:

  • max(a, max(b, c)) == max(max(a, b), c)
  • argmax(a, argmax(b, c)) != argmax(argmax(a, b), c)
Eric
  • 95,302
  • 53
  • 242
  • 374
  • Because in my mind, the way that this argmax accumulate could work easily by keeping track of what the current/last max is, but you hit the nail on the head that the difference is that the "last max" is exactly the value of maximum so far, but not for argmax. – RAY Jun 16 '16 at 09:40
  • 1
    As a side note, `np.argmax` is not a ufunc, along with `np.max`. Note that the absent `np.argmaximum` is better spelt `<` – Eric Jun 16 '16 at 09:43
  • 1
    `np.max` is basically an alias for `np.maximum.reduce`. – Jaime Jun 16 '16 at 10:47
  • @Eric can you elaborate please? How is the non associativity an issue? I don't even see why anyone would consider something like `argmax(a, argmax(b, c))` in the first place since such thing looks like total non sense to me... How is keeping track of the index where the cumulative max is reached any harder than that cumulative max itself? How would `argmax` work without anyway internally doing a cumulative argmax as it iterates through the array? – Julien Dec 11 '18 at 06:48
  • 1
    @Julien: `y = f.accumulate(x)` is defined as `y[i] = f.reduce(x[:i+1])`, and `f.reduce(x)` is defined as `f(x[0], f.reduce(x[1:])`. When `f` is `np.maximum` or `np.add`, those formulations make sense. When `f` is `np.argmin`, then those are nonsense, which is exactly why `argmin.accumulate` doesn't exist. – Eric Dec 11 '18 at 06:59
  • Ok for generic `.accumulate` not working here. But is there any good reason why no "ad hoc" accumulate functionality is provided by numpy? Is my intuition that this would be trivial too naive? Of course you can go around using @hpaulj's hack, but it seems both cumbersome and inefficient (doing multiple passes through arrays, and creating a few temp arrays too, when really only a single pass should suffice right?) Would that be a valid feature request to numpy developpers? Or am I missing something? – Julien Dec 11 '18 at 22:50
2

Is this the kind of argmax accumulate you want?

sample array:

In [135]: a
Out[135]: array([4, 6, 5, 1, 4, 4, 2, 0, 8, 4])

the maximum that you already got:

In [136]: am=np.maximum.accumulate(a)    
In [137]: am
Out[137]: array([4, 6, 6, 6, 6, 6, 6, 6, 8, 8], dtype=int32)

In [138]: a1=np.zeros_like(a)

identify the elements where the am jumped. np.diff would have also worked:

In [139]: ind=np.nonzero(a==am)[0]

In [140]: ind
Out[140]: array([0, 1, 8], dtype=int32)

In [141]: a1[ind]=ind    
In [142]: a1
Out[142]: array([0, 1, 0, 0, 0, 0, 0, 0, 8, 0])

In [143]: np.maximum.accumulate(a1)
Out[143]: array([0, 1, 1, 1, 1, 1, 1, 1, 8, 8], dtype=int32)

Alternate way of find ind - looking for the jumps in am

In [149]: ind=np.nonzero(np.diff(am))

In [150]: ind = np.concatenate([[0],ind[0]+1])

In [151]: ind
Out[151]: array([0, 1, 8])
hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • please have a look at my comment above in case you have any insight :) – Julien Dec 11 '18 at 22:52
  • @Julien `numpy` gets its speed by providing compiled building blocks, which you can combine to perform more complex operations. That may well require multiple passes. and temp arrays. My `In[139]` calc involves several passes, at least one to do `a==am`, another to count the number of nonzeros, and another to accumulated those indices in an array of the right size. – hpaulj Dec 11 '18 at 23:12