Skip to content

Commit e164738

Browse files
authored
Merge pull request NCAS-CMS#648 from davidhassell/argmin
`Data.argmin` and `Field.argmin` methods
2 parents 217e119 + d92c415 commit e164738

File tree

7 files changed

+235
-10
lines changed

7 files changed

+235
-10
lines changed

Changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ version 3.15.1
33

44
**2023-05-??**
55

6+
* New methods: `cf.Data.argmin`, `cf.Field.argmin`
7+
(https://github.com/NCAS-CMS/cf-python/issues/577)
68
* Fix bug when using the ``-d`` option to the `cfa` script
79
(https://github.com/NCAS-CMS/cf-python/issues/649)
810

cf/data/data.py

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5850,6 +5850,10 @@ def argmax(self, axis=None, unravel=False):
58505850
If the data index is returned as a `tuple` (see the *unravel*
58515851
parameter) then all delayed operations are computed.
58525852
5853+
.. versionadded:: 3.0.0
5854+
5855+
.. seealso:: `argmin`
5856+
58535857
:Parameters:
58545858
58555859
axis: `int`, optional
@@ -5878,7 +5882,7 @@ def argmax(self, axis=None, unravel=False):
58785882
>>> a = d.argmax()
58795883
>>> a
58805884
<CF Data(): 5>
5881-
>>> a.array
5885+
>>> print(a.array)
58825886
5
58835887
58845888
>>> index = d.argmax(unravel=True)
@@ -5903,7 +5907,7 @@ def argmax(self, axis=None, unravel=False):
59035907
>>> print(d.array)
59045908
[[0 1 2]
59055909
[3 5 5]]
5906-
>>> d.argmax(1)
5910+
>>> d.argmax(axis=1)
59075911
<CF Data(2): [2, 1]>
59085912
59095913
"""
@@ -5916,6 +5920,90 @@ def argmax(self, axis=None, unravel=False):
59165920

59175921
return type(self)(a)
59185922

5923+
def argmin(self, axis=None, unravel=False):
5924+
"""Return the indices of the minimum values along an axis.
5925+
5926+
If no axis is specified then the returned index locates the
5927+
minimum of the whole data.
5928+
5929+
In case of multiple occurrences of the minimum values, the
5930+
indices corresponding to the first occurrence are returned.
5931+
5932+
**Performance**
5933+
5934+
If the data index is returned as a `tuple` (see the *unravel*
5935+
parameter) then all delayed operations are computed.
5936+
5937+
.. versionadded:: 3.15.1
5938+
5939+
.. seealso:: `argmax`
5940+
5941+
:Parameters:
5942+
5943+
axis: `int`, optional
5944+
The specified axis over which to locate the minimum
5945+
values. By default the minimum over the flattened data
5946+
is located.
5947+
5948+
unravel: `bool`, optional
5949+
If True then when locating the minimum over the whole
5950+
data, return the location as an integer index for each
5951+
axis as a `tuple`. By default an index to the
5952+
flattened array is returned in this case. Ignored if
5953+
locating the minima over a subset of the axes.
5954+
5955+
:Returns:
5956+
5957+
`Data` or `tuple` of `int`
5958+
The location of the minimum, or minima.
5959+
5960+
**Examples**
5961+
5962+
>>> d = cf.Data(np.arange(5, -1, -1).reshape(2, 3))
5963+
>>> print(d.array)
5964+
[[5 4 3]
5965+
[2 1 0]]
5966+
>>> a = d.argmin()
5967+
>>> a
5968+
<CF Data(): 5>
5969+
>>> print(a.array)
5970+
5
5971+
5972+
>>> index = d.argmin(unravel=True)
5973+
>>> index
5974+
(1, 2)
5975+
>>> d[index]
5976+
<CF Data(1, 1): [[0]]>
5977+
5978+
>>> d.argmin(axis=0)
5979+
<CF Data(3): [1, 1, 1]>
5980+
>>> d.argmin(axis=1)
5981+
<CF Data(2): [2, 2]>
5982+
5983+
Only the location of the first occurrence is returned:
5984+
5985+
>>> d = cf.Data([4, 0, 2, 3, 0])
5986+
>>> d.argmin()
5987+
<CF Data(): 1>
5988+
5989+
>>> d = cf.Data(np.arange(5, -1, -1).reshape(2, 3))
5990+
>>> d[1, 1] = 0
5991+
>>> print(d.array)
5992+
[[5 4 3]
5993+
[2 0 0]]
5994+
>>> d.argmin(axis=1)
5995+
<CF Data(2): [2, 1]>
5996+
5997+
"""
5998+
dx = self.to_dask_array()
5999+
a = dx.argmin(axis=axis)
6000+
6001+
if unravel and (axis is None or self.ndim <= 1):
6002+
# Return a multidimensional index tuple
6003+
return tuple(np.array(da.unravel_index(a, self.shape)))
6004+
6005+
return type(self)(a)
6006+
59196007
@_inplace_enabled(default=False)
59206008
def convert_reference_time(
59216009
self,

cf/field.py

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11750,7 +11750,9 @@ def argmax(self, axis=None, unravel=False):
1175011750
If the data index is returned as a `tuple` (see the *unravel*
1175111751
parameter) then all delayed operations are computed.
1175211752

11753-
.. seealso:: `where`, `cf.Data.argmax`
11753+
.. versionadded:: 3.0.0
11754+
11755+
.. seealso:: `argmin`, `where`, `cf.Data.argmax`
1175411756

1175511757
:Parameters:
1175611758

@@ -11825,6 +11827,97 @@ def argmax(self, axis=None, unravel=False):
1182511827

1182611828
return self.data.argmax(axis=axis, unravel=unravel)
1182711829

11830+
def argmin(self, axis=None, unravel=False):
11831+
"""Return the indices of the minimum values along an axis.
11832+
11833+
If no axis is specified then the returned index locates the
11834+
minimum of the whole data.
11835+
11836+
In case of multiple occurrences of the minimum values, the
11837+
indices corresponding to the first occurrence are returned.
11838+
11839+
**Performance**
11840+
11841+
If the data index is returned as a `tuple` (see the *unravel*
11842+
parameter) then all delayed operations are computed.
11843+
11844+
.. versionadded:: 3.15.1
11845+
11846+
.. seealso:: `argmax`, `where`, `cf.Data.argmin`
11847+
11848+
:Parameters:
11849+
11850+
axis: optional
11851+
Select the domain axis over which to locate the
11852+
minimum values, defined by the domain axis that would
11853+
be selected by passing the given *axis* to a call of
11854+
the field construct's `domain_axis` method. For
11855+
example, for a value of ``'X'``, the domain axis
11856+
construct returned by ``f.domain_axis('X')`` is
11857+
selected.
11858+
11859+
By default the minimum over the flattened data is
11860+
located.
11861+
11862+
unravel: `bool`, optional
11863+
If True then when locating the minimum over the whole
11864+
data, return the location as an integer index for each
11865+
axis as a `tuple`. By default an index to the
11866+
flattened array is returned in this case. Ignored if
11867+
locating the minima over a subset of the axes.
11868+
11869+
:Returns:
11870+
11871+
`Data` or `tuple` of `int`
11872+
The location of the minimum, or minima.
11873+
11874+
**Examples**
11875+
11876+
>>> f = cf.example_field(2)
11877+
>>> print(f)
11878+
Field: air_potential_temperature (ncvar%air_potential_temperature)
11879+
------------------------------------------------------------------
11880+
Data : air_potential_temperature(time(36), latitude(5), longitude(8)) K
11881+
Cell methods : area: mean
11882+
Dimension coords: time(36) = [1959-12-16 12:00:00, ..., 1962-11-16 00:00:00]
11883+
: latitude(5) = [-75.0, ..., 75.0] degrees_north
11884+
: longitude(8) = [22.5, ..., 337.5] degrees_east
11885+
: air_pressure(1) = [850.0] hPa
11886+
11887+
Find the T axis indices of the minimum at each X-Y location:
11888+
11889+
>>> i = f.argmin('T')
11890+
>>> print(i.array)
11891+
[[ 5 14 4 32 11 34 9 27]
11892+
[10 7 4 21 11 10 3 33]
11893+
[21 33 1 30 26 8 33 4]
11894+
[15 10 12 19 23 20 30 25]
11895+
[28 33 31 11 15 12 9 7]]
11896+
11897+
Find the coordinates of the global minimum value, showing that
11898+
it occurs on 1960-03-16 12:00:00 at location 292.5 degrees
11899+
east, -45.0 degrees north:
11900+
11901+
>>> g = f[f.argmin(unravel=True)]
11902+
>>> print(g)
11903+
Field: air_potential_temperature (ncvar%air_potential_temperature)
11904+
------------------------------------------------------------------
11905+
Data : air_potential_temperature(time(1), latitude(1), longitude(1)) K
11906+
Cell methods : area: mean
11907+
Dimension coords: time(1) = [1960-03-16 12:00:00]
11908+
: latitude(1) = [-45.0] degrees_north
11909+
: longitude(1) = [292.5] degrees_east
11910+
: air_pressure(1) = [850.0] hPa
11911+
11912+
See `cf.Data.argmin` for further examples.
11913+
11914+
"""
11915+
if axis is not None:
11916+
axis = self.domain_axis(axis, key=True)
11917+
axis = self.get_data_axes().index(axis)
11918+
11919+
return self.data.argmin(axis=axis, unravel=unravel)
11920+
1182811921
@_deprecated_kwarg_check("i", version="3.0.0", removed_at="4.0.0")
1182911922
def squeeze(self, axes=None, inplace=False, i=False, **kwargs):
1183011923
"""Remove size 1 axes from the data.

cf/test/test_Data.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2476,21 +2476,21 @@ def test_Data__int__(self):
24762476

24772477
def test_Data_argmax(self):
24782478
"""Test the `argmax` Data method."""
2479-
d = cf.Data(np.arange(120).reshape(4, 5, 6))
2479+
d = cf.Data(np.arange(24).reshape(2, 3, 4))
24802480

2481-
self.assertEqual(d.argmax().array, 119)
2481+
self.assertEqual(d.argmax().array, 23)
24822482

24832483
index = d.argmax(unravel=True)
2484-
self.assertEqual(index, (3, 4, 5))
2485-
self.assertEqual(d[index].array, 119)
2484+
self.assertEqual(index, (1, 2, 3))
2485+
self.assertEqual(d[index].array, 23)
24862486

24872487
e = d.argmax(axis=1)
2488-
self.assertEqual(e.shape, (4, 6))
2488+
self.assertEqual(e.shape, (2, 4))
24892489
self.assertTrue(
2490-
e.equals(cf.Data.full(shape=(4, 6), fill_value=4, dtype=int))
2490+
e.equals(cf.Data.full(shape=(2, 4), fill_value=2, dtype=int))
24912491
)
24922492

2493-
self.assertEqual(d[d.argmax(unravel=True)].array, 119)
2493+
self.assertEqual(d[d.argmax(unravel=True)].array, 23)
24942494

24952495
d = cf.Data([0, 4, 2, 3, 4])
24962496
self.assertEqual(d.argmax().array, 1)
@@ -2499,6 +2499,31 @@ def test_Data_argmax(self):
24992499
with self.assertRaises(ValueError):
25002500
d.argmax(axis=d.ndim)
25012501

2502+
def test_Data_argmin(self):
2503+
"""Test the `argmin` Data method."""
2504+
d = cf.Data(np.arange(23, -1, -1).reshape(2, 3, 4))
2505+
2506+
self.assertEqual(d.argmin().array, 23)
2507+
2508+
index = d.argmin(unravel=True)
2509+
self.assertEqual(index, (1, 2, 3))
2510+
self.assertEqual(d[index].array, 0)
2511+
2512+
e = d.argmin(axis=1)
2513+
self.assertEqual(e.shape, (2, 4))
2514+
self.assertTrue(
2515+
e.equals(cf.Data.full(shape=(2, 4), fill_value=2, dtype=int))
2516+
)
2517+
2518+
self.assertEqual(d[d.argmin(unravel=True)].array, 0)
2519+
2520+
d = cf.Data([4, 0, 2, 3, 0])
2521+
self.assertEqual(d.argmin().array, 1)
2522+
2523+
# Bad axis
2524+
with self.assertRaises(ValueError):
2525+
d.argmin(axis=d.ndim)
2526+
25022527
def test_Data_percentile_median(self):
25032528
"""Test the `percentile` and `median` Data methods."""
25042529
# ranks: a sequence of percentile rank inputs. NOTE: must

cf/test/test_Field.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2572,6 +2572,21 @@ def test_Field_argmax(self):
25722572
with self.assertRaises(ValueError):
25732573
f.argmax(axis="foo")
25742574

2575+
def test_Field_argmin(self):
2576+
"""Test the `argmin` Field method."""
2577+
f = cf.example_field(2)
2578+
i = f.argmin("T")
2579+
self.assertEqual(i.shape, f.shape[1:])
2580+
2581+
i = f.argmin(unravel=True)
2582+
self.assertIsInstance(i, tuple)
2583+
g = f[i]
2584+
self.assertEqual(g.shape, (1, 1, 1))
2585+
2586+
# Bad axis
2587+
with self.assertRaises(ValueError):
2588+
f.argmin(axis="foo")
2589+
25752590
def test_Field_subspace(self):
25762591
f = self.f
25772592

docs/source/class/cf.Data.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,7 @@ Searching
458458
:template: method.rst
459459

460460
~cf.Data.argmax
461+
~cf.Data.argmin
461462
~cf.Data.where
462463

463464
Counting

docs/source/class/cf.Field.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ Data
241241
:template: method.rst
242242

243243
~cf.Field.argmax
244+
~cf.Field.argmin
244245
~cf.Field.where
245246

246247
Miscellaneous data operations

0 commit comments

Comments
 (0)