Skip to content

Commit 3956b73

Browse files
authored
conditionally disable bottleneck (#5560)
1 parent 4bb9d9c commit 3956b73

10 files changed

+103
-6
lines changed

doc/whats-new.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ v0.19.1 (unreleased)
2222

2323
New Features
2424
~~~~~~~~~~~~
25+
- Add a option to disable the use of ``bottleneck`` (:pull:`5560`)
26+
By `Justus Magin <https://github.com/keewis>`_.
27+
- Added ``**kwargs`` argument to :py:meth:`open_rasterio` to access overviews (:issue:`3269`).
28+
By `Pushkar Kopparla <https://github.com/pkopparla>`_.
2529

2630

2731
Breaking changes
@@ -104,8 +108,6 @@ New Features
104108
- Allow removal of the coordinate attribute ``coordinates`` on variables by setting ``.attrs['coordinates']= None``
105109
(:issue:`5510`).
106110
By `Elle Smith <https://github.com/ellesmith88>`_.
107-
- Added ``**kwargs`` argument to :py:meth:`open_rasterio` to access overviews (:issue:`3269`).
108-
By `Pushkar Kopparla <https://github.com/pkopparla>`_.
109111
- Added :py:meth:`DataArray.to_numpy`, :py:meth:`DataArray.as_numpy`, and :py:meth:`Dataset.as_numpy`. (:pull:`5568`).
110112
By `Tom Nicholas <https://github.com/TomNicholas>`_.
111113
- Units in plot labels are now automatically inferred from wrapped :py:meth:`pint.Quantity` arrays. (:pull:`5561`).

xarray/core/dataset.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6228,6 +6228,12 @@ def rank(self, dim, pct=False, keep_attrs=None):
62286228
ranked : Dataset
62296229
Variables that do not depend on `dim` are dropped.
62306230
"""
6231+
if not OPTIONS["use_bottleneck"]:
6232+
raise RuntimeError(
6233+
"rank requires bottleneck to be enabled."
6234+
" Call `xr.set_options(use_bottleneck=True)` to enable it."
6235+
)
6236+
62316237
if dim not in self.dims:
62326238
raise ValueError(f"Dataset does not contain the dimension: {dim}")
62336239

xarray/core/missing.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from .common import _contains_datetime_like_objects, ones_like
1313
from .computation import apply_ufunc
1414
from .duck_array_ops import datetime_to_numeric, push, timedelta_to_numeric
15-
from .options import _get_keep_attrs
15+
from .options import OPTIONS, _get_keep_attrs
1616
from .pycompat import dask_version, is_duck_dask_array
1717
from .utils import OrderedSet, is_scalar
1818
from .variable import Variable, broadcast_variables
@@ -405,6 +405,12 @@ def _bfill(arr, n=None, axis=-1):
405405

406406
def ffill(arr, dim=None, limit=None):
407407
"""forward fill missing values"""
408+
if not OPTIONS["use_bottleneck"]:
409+
raise RuntimeError(
410+
"ffill requires bottleneck to be enabled."
411+
" Call `xr.set_options(use_bottleneck=True)` to enable it."
412+
)
413+
408414
axis = arr.get_axis_num(dim)
409415

410416
# work around for bottleneck 178
@@ -422,6 +428,12 @@ def ffill(arr, dim=None, limit=None):
422428

423429
def bfill(arr, dim=None, limit=None):
424430
"""backfill missing values"""
431+
if not OPTIONS["use_bottleneck"]:
432+
raise RuntimeError(
433+
"bfill requires bottleneck to be enabled."
434+
" Call `xr.set_options(use_bottleneck=True)` to enable it."
435+
)
436+
425437
axis = arr.get_axis_num(dim)
426438

427439
# work around for bottleneck 178

xarray/core/nputils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import pandas as pd
55
from numpy.core.multiarray import normalize_axis_index # type: ignore[attr-defined]
66

7+
from .options import OPTIONS
8+
79
try:
810
import bottleneck as bn
911

@@ -138,6 +140,7 @@ def f(values, axis=None, **kwargs):
138140

139141
if (
140142
_USE_BOTTLENECK
143+
and OPTIONS["use_bottleneck"]
141144
and isinstance(values, np.ndarray)
142145
and bn_func is not None
143146
and not isinstance(axis, tuple)

xarray/core/options.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
FILE_CACHE_MAXSIZE = "file_cache_maxsize"
1515
KEEP_ATTRS = "keep_attrs"
1616
WARN_FOR_UNCLOSED_FILES = "warn_for_unclosed_files"
17+
USE_BOTTLENECK = "use_bottleneck"
1718

1819

1920
OPTIONS = {
@@ -31,6 +32,7 @@
3132
FILE_CACHE_MAXSIZE: 128,
3233
KEEP_ATTRS: "default",
3334
WARN_FOR_UNCLOSED_FILES: False,
35+
USE_BOTTLENECK: True,
3436
}
3537

3638
_JOIN_OPTIONS = frozenset(["inner", "outer", "left", "right", "exact"])
@@ -54,6 +56,7 @@ def _positive_integer(value):
5456
FILE_CACHE_MAXSIZE: _positive_integer,
5557
KEEP_ATTRS: lambda choice: choice in [True, False, "default"],
5658
WARN_FOR_UNCLOSED_FILES: lambda value: isinstance(value, bool),
59+
USE_BOTTLENECK: lambda choice: choice in [True, False],
5760
}
5861

5962

@@ -122,6 +125,9 @@ class set_options:
122125
attrs, ``False`` to always discard them, or ``'default'`` to use original
123126
logic that attrs should only be kept in unambiguous circumstances.
124127
Default: ``'default'``.
128+
- ``use_bottleneck``: allow using bottleneck. Either ``True`` to accelerate
129+
operations using bottleneck if it is installed or ``False`` to never use it.
130+
Default: ``True``
125131
- ``display_style``: display style to use in jupyter for xarray objects.
126132
Default: ``'html'``. Other options are ``'text'``.
127133
- ``display_expand_attrs``: whether to expand the attributes section for

xarray/core/rolling.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from . import dtypes, duck_array_ops, utils
99
from .arithmetic import CoarsenArithmetic
10-
from .options import _get_keep_attrs
10+
from .options import OPTIONS, _get_keep_attrs
1111
from .pycompat import is_duck_dask_array
1212
from .utils import either_dict_or_kwargs
1313

@@ -517,7 +517,8 @@ def _numpy_or_bottleneck_reduce(
517517
del kwargs["dim"]
518518

519519
if (
520-
bottleneck_move_func is not None
520+
OPTIONS["use_bottleneck"]
521+
and bottleneck_move_func is not None
521522
and not is_duck_dask_array(self.obj.data)
522523
and len(self.dim) == 1
523524
):

xarray/core/variable.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
VectorizedIndexer,
3434
as_indexable,
3535
)
36-
from .options import _get_keep_attrs
36+
from .options import OPTIONS, _get_keep_attrs
3737
from .pycompat import (
3838
DuckArrayModule,
3939
cupy_array_type,
@@ -2052,6 +2052,12 @@ def rank(self, dim, pct=False):
20522052
--------
20532053
Dataset.rank, DataArray.rank
20542054
"""
2055+
if not OPTIONS["use_bottleneck"]:
2056+
raise RuntimeError(
2057+
"rank requires bottleneck to be enabled."
2058+
" Call `xr.set_options(use_bottleneck=True)` to enable it."
2059+
)
2060+
20552061
import bottleneck as bn
20562062

20572063
data = self.data

xarray/tests/test_dataset.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4972,6 +4972,12 @@ def test_rank(self):
49724972
with pytest.raises(ValueError, match=r"does not contain"):
49734973
x.rank("invalid_dim")
49744974

4975+
def test_rank_use_bottleneck(self):
4976+
ds = Dataset({"a": ("x", [0, np.nan, 2]), "b": ("y", [4, 6, 3, 4])})
4977+
with xr.set_options(use_bottleneck=False):
4978+
with pytest.raises(RuntimeError):
4979+
ds.rank("x")
4980+
49754981
def test_count(self):
49764982
ds = Dataset({"x": ("a", [np.nan, 1]), "y": 0, "z": np.nan})
49774983
expected = Dataset({"x": 1, "y": 1, "z": 0})

xarray/tests/test_missing.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,38 @@ def test_ffill():
392392
assert_equal(actual, expected)
393393

394394

395+
def test_ffill_use_bottleneck():
396+
da = xr.DataArray(np.array([4, 5, np.nan], dtype=np.float64), dims="x")
397+
with xr.set_options(use_bottleneck=False):
398+
with pytest.raises(RuntimeError):
399+
da.ffill("x")
400+
401+
402+
@requires_dask
403+
def test_ffill_use_bottleneck_dask():
404+
da = xr.DataArray(np.array([4, 5, np.nan], dtype=np.float64), dims="x")
405+
da = da.chunk({"x": 1})
406+
with xr.set_options(use_bottleneck=False):
407+
with pytest.raises(RuntimeError):
408+
da.ffill("x")
409+
410+
411+
def test_bfill_use_bottleneck():
412+
da = xr.DataArray(np.array([4, 5, np.nan], dtype=np.float64), dims="x")
413+
with xr.set_options(use_bottleneck=False):
414+
with pytest.raises(RuntimeError):
415+
da.bfill("x")
416+
417+
418+
@requires_dask
419+
def test_bfill_use_bottleneck_dask():
420+
da = xr.DataArray(np.array([4, 5, np.nan], dtype=np.float64), dims="x")
421+
da = da.chunk({"x": 1})
422+
with xr.set_options(use_bottleneck=False):
423+
with pytest.raises(RuntimeError):
424+
da.bfill("x")
425+
426+
395427
@requires_bottleneck
396428
@requires_dask
397429
@pytest.mark.parametrize("method", ["ffill", "bfill"])

xarray/tests/test_variable.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,23 @@ def test_reduce(self):
16731673
with pytest.raises(ValueError, match=r"cannot supply both"):
16741674
v.mean(dim="x", axis=0)
16751675

1676+
@requires_bottleneck
1677+
def test_reduce_use_bottleneck(self, monkeypatch):
1678+
def raise_if_called(*args, **kwargs):
1679+
raise RuntimeError("should not have been called")
1680+
1681+
import bottleneck as bn
1682+
1683+
monkeypatch.setattr(bn, "nanmin", raise_if_called)
1684+
1685+
v = Variable("x", [0.0, np.nan, 1.0])
1686+
with pytest.raises(RuntimeError, match="should not have been called"):
1687+
with set_options(use_bottleneck=True):
1688+
v.min()
1689+
1690+
with set_options(use_bottleneck=False):
1691+
v.min()
1692+
16761693
@pytest.mark.parametrize("skipna", [True, False])
16771694
@pytest.mark.parametrize("q", [0.25, [0.50], [0.25, 0.75]])
16781695
@pytest.mark.parametrize(
@@ -1720,6 +1737,12 @@ def test_rank_dask_raises(self):
17201737
with pytest.raises(TypeError, match=r"arrays stored as dask"):
17211738
v.rank("x")
17221739

1740+
def test_rank_use_bottleneck(self):
1741+
v = Variable(["x"], [3.0, 1.0, np.nan, 2.0, 4.0])
1742+
with set_options(use_bottleneck=False):
1743+
with pytest.raises(RuntimeError):
1744+
v.rank("x")
1745+
17231746
@requires_bottleneck
17241747
def test_rank(self):
17251748
import bottleneck as bn

0 commit comments

Comments
 (0)