Skip to content

Commit e92a807

Browse files
author
Joe Hamman
authored
Feature/support bn 1 1 (#1278)
* add rolling.var and allow min_periods for bottleck version 1.1 and later * add TODO note for updating minimum suported bottleneck version
1 parent 7f71e40 commit e92a807

File tree

3 files changed

+52
-17
lines changed

3 files changed

+52
-17
lines changed

doc/whats-new.rst

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ v0.9.2 (unreleased)
2121

2222
Enhancements
2323
~~~~~~~~~~~~
24+
- When bottleneck version 1.1 or later is installed, use bottleneck for rolling
25+
`var`, `argmin`, `argmax`, and `rank` computations. Also, `rolling.median`
26+
now also accepts a `min_periods` argument (:issue:`1276`).
27+
By `Joe Hamman <https://github.com/jhamman>`_.
2428

2529
Bug fixes
2630
~~~~~~~~~

xarray/core/ops.py

+31-12
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@
5252
NAN_CUM_METHODS = ['cumsum', 'cumprod']
5353
BOTTLENECK_ROLLING_METHODS = {'move_sum': 'sum', 'move_mean': 'mean',
5454
'move_std': 'std', 'move_min': 'min',
55-
'move_max': 'max'}
55+
'move_max': 'max', 'move_var': 'var',
56+
'move_argmin': 'argmin', 'move_argmax': 'argmax'}
5657
# TODO: wrap take, dot, sort
5758

5859

@@ -520,24 +521,42 @@ def inject_bottleneck_rolling_methods(cls):
520521
for name, f in methods:
521522
func = cls._reduce_method(f)
522523
func.__name__ = name
523-
func.__doc__ = _ROLLING_REDUCE_DOCSTRING_TEMPLATE.format(name=func.__name__)
524+
func.__doc__ = _ROLLING_REDUCE_DOCSTRING_TEMPLATE.format(
525+
name=func.__name__)
524526
setattr(cls, name, func)
525527

526528
# bottleneck rolling methods
527529
if has_bottleneck:
528-
if LooseVersion(bn.__version__) < LooseVersion('1.0'):
530+
# TODO: Bump the required version of bottlneck to 1.1 and remove all
531+
# these version checks (see GH#1278)
532+
bn_version = LooseVersion(bn.__version__)
533+
bn_min_version = LooseVersion('1.0')
534+
bn_version_1_1 = LooseVersion('1.1')
535+
if bn_version < bn_min_version:
529536
return
530537

531538
for bn_name, method_name in BOTTLENECK_ROLLING_METHODS.items():
532-
f = getattr(bn, bn_name)
533-
func = cls._bottleneck_reduce(f)
534-
func.__name__ = method_name
535-
func.__doc__ = _ROLLING_REDUCE_DOCSTRING_TEMPLATE.format(name=func.__name__)
536-
setattr(cls, method_name, func)
537-
538-
# bottleneck rolling methods without min_count
539+
try:
540+
f = getattr(bn, bn_name)
541+
func = cls._bottleneck_reduce(f)
542+
func.__name__ = method_name
543+
func.__doc__ = _ROLLING_REDUCE_DOCSTRING_TEMPLATE.format(
544+
name=func.__name__)
545+
setattr(cls, method_name, func)
546+
except AttributeError as e:
547+
# skip functions not in Bottleneck 1.0
548+
if ((bn_version < bn_version_1_1) and
549+
(bn_name not in ['move_var', 'move_argmin',
550+
'move_argmax', 'move_rank'])):
551+
raise e
552+
553+
# bottleneck rolling methods without min_count (bn.__version__ < 1.1)
539554
f = getattr(bn, 'move_median')
540-
func = cls._bottleneck_reduce_without_min_count(f)
555+
if bn_version >= bn_version_1_1:
556+
func = cls._bottleneck_reduce(f)
557+
else:
558+
func = cls._bottleneck_reduce_without_min_count(f)
541559
func.__name__ = 'median'
542-
func.__doc__ = _ROLLING_REDUCE_DOCSTRING_TEMPLATE.format(name=func.__name__)
560+
func.__doc__ = _ROLLING_REDUCE_DOCSTRING_TEMPLATE.format(
561+
name=func.__name__)
543562
setattr(cls, 'median', func)

xarray/tests/test_dataarray.py

+17-5
Original file line numberDiff line numberDiff line change
@@ -2448,16 +2448,24 @@ def test_rolling_properties(da):
24482448
assert 'min_periods must be greater than zero' in str(exception)
24492449

24502450

2451-
@pytest.mark.parametrize('name', ('sum', 'mean', 'std', 'min', 'max', 'median'))
2451+
@pytest.mark.parametrize('name', ('sum', 'mean', 'std', 'var',
2452+
'min', 'max', 'median'))
24522453
@pytest.mark.parametrize('center', (True, False, None))
24532454
@pytest.mark.parametrize('min_periods', (1, None))
24542455
def test_rolling_wrapped_bottleneck(da, name, center, min_periods):
24552456
pytest.importorskip('bottleneck')
24562457
import bottleneck as bn
24572458

2458-
# skip if median and min_periods
2459-
if (min_periods == 1) and (name == 'median'):
2460-
pytest.skip()
2459+
# skip if median and min_periods bottleneck version < 1.1
2460+
if ((min_periods == 1) and
2461+
(name == 'median') and
2462+
(LooseVersion(bn.__version__) < LooseVersion('1.1'))):
2463+
pytest.skip('rolling median accepts min_periods for bottleneck 1.1')
2464+
2465+
# skip if var and bottleneck version < 1.1
2466+
if ((name == 'median') and
2467+
(LooseVersion(bn.__version__) < LooseVersion('1.1'))):
2468+
pytest.skip('rolling median accepts min_periods for bottleneck 1.1')
24612469

24622470
# Test all bottleneck functions
24632471
rolling_obj = da.rolling(time=7, min_periods=min_periods)
@@ -2473,8 +2481,12 @@ def test_rolling_wrapped_bottleneck(da, name, center, min_periods):
24732481
actual = getattr(rolling_obj, name)()['time']
24742482
assert_equal(actual, da['time'])
24752483

2484+
24762485
def test_rolling_invalid_args(da):
2477-
pytest.importorskip('bottleneck')
2486+
pytest.importorskip('bottleneck', minversion="1.0")
2487+
import bottleneck as bn
2488+
if LooseVersion(bn.__version__) >= LooseVersion('1.1'):
2489+
pytest.skip('rolling median accepts min_periods for bottleneck 1.1')
24782490
with pytest.raises(ValueError) as exception:
24792491
da.rolling(time=7, min_periods=1).median()
24802492
assert 'Rolling.median does not' in str(exception)

0 commit comments

Comments
 (0)