# \`findmax\` and friends: confusing behaviour to be introduced in 1.7

**URL:** https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904
**Category:** Internals & Design
**Created:** [May 27, 2021, 7:23am UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904 "2021-05-27T07:23:32Z")
**Posts on this page:** 17
**Page:** 5

<div class="post-metadata">

### Author: ![jzr](https://avatars.discourse-cdn.com/v4/letter/j/eb9ed0/32.png) [@jzr](https://discourse.julialang.org/u/jzr)
#### Post date: [June 23, 2021, 7:57am UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/81 "2021-06-23T07:57:07Z")

</div>

> [@DNF](#):
>
> `argmax(A)` should be removed and replaced with something with a better name (in the long term, of course.)

I agree it should be removed but I don’t think it should be replaced. There are too many functions in Base, and this one is definitely confusing. If it’s gonna be anywhere, it should be in a package.

---

<div class="post-metadata">

### Author: ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)
#### Post date: [June 23, 2021, 8:19am UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/82 "2021-06-23T08:19:05Z")

</div>

basically, given these four operations:

```julia
# in v1.7 beta
julia> a = [1,3,2];

julia> findmax(a) #1
(3, 2)

julia> findmax(inv, a) #2
(1.0, 1)

julia> argmax(a) #3
2

julia> argmax(inv, a) #4
1

```

we want to be able to get these pieces directly:

1. the biggest value in the data (✅, #1)
2. the index of 1. (✅, #1 or #3)
3. the biggest evaluated value in data after evaluation. (✅ , #2 )
4. the index of 3. (✅, #2)
5. the original value of 3. (✅, #4)

so I’m fine with this I guess.

---

<div class="post-metadata">

### Author: ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)
#### Post date: [June 23, 2021, 8:22am UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/83 "2021-06-23T08:22:10Z")

</div>

> [@jzr](#):
>
> too many functions in Base, and this one is definitely confusing. If it’s gonna be anywhere, it should be in a package.

Yes, this is the lesson from all of these discussions: once the obvious core functionality is taken care of, API design is becomes increasingly harder and involves iterating on interfaces and learning from mistakes… which is practically impossible or very, very difficult with `Base` (and the standard libraries), since once something end up there it has to be supported.

People mean well when they add new functionality into `Base`, but the bar should be much higher for that than it is currently. IMO politely redirecting all such additions to a package (if feasible) should be the default.

---

<div class="post-metadata">

### Author: ![jeff.bezanson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeff.bezanson/32/48_2.png) [@jeff.bezanson](https://discourse.julialang.org/u/jeff.bezanson)
#### Post date: [June 23, 2021, 3:00pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/84 "2021-06-23T15:00:39Z")

</div>

You are wrong. Read the wikipedia page: [Arg max - Wikipedia](https://en.wikipedia.org/wiki/Arg_max)

It is unambiguous that argmax takes a function and a set, and returns an element of the set (or, all such elements, but programming languages tend not to do that). I don’t care about `argmax(A)`; we could remove that as far as I’m concerned. We just can’t now since it would be breaking. (But note numpy has it, with the same meaning.)

There is no reason to think certain identities need to hold between argmax and findmax. Where is that coming from? A few bad definitions in packages do not overturn a long-standing mathematical convention.

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [June 23, 2021, 4:25pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/85 "2021-06-23T16:25:18Z")

</div>

> [@jeff.bezanson](#):
>
> You are wrong.

Thanks for your friendly and welcome reaction!

> [@jeff.bezanson](#):
>
> It is unambiguous that argmax takes a function and a set, and returns an element of the set  
> \<…\>

This must be the most common argument given by those who come to this thread for the first time. And it was already addressed multiple times, not only by myself.  
For example:

> However, this is only true in isolation. For now, argmax in julia is the function with the only purpose: find index of max in the array. I don’t believe any amount of documentation can change this perception if argmax(A) remains: reaching for it is something many users would try even without reading any docs. And it works! Until they try to apply another pattern learnt from julia, that about reductions-taking-another-function: they may get a reasonable-looking result, but it would be wrong!

> [@jeff.bezanson](#):
>
> There is no reason to think certain identities need to hold between argmax and findmax. Where is that coming from?

None of my arguments are based on identities between argmax and findmax, I believe. Instead, the consistency is between `argmax(A)` and `argmax(f, A)`: all other reductions in julia follow `red(f, A) == red(f.(A))` if both methods are defined. Even `argmax`’s close cousins `findfirst/findmax/maximum`.

I just noticed that your are also present in the slack discussion, so probably better to continue there. It was just not clear for me at first, where the best place is.

---

<div class="post-metadata">

### Author: ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)
#### Post date: [June 23, 2021, 6:25pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/86 "2021-06-23T18:25:26Z")

</div>

> [@aplavin](#):
>
> Thanks for your friendly and welcome reaction!

@aplavin for what it’s worth, I think a few people are starting to get annoyed with the frequency and repetitiveness of the points you’re raising here any in other channels. I don’t know if that’s what coloured Jeff’s comment, but if you’re detecting people being impatient with you, I think this is a likely cause. It feels like your default assumption here when someone disagrees with you, is that they didn’t understand what you said, so you try to say it again. Sometimes though, people just have different priorities or understandings that cause them to reach different conclusions.

You’ve made your same point over and over again many times, in many places, and I think there’s a point where it becomes a bit inappropriate. I don’t know if you’ve reached that point yet, but I feel it’s getting close at least.

I think you’ve raised some good, valuable points here, but I think there’s a point where one has to just see what the decision makes decide after hearing your input, or open a PR to make the changes you want to see and accept the results.

---

<div class="post-metadata">

### Author: ![Skoffer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/skoffer/32/378_2.png) [@Skoffer](https://discourse.julialang.org/u/Skoffer)
#### Post date: [June 23, 2021, 6:33pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/87 "2021-06-23T18:33:03Z")

</div>

I do not understand, can someone enlighten me please?

> Read the wikipedia page: [Arg max - Wikipedia](https://en.wikipedia.org/wiki/Arg_max)
> 
> It is unambiguous that argmax takes a function and a set, and returns an element of the set (or, all such elements, but programming languages tend not to do that).

Arbitrary `Vector`v is not a `Set`, it is a special structure with mapping from indices to values, i.e. i \to v[i] . Or at least it can be treated as such a structure (and indeed we have `getindex()` functions to support this mapping).

With that given, `argmax(f, v::Vector)` can have two different, mathematically correct meanings:

1. argmax(f, v) = \{v\_0 \in v | f(v\_0) = max f(v) \}
2. argmax(f, v) = \{i\_0 \in I | (f \circ \bar{v}) (i\_0) = max f(v)\} where I is a set of indices and \bar{v}(i) defined as \bar{v}: i \to v[i].

So the question is really about definition of argmax over the functions composition, i.e. if we have set A and mappings f: A \to B and g: B \to C, then how one defines `argmax` for g \circ f? It can be either argmax\_{x \in f(A)} g or argmax\_{x \in A} g \circ f. Both definitions are valid and do not contradict Wikipedia definition.

It looks like usability is defined by the context (what user defines as function domain)… It’s just that in case of `Vector` one of the functions is implicit, but that doesn’t make it somehow wrong.

Interestingly enough, but it looks like `argmax` is somewhat pathological function in that sense. For example, `mean` or `sum` do not have this ambiguity, i.e. sum\_{x \in A} g \circ f == sum\_{y \in f(A)} g (at least if f is surjection as in the case of `getindex`). So, as a funny consequence of it, it may be possible that this whole time Julia developers were using second definition, while users thought that the first one is used. And only for `argmax` this distinction became apparent.

---

<div class="post-metadata">

### Author: ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)
#### Post date: [June 23, 2021, 7:01pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/88 "2021-06-23T19:01:31Z")

</div>

> [@Skoffer](#):
>
> Arbitrary `Vector` v vv is not a `Set`

simply because `values(v)` in Julia returns something that has type name `Vector` and happens to be index-able doesn’t mean it’s not compatible, in this context, with a mathematical Set, which only cares about values. In that sense, it is unambiguous: arg max returns the element from the “bag of values” (which are evaluated by function. There’s no need for another set of “index” for this definition

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [June 23, 2021, 7:04pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/89 "2021-06-23T19:04:45Z")

</div>

> [@Mason](#):
>
> I think a few people are starting to get annoyed with the frequency and repetitiveness of the points you’re raising here any in other channels.

I agree this is unfortunate, but I just have to: no related decisions got changed before this topic, even the (really clear-cut!) findmax/findmin one. There was a GH issue before, but with no activity. Only after really reaching everyone around here that decision got changed and luckily we’ll have consistent findmax in 1.7.

It would be much better both for my general feelings and for julia if raising these extended discussions where many things get repeated would not be necessary - but we are where we are.

> It feels like your default assumption here when someone disagrees with you, is that they didn’t understand what you said, so you try to say it again.

Clearly, this may be the case sometimes. But I find it hard to respond in another style when a person makes the very same argument already made several times, that I addressed. At least when doing so it would be great to directly refer to what is wrong in my previous explanations.

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [June 23, 2021, 7:09pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/90 "2021-06-23T19:09:52Z")

</div>

An interesting point you raised!  
Speaking about sets, should we expect stuff like `argmax(abs, Int16) == 32767` to work? 🙂  
Because

> A _concrete_ type `T` describes the set of values whose direct tag, as returned by the [`typeof`](https://docs.julialang.org/en/v1/base/base/#Core.typeof) function, is `T` .

Or maybe `argmax(typemin, Real) == Bool`? Because

> Abstract types cannot be instantiated, and serve only as nodes in the type graph, thereby describing sets of related concrete types

---

<div class="post-metadata">

### Author: ![Skoffer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/skoffer/32/378_2.png) [@Skoffer](https://discourse.julialang.org/u/Skoffer)
#### Post date: [June 23, 2021, 7:12pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/91 "2021-06-23T19:12:48Z")

</div>

Ah, sorry, I put it wrong. Of course `Vector` is set! But it’s much more than just a set, it’s a set plus some additional structure. So my questions are “why this structure is ignored? why we are saying that this structure is wrong? why we are saying that this structure contradicts mathematical definition?”

I mean, Vector is more rich structure than Set in the same sense as group is a semigroup plus two additional axioms. The fact that group is a seimgroup doesn’t mean that we should just ignore group structure, right? We would lose half of the mathematics this way. But why would we want to do it?

It’s perfectly normal to have some theorem and constructions for semigroups only and at the same time we have theorems which utilize group structures. There is nothing wrong with that.

I am not joking, I am really do not understand what is the problem, sorry.

---

<div class="post-metadata">

### Author: ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)
#### Post date: [June 23, 2021, 7:13pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/92 "2021-06-23T19:13:58Z")

</div>

> [@Skoffer](#):
>
> “why this structure is ignored? why we are saying that this structure is wrong? why we are saying that this structure contradicts mathematical definition?”

I will give one reason first: because we want `argmax` to work with actual `Set` and in genera iterators

---

<div class="post-metadata">

### Author: ![tbeason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tbeason/32/15898_2.png) [@tbeason](https://discourse.julialang.org/u/tbeason)
#### Post date: [June 23, 2021, 7:28pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/93 "2021-06-23T19:28:03Z")

</div>

Without re-reading the entire thread (I know I made some _very very_ good points way up there somewhere), I think my observation is that most people appear to agree that the optimal design here cannot be done without breaking changes (especially regarding `argmax(A)`). I feel it is also important to point out that the severity of the issue seems rather low to me. So, the cost / barriers to change are quite substantial yet the payoff not too big…

I agree with a lot of what @aplavin is saying (via my earlier posts), but I also think that we can live with a `findmax(f,A)` definition in Julia 1.x which is counterintuitive to some people (but not all) as long as it is clearly documented. It isn’t broken or wrong in that sense. Perhaps the entire set of `findmax` and friends should be revisited come 2.0 though.

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [June 23, 2021, 7:30pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/94 "2021-06-23T19:30:18Z")

</div>

Thanks to this very topic, the `findmax` inconsistency issue is already resolved! (:

I mean, there are still major differences between e.g. `findfirst` and `findmax` interface, but they are historical artifacts and not in scope here - for better or worse. This topic and issue only address the new behavior to-be-introduced in 1.7.

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [June 1, 2022, 12:38pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/95 "2022-06-01T12:38:34Z")

</div>

About a year has passed, and I got curious how the updated `argmax`+ behavior is supported across the ecosystem. Turns out, even the most popular packages still get the function wrong!

For example, CSV.jl:

```julia
function csv_table(n)
	buf = IOBuffer()
	CSV.write(buf, (x=n:-1:1,))
	seekstart(buf)
	CSV.File(buf) |> columntable
end

# correct: the first element is the largest
julia> argmax(csv_table(10).x)
1

# wrong: the first element is still the largest
julia> argmax(csv_table(10000).x)
10000

```

Note, this bug is in the `argmax(A)` method, but it was introduced when changing `argmax(f, A)` to work like it does in julia 1.7. CSV versions older than a year don’t have this issue.

I’m not writing this post to point towards CSV.jl specifically, this is just a good illustration of how weird the new argmax behavior actually is. Even CSV with a large userbase and good testing didn’t catch that argmax does the wrong thing for a year.

Meanwhile, the sibling `findmax` seems to be correctly implemented in all packages that do it. Makes sense, findmax’s 1- and 2-arg methods are consistent with how basically everything else works in Julia.

---

<div class="post-metadata">

### Author: ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)
#### Post date: [June 1, 2022, 1:34pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/96 "2022-06-01T13:34:03Z")

</div>

I don’t know what a `SentinelArrays.ChainedVector{Int64, Vector{Int64}}` is, but there seems to be a workaround:

```julia
julia> argmax(csv_table(10000).x)
10000

julia> argmax(csv_table(10000).x, dims=1)[]
1

```

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [July 6, 2024, 6:16pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/97 "2024-07-06T18:16:19Z")

</div>

And now, three years after this thread and two years after the error was reported, that specific CSV bug is finally fixed (thanks @mkitti for pushing it recently!) 🙂

The confusing and error-prone `argmax(X)` vs `argmax(f, X)` behavior (introduced in 1.7) was only a part of the story, but I think a significant one. Testing is never perfect and complete, so it’s best to avoid introducing APIs with a strong potential for mistakes.  
Anyway, now Julia has to live with this interface, even though it was noted as inconsistent at least several times during these years…

[Previous page](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904.md?page=4)
