Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 24 additions & 0 deletions src/CSET/operators/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import iris.cube
import iris.exceptions
import iris.util
import numpy as np
from iris.time import PartialDateTime

from CSET._common import iter_maybe
Expand Down Expand Up @@ -583,3 +584,26 @@ def check_if_cylc_workflow() -> Path | None:

# If ROSE_DATAC unset or its path does not exist, return None
return None


def calc_array_stats(
array: np.ndarray | np.ma.MaskedArray,
) -> tuple[float, float, float]:
"""Calculate the min, max, and mean of an array.

NaNs/Masked data is ignored.

Returns
-------
stats:
A tuple of (min, max, mean).
"""
if np.ma.isMaskedArray(array):
array_min = array.min()
array_max = array.max()
array_mean = array.mean()
else:
array_min = np.nanmin(array)
array_max = np.nanmax(array)
array_mean = np.nanmean(array)
return array_min, array_max, array_mean
7 changes: 5 additions & 2 deletions src/CSET/operators/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
get_model_colors_map,
)
from CSET.operators._utils import (
calc_array_stats,
check_sequence_coordinate,
check_single_cube,
check_stamp_coordinate,
Expand Down Expand Up @@ -749,8 +750,9 @@ def _plot_and_save_spatial_plot(

# Add watermark with min/max/mean. Currently not user togglable.
# In the bbox dictionary, fc and ec are hex colour codes for grey shade.
cube_min, cube_max, cube_mean = calc_array_stats(cube.data)
axes.annotate(
f"Min: {np.nanmin(cube.data.filled(np.nan)):.3g} Max: {np.nanmax(cube.data.filled(np.nan)):.3g} Mean: {np.nanmean(cube.data.filled(np.nan)):.3g}",
f"Min: {cube_min:.3g} Max: {cube_max:.3g} Mean: {cube_mean:.3g}",
xy=(0.025, yinfopad),
xycoords="axes fraction",
xytext=(-5, 5),
Expand Down Expand Up @@ -1478,8 +1480,9 @@ def _plot_and_save_vector_plot(

# Add watermark with min/max/mean. Currently not user togglable.
# In the bbox dictionary, fc and ec are hex colour codes for grey shade.
cube_min, cube_max, cube_mean = calc_array_stats(cube_vec_mag.data)
axes.annotate(
f"Min: {np.nanmin(cube_vec_mag.data.filled(np.nan)):.3g} Max: {np.nanmax(cube_vec_mag.data.filled(np.nan)):.3g} Mean: {np.nanmean(cube_vec_mag.data.filled(np.nan)):.3g}",
f"Min: {cube_min:.3g} Max: {cube_max:.3g} Mean: {cube_mean:.3g}",
xy=(0.05, -0.05),
xycoords="axes fraction",
xytext=(-5, 5),
Expand Down
27 changes: 27 additions & 0 deletions tests/operators/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,3 +686,30 @@ def test_check_if_cylc_workflow_no_dir(monkeypatch, tmp_path):
def test_check_if_cylc_workflow_false(monkeypatch, tmp_path):
"""Test no ROSE_DATAC present."""
assert operator_utils.check_if_cylc_workflow() is None


def test_calc_array_stats():
"""Array stats are calculated for a normal array."""
array = np.array((1.0, 2.0, 3.0))
array_min, array_max, array_mean = operator_utils.calc_array_stats(array)
assert array_min == 1.0
assert array_max == 3.0
assert array_mean == 2.0


def test_calc_array_stats_nans():
"""Array stats are calculated for an array containing NaNs."""
array = np.array((1.0, np.nan, 3.0))
array_min, array_max, array_mean = operator_utils.calc_array_stats(array)
assert array_min == 1.0
assert array_max == 3.0
assert array_mean == 2.0


def test_calc_array_stats_masked_array():
"""Array stats are calculated for a masked array."""
array = np.ma.MaskedArray((1.0, -9999, 3.0), mask=(False, True, False))
array_min, array_max, array_mean = operator_utils.calc_array_stats(array)
assert array_min == 1.0
assert array_max == 3.0
assert array_mean == 2.0