Two behaviours to know before trusting the answer. The second one is the dangerous one.
import numpy as np
grid = np.array([[1, 9], [9, 2]])
np.argmax(grid) # -> 1 a position in the FLATTENED array
# not (0, 1) — one number, counting row by row
np.unravel_index(np.argmax(grid), grid.shape) # -> (0, 1)
# translates a flat position back into one index per dimension
# Ties: the first one wins, reading row by row. Both 9s, and it says (0, 1).
np.argmax(np.array([1.0, float('nan'), 3.0])) # -> 1
# it points at the nan, because every comparison with nan is False and
# nothing ever beats it. The 3.0 is simply not found.
- argmax counts through the flattened array
It returns one number however many dimensions the array has, because it walks the buffer in order. On a 2-D array that number is row * width + column, which is rarely what you want to report. np.unravel_index turns it back into a tuple using the shape — and the reverse, np.ravel_multi_index, exists for when you need to go the other way.
- nan is found, not skipped
Every comparison involving nan is False, so a running 'is this bigger than the best so far' never displaces a nan once it has been seen — argmax returns its position and the genuine maximum goes unreported. There is np.nanargmax for ignoring them, and it raises when everything is nan. Either way the decision has to be made deliberately.
- Ties go to the first, and that is documented rather than incidental
With two equal maxima argmax returns the lower flat index, which reading row by row means the earliest. That is worth relying on — it is specified — and it means 'the best row' is stable when several rows tie, which matters for a leaderboard that must not shuffle between runs.
Two guards, then two calls. argmax gives you a single number; something has to turn that into one index per dimension.
np.unravel_index(np.argmax(values), values.shape) does the translation. Check for nan with np.isnan(...).any() first — but only for float arrays, because isnan raises on some integer and object dtypes.
Refuse the empty array before anything else, since argmax raises on it with a message about sequences. Then wrap each index in int() — unravel_index returns numpy integers, and a tuple of them is not quite ordinary data.
import numpy as np def best_position(values): if values.size == ____: raise ValueError('no values, so there is no largest one') if values.dtype.kind == 'f' and np.isnan(values).____(): raise ____('cannot find the largest value: some are missing') flat = np.____(values) position = np.____(flat, values.shape) return tuple(____(index) for index in position)
import numpy as np
def best_position(values):
if values.size == 0:
raise ValueError("no values, so there is no largest one")
# Only float arrays can hold nan, and isnan refuses some other dtypes.
if values.dtype.kind == "f" and np.isnan(values).any():
raise ValueError(
"cannot find the largest value while some are missing"
)
# argmax counts through the flattened array; unravel_index turns that
# single number back into one index per dimension.
flat = np.argmax(values)
position = np.unravel_index(flat, values.shape)
return tuple(int(index) for index in position)
np.isnan raises a TypeError on integer object arrays and on some string dtypes, so calling it unconditionally trades one problem for another. Checking `dtype.kind == 'f'` first is both faster and safer: an int array cannot contain nan, so there is nothing to look for. The same guard shape appears any time a check only applies to some types.
Computing the row and column by hand — divmod(flat, width) — works for exactly two dimensions and is wrong for one or three. unravel_index takes the shape and returns as many indices as there are dimensions, which is why the same function serves a flat array and a 3-D volume with no branches.
numpy integers index arrays perfectly well and then surprise somebody serialising them. A tuple rather than a list says the length is fixed by the array’s dimensions — and it is exactly what values[position] accepts, so the result can be fed straight back in to read the value.
If you want the value rather than the position, .max() is the call and this is a detour. If you want the top few rather than the single best, argsort or np.argpartition are the tools — argpartition is the right one for 'the best ten of a million', because it does not sort what it does not have to. And if missing values are expected rather than a fault, np.nanargmax ignores them: this function refuses instead because a position is usually about to be used to look something up, and looking up the wrong row is worse than stopping.
- Return the value alongside the position, and notice that values[position] does it in one expression because the result is a tuple.
- Switch to np.nanargmax and work out what it does when every value is missing — then decide which behaviour your caller wants.
- Find the best position within each row rather than overall, using axis=1, and see why the result no longer needs unravelling.