Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/src/whatsnew/latest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,17 @@ This document explains the changes made to Iris for this release
#. `@wjbenfold`_ added caching to the calculation of the points array in a
:class:`~iris.coords.DimCoord` created using
:meth:`~iris.coords.DimCoord.from_regular`. (:pull:`4698`)

#. `@wjbenfold`_ introduced caching in :func:`_lazy_data._optimum_chunksize` and
:func:`iris.fileformats.pp_load_rules._epoch_date_hours` to reduce time spent
repeating calculations. (:pull:`4716`)

#. `@pp-mo`_ made :meth:`~iris.cube.Cube.add_aux_factory` faster.
(:pull:`4718`)

#. `@wjbenfold`_ permitted the fast percentile aggregation method to be used on
masked data when the missing data tolerance is set to 0. (:issue:`4735`,
:pull:`4755`)


🔥 Deprecations
Expand Down
46 changes: 30 additions & 16 deletions lib/iris/analysis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,25 @@ def __init__(self, units_func=None, **kwargs):
**kwargs,
)

def _base_aggregate(self, data, axis, lazy, **kwargs):
"""
Method to avoid duplication of checks in aggregate and lazy_aggregate.
"""
msg = "{} aggregator requires the mandatory keyword argument {!r}."
for arg in self._args:
if arg not in kwargs:
raise ValueError(msg.format(self.name(), arg))

if kwargs.get("fast_percentile_method", False) and (
kwargs.get("mdtol", 1) != 0
):
kwargs["error_on_masked"] = True

if lazy:
return _Aggregator.lazy_aggregate(self, data, axis, **kwargs)
else:
return _Aggregator.aggregate(self, data, axis, **kwargs)

def aggregate(self, data, axis, **kwargs):
"""
Perform the percentile aggregation over the given data.
Expand Down Expand Up @@ -755,12 +774,7 @@ def aggregate(self, data, axis, **kwargs):

"""

msg = "{} aggregator requires the mandatory keyword argument {!r}."
for arg in self._args:
if arg not in kwargs:
raise ValueError(msg.format(self.name(), arg))

return _Aggregator.aggregate(self, data, axis, **kwargs)
return self._base_aggregate(data, axis, lazy=False, **kwargs)

def lazy_aggregate(self, data, axis, **kwargs):
"""
Expand Down Expand Up @@ -794,12 +808,7 @@ def lazy_aggregate(self, data, axis, **kwargs):

"""

msg = "{} aggregator requires the mandatory keyword argument {!r}."
for arg in self._args:
if arg not in kwargs:
raise ValueError(msg.format(self.name(), arg))

return _Aggregator.lazy_aggregate(self, data, axis, **kwargs)
return self._base_aggregate(data, axis, lazy=True, **kwargs)

def post_process(self, collapsed_cube, data_result, coords, **kwargs):
"""
Expand Down Expand Up @@ -1281,9 +1290,13 @@ def _calc_percentile(data, percent, fast_percentile_method=False, **kwargs):

"""
if fast_percentile_method:
msg = "Cannot use fast np.percentile method with masked array."
if ma.is_masked(data):
raise TypeError(msg)
if kwargs.pop("error_on_masked", False):
msg = (
"Cannot use fast np.percentile method with masked array unless"
" mdtol is 0."
)
if ma.is_masked(data):
raise TypeError(msg)
result = np.percentile(data, percent, axis=-1)
result = result.T
else:
Expand Down Expand Up @@ -1965,7 +1978,8 @@ def interp_order(length):
* fast_percentile_method (boolean):
When set to True, uses :func:`numpy.percentile` method as a faster
alternative to the :func:`scipy.stats.mstats.mquantiles` method. alphap and
betap are ignored. An exception is raised if the data are masked.
betap are ignored. An exception is raised if the data are masked and the
missing data tolerance is not 0.
Defaults to False.

**For example**:
Expand Down
31 changes: 29 additions & 2 deletions lib/iris/tests/unit/analysis/test_PERCENTILE.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,25 @@ def test_masked(self):
shape = (2, 11)
data = ma.arange(np.prod(shape)).reshape(shape)
data[0, ::2] = ma.masked
emsg = "Cannot use fast np.percentile method with masked array."
emsg = (
"Cannot use fast np.percentile method with masked array unless "
"mdtol is 0."
)
with self.assertRaisesRegex(TypeError, emsg):
PERCENTILE.aggregate(
data, axis=0, percent=50, fast_percentile_method=True
)

def test_masked_mdtol_0(self):
shape = (2, 11)
axis = 0
percent = 50
data = ma.arange(np.prod(shape)).reshape(shape)
data[0, ::2] = ma.masked
expected = np.arange(shape[-1]) + 5.5
expected[0] = ma.masked
self.check_percentile_calc(data, axis, percent, expected, mdtol=0)

@mock.patch("numpy.percentile")
def test_numpy_percentile_called(self, mocked_percentile):
# Basic check that numpy.percentile is called.
Expand Down Expand Up @@ -286,10 +299,24 @@ def test_masked(self):
actual = PERCENTILE.lazy_aggregate(
data, axis=0, percent=50, fast_percentile_method=True
)
emsg = "Cannot use fast np.percentile method with masked array."
emsg = (
"Cannot use fast np.percentile method with masked array unless "
"mdtol is 0."
)
with self.assertRaisesRegex(TypeError, emsg):
as_concrete_data(actual)

def test_masked_mdtol_0(self):
shape = (2, 11)
axis = 0
percent = 50
data = ma.arange(np.prod(shape)).reshape(shape)
data[0, ::2] = ma.masked
data = as_lazy_data(data)
expected = np.arange(shape[-1]) + 5.5
expected[0] = ma.masked
self.check_percentile_calc(data, axis, percent, expected, mdtol=0)

@mock.patch("numpy.percentile", return_value=np.array([2, 4]))
def test_numpy_percentile_called(self, mocked_percentile):
# Basic check that numpy.percentile is called.
Expand Down