diff --git a/docs/source/umami.calculations.metric.mask_aggregation.rst b/docs/source/umami.calculations.metric.mask_aggregation.rst new file mode 100644 index 0000000..65bcf92 --- /dev/null +++ b/docs/source/umami.calculations.metric.mask_aggregation.rst @@ -0,0 +1,7 @@ +mask_aggregation: aggregate masked field values +----------------------------------------------- + +.. automodule:: umami.calculations.metric.mask_aggregation + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/umami.calculations.rst b/docs/source/umami.calculations.rst index c00fdfc..a47b542 100644 --- a/docs/source/umami.calculations.rst +++ b/docs/source/umami.calculations.rst @@ -12,6 +12,7 @@ Calculations for Metrics or Residuals umami.calculations.metric.chi_intercept_gradient umami.calculations.metric.count_equal umami.calculations.metric.hypsometric_integral + umami.calculations.metric.mask_aggregation umami.calculations.metric.watershed_aggregation Calculations for Residuals only diff --git a/umami/calculations/__init__.py b/umami/calculations/__init__.py index 3737646..e759722 100644 --- a/umami/calculations/__init__.py +++ b/umami/calculations/__init__.py @@ -11,6 +11,7 @@ count_equal, hypsometric_integral, watershed_aggregation, + mask_aggregation, ) from .residual import ( discretized_misfit, @@ -26,6 +27,7 @@ "count_equal", "hypsometric_integral", "watershed_aggregation", + "mask_aggregation", "discretized_misfit", "joint_density_misfit", "kstest", diff --git a/umami/calculations/metric/__init__.py b/umami/calculations/metric/__init__.py index 9987265..7696daf 100644 --- a/umami/calculations/metric/__init__.py +++ b/umami/calculations/metric/__init__.py @@ -3,6 +3,7 @@ from .count_equal import count_equal from .hypsometric_integral import hypsometric_integral from .watershed_aggregation import watershed_aggregation +from .mask_aggregation import mask_aggregation __all__ = [ "aggregate", @@ -11,4 +12,5 @@ "count_equal", "hypsometric_integral", "watershed_aggregation", + "mask_aggregation", ] diff --git a/umami/calculations/metric/mask_aggregation.py b/umami/calculations/metric/mask_aggregation.py new file mode 100644 index 0000000..12f6c3f --- /dev/null +++ b/umami/calculations/metric/mask_aggregation.py @@ -0,0 +1,94 @@ +import numpy as np + +from .aggregate import _aggregate + + +def mask_aggregation(grid, field, mask, method, **kwds): + """Aggregate a field value masked by a boolean field. + + ``mask_aggregation`` calculates aggregate values on a field masked by a + second, boolean field. It supports all methods in the `numpy`_ namespace + that reduce an array to a scalar. + + .. _numpy: https://numpy.org + + Parameters + ---------- + grid : Landlab model grid + field : str + An at-node Landlab grid field that is present on the model grid. + mask : str + An at-node Landlab grid field of boolean type. Aggregation is done + where the masked value is True. + method : str + The name of a numpy namespace method. + **kwds + Any additional keyword arguments needed by the method. + + Returns + ------- + out : float + The aggregate value. + + Examples + -------- + First an example that only uses the ``mask_aggregation`` function. + + >>> from landlab import RasterModelGrid + >>> from landlab.components import FlowAccumulator + >>> from umami.calculations import mask_aggregation + >>> grid = RasterModelGrid((10, 10)) + >>> z = grid.add_zeros("node", "topographic__elevation") + >>> z += grid.x_of_node + grid.y_of_node + + Create a boolean mask and add it to the grid. + + >>> mask = grid.add_field("mask", z > 11, at="node") + + ``mask_aggregation`` supports all functions in the `numpy`_ namespace. + Here we show `mean`_ and `percentile`_. The latter of which takes an + additional argument, *q*. + + .. _numpy: https://numpy.org + .. _mean: https://docs.scipy.org/doc/numpy/reference/generated/numpy.mean.html + .. _percentile: https://docs.scipy.org/doc/numpy/reference/generated/numpy.percentile.html + + + >>> mask_aggregation(grid, "topographic__elevation", "mask", "mean") + 14.0 + >>> mask_aggregation( + ... grid, + ... "topographic__elevation", + ... "mask", + ... "percentile", + ... q=10) + 12.0 + + Next, the same calculations are shown as part of an umami ``Metric``. + + >>> from io import StringIO + >>> from umami import Metric + >>> file_like=StringIO(''' + ... mask_mean: + ... _func: mask_aggregation + ... mask: mask + ... method: mean + ... field: topographic__elevation + ... mask_10thptile: + ... _func: mask_aggregation + ... mask: mask + ... method: percentile + ... field: topographic__elevation + ... q: 10 + ... ''') + >>> metric = Metric(grid) + >>> metric.add_from_file(file_like) + >>> metric.names + ['mask_mean', 'mask_10thptile'] + >>> metric.calculate() + >>> metric.values + [14.0, 12.0] + """ + masked = grid.at_node[mask] + vals = grid.at_node[field][masked] + return _aggregate(vals, method, **kwds)