From 3aa38d411bf16d56bd7fdcf89a9a1bac34760564 Mon Sep 17 00:00:00 2001 From: "Marten H. van Kerkwijk" Date: Wed, 23 Oct 2024 12:30:38 -0400 Subject: [PATCH 1/2] TST: verify np.nan* functions give back correct class --- astropy/coordinates/tests/test_angles.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/astropy/coordinates/tests/test_angles.py b/astropy/coordinates/tests/test_angles.py index 0ee290877bc9..0c5f17290df7 100644 --- a/astropy/coordinates/tests/test_angles.py +++ b/astropy/coordinates/tests/test_angles.py @@ -216,6 +216,28 @@ def test_angle_methods(): assert a_min == 0.0 * u.degree +def test_angle_nan_functions(): + # Most numpy functions tested as part of the Quantity tests. + # But check that we drop to Quantity when appropriate; see + # https://github.com/astropy/astropy/pull/17221#discussion_r1813060768 + a = Angle([0.0, 2.0, np.nan], "deg") + a_mean = np.nanmean(a) + assert type(a_mean) is Angle + assert a_mean == 1.0 * u.degree + a_std = np.nanstd(a) + assert type(a_std) is Angle + assert a_std == 1.0 * u.degree + a_var = np.nanvar(a) + assert type(a_var) is u.Quantity + assert a_var == 1.0 * u.degree**2 + a_max = np.nanmax(a) + assert type(a_max) is Angle + assert a_max == 2.0 * u.degree + a_min = np.nanmin(a) + assert type(a_min) is Angle + assert a_min == 0.0 * u.degree + + def test_angle_convert(): """ Test unit conversion of Angle objects From f3bcf5f60940e4a1d9b29c04c33a5cf67ba1413f Mon Sep 17 00:00:00 2001 From: "Marten H. van Kerkwijk" Date: Wed, 23 Oct 2024 12:32:01 -0400 Subject: [PATCH 2/2] BUG: ensure nanvar(angle) drops to quantity. --- astropy/units/quantity_helper/function_helpers.py | 15 +++++++++++++-- docs/changes/coordinates/17239.bugfix.rst | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 docs/changes/coordinates/17239.bugfix.rst diff --git a/astropy/units/quantity_helper/function_helpers.py b/astropy/units/quantity_helper/function_helpers.py index c515d28fc951..be464c573b07 100644 --- a/astropy/units/quantity_helper/function_helpers.py +++ b/astropy/units/quantity_helper/function_helpers.py @@ -97,8 +97,7 @@ np.isposinf, np.isneginf, np.isreal, np.iscomplex, np.average, np.mean, np.std, np.var, np.trace, np.nanmax, np.nanmin, np.nanargmin, np.nanargmax, np.nanmean, - np.nansum, np.nancumsum, np.nanstd, np.nanvar, - np.nanprod, np.nancumprod, + np.nansum, np.nancumsum, np.nanprod, np.nancumprod, np.einsum_path, np.linspace, np.sort, np.partition, np.meshgrid, np.common_type, np.result_type, np.can_cast, np.min_scalar_type, @@ -219,6 +218,7 @@ def __call__(self, f=None, helps=None, module=np): np.fft.fft2, np.fft.ifft2, np.fft.rfft2, np.fft.irfft2, np.fft.fftn, np.fft.ifftn, np.fft.rfftn, np.fft.irfftn, np.fft.hfft, np.fft.ihfft, + np.nanstd, # See comment on nanvar helper. np.linalg.eigvals, np.linalg.eigvalsh, } | ({np.asfarray} if NUMPY_LT_2_0 else set()) # noqa: NPY201 ) # fmt: skip @@ -252,6 +252,17 @@ def like_helper(a, *args, **kwargs): return (a.view(np.ndarray),) + args, kwargs, unit, None +# nanvar is safe for Quantity and was previously in SUBCLASS_FUNCTIONS, but it +# is not safe for Angle, since the resulting unit is inconsistent with being +# an Angle. By using FUNCTION_HELPERS, the unit gets passed through +# _result_as_quantity, which will correctly drop to Quantity. +# A side effect would be that np.nanstd then also produces Quantity; this +# is avoided by it being helped by invariant_a_helpers above. +@function_helper +def nanvar(a, *args, **kwargs): + return (a.view(np.ndarray),) + args, kwargs, a.unit**2, None + + @function_helper def sinc(x): from astropy.units.si import radian diff --git a/docs/changes/coordinates/17239.bugfix.rst b/docs/changes/coordinates/17239.bugfix.rst new file mode 100644 index 000000000000..34f071f1f925 --- /dev/null +++ b/docs/changes/coordinates/17239.bugfix.rst @@ -0,0 +1,2 @@ +``np.nanvar(angle)`` now produces a ``Quantity`` with the correct +unit, rather than raising an exception.