From 33cdabd261b5725ac357c2823bd0f33684d3a954 Mon Sep 17 00:00:00 2001 From: Maximilian Roos <5635139+max-sixty@users.noreply.github.com> Date: Mon, 18 Apr 2022 20:26:53 -0700 Subject: [PATCH] Remove xarray.ufuncs (#6491) * Remove xarray.ufuncs * Remove ufunc docs --- doc/api-hidden.rst | 60 ----------- doc/api.rst | 78 -------------- doc/whats-new.rst | 3 + xarray/__init__.py | 3 +- xarray/tests/test_dask.py | 12 +-- xarray/tests/test_sparse.py | 12 +-- xarray/tests/test_ufuncs.py | 52 ---------- xarray/ufuncs.py | 197 ------------------------------------ 8 files changed, 11 insertions(+), 406 deletions(-) delete mode 100644 xarray/ufuncs.py diff --git a/doc/api-hidden.rst b/doc/api-hidden.rst index 8ed9e47be01..30bc9f858f2 100644 --- a/doc/api-hidden.rst +++ b/doc/api-hidden.rst @@ -319,66 +319,6 @@ IndexVariable.sizes IndexVariable.values - ufuncs.angle - ufuncs.arccos - ufuncs.arccosh - ufuncs.arcsin - ufuncs.arcsinh - ufuncs.arctan - ufuncs.arctan2 - ufuncs.arctanh - ufuncs.ceil - ufuncs.conj - ufuncs.copysign - ufuncs.cos - ufuncs.cosh - ufuncs.deg2rad - ufuncs.degrees - ufuncs.exp - ufuncs.expm1 - ufuncs.fabs - ufuncs.fix - ufuncs.floor - ufuncs.fmax - ufuncs.fmin - ufuncs.fmod - ufuncs.fmod - ufuncs.frexp - ufuncs.hypot - ufuncs.imag - ufuncs.iscomplex - ufuncs.isfinite - ufuncs.isinf - ufuncs.isnan - ufuncs.isreal - ufuncs.ldexp - ufuncs.log - ufuncs.log10 - ufuncs.log1p - ufuncs.log2 - ufuncs.logaddexp - ufuncs.logaddexp2 - ufuncs.logical_and - ufuncs.logical_not - ufuncs.logical_or - ufuncs.logical_xor - ufuncs.maximum - ufuncs.minimum - ufuncs.nextafter - ufuncs.rad2deg - ufuncs.radians - ufuncs.real - ufuncs.rint - ufuncs.sign - ufuncs.signbit - ufuncs.sin - ufuncs.sinh - ufuncs.sqrt - ufuncs.square - ufuncs.tan - ufuncs.tanh - ufuncs.trunc - plot.plot plot.line plot.step diff --git a/doc/api.rst b/doc/api.rst index 7fdd775e168..644b86cdebb 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -610,84 +610,6 @@ Plotting DataArray.plot.step DataArray.plot.surface -.. _api.ufuncs: - -Universal functions -=================== - -.. warning:: - - With recent versions of NumPy, Dask and xarray, NumPy ufuncs are now - supported directly on all xarray and Dask objects. This obviates the need - for the ``xarray.ufuncs`` module, which should not be used for new code - unless compatibility with versions of NumPy prior to v1.13 is - required. They will be removed once support for NumPy prior to - v1.17 is dropped. - -These functions are copied from NumPy, but extended to work on NumPy arrays, -dask arrays and all xarray objects. You can find them in the ``xarray.ufuncs`` -module: - -:py:attr:`~ufuncs.angle` -:py:attr:`~ufuncs.arccos` -:py:attr:`~ufuncs.arccosh` -:py:attr:`~ufuncs.arcsin` -:py:attr:`~ufuncs.arcsinh` -:py:attr:`~ufuncs.arctan` -:py:attr:`~ufuncs.arctan2` -:py:attr:`~ufuncs.arctanh` -:py:attr:`~ufuncs.ceil` -:py:attr:`~ufuncs.conj` -:py:attr:`~ufuncs.copysign` -:py:attr:`~ufuncs.cos` -:py:attr:`~ufuncs.cosh` -:py:attr:`~ufuncs.deg2rad` -:py:attr:`~ufuncs.degrees` -:py:attr:`~ufuncs.exp` -:py:attr:`~ufuncs.expm1` -:py:attr:`~ufuncs.fabs` -:py:attr:`~ufuncs.fix` -:py:attr:`~ufuncs.floor` -:py:attr:`~ufuncs.fmax` -:py:attr:`~ufuncs.fmin` -:py:attr:`~ufuncs.fmod` -:py:attr:`~ufuncs.fmod` -:py:attr:`~ufuncs.frexp` -:py:attr:`~ufuncs.hypot` -:py:attr:`~ufuncs.imag` -:py:attr:`~ufuncs.iscomplex` -:py:attr:`~ufuncs.isfinite` -:py:attr:`~ufuncs.isinf` -:py:attr:`~ufuncs.isnan` -:py:attr:`~ufuncs.isreal` -:py:attr:`~ufuncs.ldexp` -:py:attr:`~ufuncs.log` -:py:attr:`~ufuncs.log10` -:py:attr:`~ufuncs.log1p` -:py:attr:`~ufuncs.log2` -:py:attr:`~ufuncs.logaddexp` -:py:attr:`~ufuncs.logaddexp2` -:py:attr:`~ufuncs.logical_and` -:py:attr:`~ufuncs.logical_not` -:py:attr:`~ufuncs.logical_or` -:py:attr:`~ufuncs.logical_xor` -:py:attr:`~ufuncs.maximum` -:py:attr:`~ufuncs.minimum` -:py:attr:`~ufuncs.nextafter` -:py:attr:`~ufuncs.rad2deg` -:py:attr:`~ufuncs.radians` -:py:attr:`~ufuncs.real` -:py:attr:`~ufuncs.rint` -:py:attr:`~ufuncs.sign` -:py:attr:`~ufuncs.signbit` -:py:attr:`~ufuncs.sin` -:py:attr:`~ufuncs.sinh` -:py:attr:`~ufuncs.sqrt` -:py:attr:`~ufuncs.square` -:py:attr:`~ufuncs.tan` -:py:attr:`~ufuncs.tanh` -:py:attr:`~ufuncs.trunc` - IO / Conversion =============== diff --git a/doc/whats-new.rst b/doc/whats-new.rst index d86ec107205..4882402073c 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -51,6 +51,9 @@ Breaking changes - Many arguments like ``keep_attrs``, ``axis``, and ``skipna`` are now keyword only for all reduction operations like ``.mean``. By `Deepak Cherian `_, `Jimmy Westling `_. +- Xarray's ufuncs have been removed, now that they can be replaced by numpy's ufuncs in all + supported versions of numpy. + By `Maximilian Roos `_. Deprecations ~~~~~~~~~~~~ diff --git a/xarray/__init__.py b/xarray/__init__.py index aa9739d3d35..46dcf0e9b32 100644 --- a/xarray/__init__.py +++ b/xarray/__init__.py @@ -1,4 +1,4 @@ -from . import testing, tutorial, ufuncs +from . import testing, tutorial from .backends.api import ( load_dataarray, load_dataset, @@ -53,7 +53,6 @@ # `mypy --strict` running in projects that import xarray. __all__ = ( # Sub-packages - "ufuncs", "testing", "tutorial", # Top-level functions diff --git a/xarray/tests/test_dask.py b/xarray/tests/test_dask.py index 872c0c6f1db..df69e8d9d6e 100644 --- a/xarray/tests/test_dask.py +++ b/xarray/tests/test_dask.py @@ -10,7 +10,6 @@ from packaging.version import Version import xarray as xr -import xarray.ufuncs as xu from xarray import DataArray, Dataset, Variable from xarray.core import duck_array_ops from xarray.core.pycompat import dask_version @@ -265,18 +264,16 @@ def test_missing_methods(self): except NotImplementedError as err: assert "dask" in str(err) - @pytest.mark.filterwarnings("ignore::FutureWarning") def test_univariate_ufunc(self): u = self.eager_var v = self.lazy_var - self.assertLazyAndAllClose(np.sin(u), xu.sin(v)) + self.assertLazyAndAllClose(np.sin(u), np.sin(v)) - @pytest.mark.filterwarnings("ignore::FutureWarning") def test_bivariate_ufunc(self): u = self.eager_var v = self.lazy_var - self.assertLazyAndAllClose(np.maximum(u, 0), xu.maximum(v, 0)) - self.assertLazyAndAllClose(np.maximum(u, 0), xu.maximum(0, v)) + self.assertLazyAndAllClose(np.maximum(u, 0), np.maximum(v, 0)) + self.assertLazyAndAllClose(np.maximum(u, 0), np.maximum(0, v)) def test_compute(self): u = self.eager_var @@ -605,11 +602,10 @@ def duplicate_and_merge(array): actual = duplicate_and_merge(self.lazy_array) self.assertLazyAndEqual(expected, actual) - @pytest.mark.filterwarnings("ignore::FutureWarning") def test_ufuncs(self): u = self.eager_array v = self.lazy_array - self.assertLazyAndAllClose(np.sin(u), xu.sin(v)) + self.assertLazyAndAllClose(np.sin(u), np.sin(v)) def test_where_dispatching(self): a = np.arange(10) diff --git a/xarray/tests/test_sparse.py b/xarray/tests/test_sparse.py index bf4d39105c4..bac1f6407fc 100644 --- a/xarray/tests/test_sparse.py +++ b/xarray/tests/test_sparse.py @@ -7,7 +7,6 @@ from packaging.version import Version import xarray as xr -import xarray.ufuncs as xu from xarray import DataArray, Variable from xarray.core.pycompat import sparse_array_type, sparse_version @@ -279,12 +278,12 @@ def test_unary_op(self): @pytest.mark.filterwarnings("ignore::FutureWarning") def test_univariate_ufunc(self): - assert_sparse_equal(np.sin(self.data), xu.sin(self.var).data) + assert_sparse_equal(np.sin(self.data), np.sin(self.var).data) @pytest.mark.filterwarnings("ignore::FutureWarning") def test_bivariate_ufunc(self): - assert_sparse_equal(np.maximum(self.data, 0), xu.maximum(self.var, 0).data) - assert_sparse_equal(np.maximum(self.data, 0), xu.maximum(0, self.var).data) + assert_sparse_equal(np.maximum(self.data, 0), np.maximum(self.var, 0).data) + assert_sparse_equal(np.maximum(self.data, 0), np.maximum(0, self.var).data) def test_repr(self): expected = dedent( @@ -665,11 +664,6 @@ def test_stack(self): roundtripped = stacked.unstack() assert_identical(arr, roundtripped) - @pytest.mark.filterwarnings("ignore::FutureWarning") - def test_ufuncs(self): - x = self.sp_xr - assert_equal(np.sin(x), xu.sin(x)) - def test_dataarray_repr(self): a = xr.DataArray( sparse.COO.from_numpy(np.ones(4)), diff --git a/xarray/tests/test_ufuncs.py b/xarray/tests/test_ufuncs.py index 590ae9ae003..28e5c6cbcb1 100644 --- a/xarray/tests/test_ufuncs.py +++ b/xarray/tests/test_ufuncs.py @@ -1,10 +1,7 @@ -import pickle - import numpy as np import pytest import xarray as xr -import xarray.ufuncs as xu from . import assert_array_equal from . import assert_identical as assert_identical_ @@ -158,52 +155,3 @@ def test_gufuncs(): fake_gufunc = mock.Mock(signature="(n)->()", autospec=np.sin) with pytest.raises(NotImplementedError, match=r"generalized ufuncs"): xarray_obj.__array_ufunc__(fake_gufunc, "__call__", xarray_obj) - - -def test_xarray_ufuncs_deprecation(): - with pytest.warns(FutureWarning, match="xarray.ufuncs"): - xu.cos(xr.DataArray([0, 1])) - - with assert_no_warnings(): - xu.angle(xr.DataArray([0, 1])) - - -@pytest.mark.filterwarnings("ignore::RuntimeWarning") -@pytest.mark.parametrize( - "name", - [ - name - for name in dir(xu) - if ( - not name.startswith("_") - and hasattr(np, name) - and name not in ["print_function", "absolute_import", "division"] - ) - ], -) -def test_numpy_ufuncs(name, request): - x = xr.DataArray([1, 1]) - - np_func = getattr(np, name) - if hasattr(np_func, "nin") and np_func.nin == 2: - args = (x, x) - else: - args = (x,) - - y = np_func(*args) - - if name in ["angle", "iscomplex"]: - # these functions need to be handled with __array_function__ protocol - assert isinstance(y, np.ndarray) - elif name in ["frexp"]: - # np.frexp returns a tuple - assert not isinstance(y, xr.DataArray) - else: - assert isinstance(y, xr.DataArray) - - -@pytest.mark.filterwarnings("ignore:xarray.ufuncs") -def test_xarray_ufuncs_pickle(): - a = 1.0 - cos_pickled = pickle.loads(pickle.dumps(xu.cos)) - assert_identical(cos_pickled(a), xu.cos(a)) diff --git a/xarray/ufuncs.py b/xarray/ufuncs.py deleted file mode 100644 index 24907a158ef..00000000000 --- a/xarray/ufuncs.py +++ /dev/null @@ -1,197 +0,0 @@ -"""xarray specific universal functions - -Handles unary and binary operations for the following types, in ascending -priority order: -- scalars -- numpy.ndarray -- dask.array.Array -- xarray.Variable -- xarray.DataArray -- xarray.Dataset -- xarray.core.groupby.GroupBy - -Once NumPy 1.10 comes out with support for overriding ufuncs, this module will -hopefully no longer be necessary. -""" -import textwrap -import warnings as _warnings - -import numpy as _np - -from .core.dataarray import DataArray as _DataArray -from .core.dataset import Dataset as _Dataset -from .core.groupby import GroupBy as _GroupBy -from .core.pycompat import dask_array_type as _dask_array_type -from .core.variable import Variable as _Variable - -_xarray_types = (_Variable, _DataArray, _Dataset, _GroupBy) -_dispatch_order = (_np.ndarray, _dask_array_type) + _xarray_types -_UNDEFINED = object() - - -def _dispatch_priority(obj): - for priority, cls in enumerate(_dispatch_order): - if isinstance(obj, cls): - return priority - return -1 - - -class _UFuncDispatcher: - """Wrapper for dispatching ufuncs.""" - - def __init__(self, name): - self._name = name - - def __call__(self, *args, **kwargs): - if self._name not in ["angle", "iscomplex"]: - _warnings.warn( - "xarray.ufuncs is deprecated. Instead, use numpy ufuncs directly.", - FutureWarning, - stacklevel=2, - ) - - new_args = args - res = _UNDEFINED - if len(args) > 2 or len(args) == 0: - raise TypeError(f"cannot handle {len(args)} arguments for {self._name!r}") - elif len(args) == 1: - if isinstance(args[0], _xarray_types): - res = args[0]._unary_op(self) - else: # len(args) = 2 - p1, p2 = map(_dispatch_priority, args) - if p1 >= p2: - if isinstance(args[0], _xarray_types): - res = args[0]._binary_op(args[1], self) - else: - if isinstance(args[1], _xarray_types): - res = args[1]._binary_op(args[0], self, reflexive=True) - new_args = tuple(reversed(args)) - - if res is _UNDEFINED: - f = getattr(_np, self._name) - res = f(*new_args, **kwargs) - if res is NotImplemented: - raise TypeError( - f"{self._name!r} not implemented for types ({type(args[0])!r}, {type(args[1])!r})" - ) - return res - - -def _skip_signature(doc, name): - if not isinstance(doc, str): - return doc - - if doc.startswith(name): - signature_end = doc.find("\n\n") - doc = doc[signature_end + 2 :] - - return doc - - -def _remove_unused_reference_labels(doc): - if not isinstance(doc, str): - return doc - - max_references = 5 - for num in range(max_references): - label = f".. [{num}]" - reference = f"[{num}]_" - index = f"{num}. " - - if label not in doc or reference in doc: - continue - - doc = doc.replace(label, index) - - return doc - - -def _dedent(doc): - if not isinstance(doc, str): - return doc - - return textwrap.dedent(doc) - - -def _create_op(name): - func = _UFuncDispatcher(name) - func.__name__ = name - doc = getattr(_np, name).__doc__ - - doc = _remove_unused_reference_labels(_skip_signature(_dedent(doc), name)) - - func.__doc__ = ( - f"xarray specific variant of numpy.{name}. Handles " - "xarray.Dataset, xarray.DataArray, xarray.Variable, " - "numpy.ndarray and dask.array.Array objects with " - "automatic dispatching.\n\n" - f"Documentation from numpy:\n\n{doc}" - ) - return func - - -__all__ = ( # noqa: F822 - "angle", - "arccos", - "arccosh", - "arcsin", - "arcsinh", - "arctan", - "arctan2", - "arctanh", - "ceil", - "conj", - "copysign", - "cos", - "cosh", - "deg2rad", - "degrees", - "exp", - "expm1", - "fabs", - "fix", - "floor", - "fmax", - "fmin", - "fmod", - "fmod", - "frexp", - "hypot", - "imag", - "iscomplex", - "isfinite", - "isinf", - "isnan", - "isreal", - "ldexp", - "log", - "log10", - "log1p", - "log2", - "logaddexp", - "logaddexp2", - "logical_and", - "logical_not", - "logical_or", - "logical_xor", - "maximum", - "minimum", - "nextafter", - "rad2deg", - "radians", - "real", - "rint", - "sign", - "signbit", - "sin", - "sinh", - "sqrt", - "square", - "tan", - "tanh", - "trunc", -) - - -for name in __all__: - globals()[name] = _create_op(name)