Skip to content

Commit 2bf54a8

Browse files
authored
ERR: Raise NotImplementedError with BaseIndexer and certain rolling operations (pandas-dev#33057)
1 parent 1e8f6b2 commit 2bf54a8

File tree

4 files changed

+38
-2
lines changed

4 files changed

+38
-2
lines changed

doc/source/whatsnew/v1.1.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Other API changes
8585
- Added :meth:`DataFrame.value_counts` (:issue:`5377`)
8686
- :meth:`Groupby.groups` now returns an abbreviated representation when called on large dataframes (:issue:`1135`)
8787
- ``loc`` lookups with an object-dtype :class:`Index` and an integer key will now raise ``KeyError`` instead of ``TypeError`` when key is missing (:issue:`31905`)
88+
- Using a :func:`pandas.api.indexers.BaseIndexer` with ``min``, ``max``, ``std``, ``var``, ``count``, ``skew``, ``cov``, ``corr`` will now raise a ``NotImplementedError`` (:issue:`32865`)
8889
-
8990

9091
Backwards incompatible API changes

pandas/core/window/common.py

+10
Original file line numberDiff line numberDiff line change
@@ -323,3 +323,13 @@ def func(arg, window, min_periods=None):
323323
return cfunc(arg, window, min_periods)
324324

325325
return func
326+
327+
328+
def validate_baseindexer_support(func_name: Optional[str]) -> None:
329+
# GH 32865: These functions work correctly with a BaseIndexer subclass
330+
BASEINDEXER_WHITELIST = {"mean", "sum", "median", "kurt", "quantile"}
331+
if isinstance(func_name, str) and func_name not in BASEINDEXER_WHITELIST:
332+
raise NotImplementedError(
333+
f"{func_name} is not supported with using a BaseIndexer "
334+
f"subclasses. You can use .apply() with {func_name}."
335+
)

pandas/core/window/rolling.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
calculate_center_offset,
4747
calculate_min_periods,
4848
get_weighted_roll_func,
49+
validate_baseindexer_support,
4950
zsqrt,
5051
)
5152
from pandas.core.window.indexers import (
@@ -391,11 +392,12 @@ def _get_cython_func_type(self, func: str) -> Callable:
391392
return self._get_roll_func(f"{func}_variable")
392393
return partial(self._get_roll_func(f"{func}_fixed"), win=self._get_window())
393394

394-
def _get_window_indexer(self, window: int) -> BaseIndexer:
395+
def _get_window_indexer(self, window: int, func_name: Optional[str]) -> BaseIndexer:
395396
"""
396397
Return an indexer class that will compute the window start and end bounds
397398
"""
398399
if isinstance(self.window, BaseIndexer):
400+
validate_baseindexer_support(func_name)
399401
return self.window
400402
if self.is_freq_type:
401403
return VariableWindowIndexer(index_array=self._on.asi8, window_size=window)
@@ -441,7 +443,7 @@ def _apply(
441443

442444
blocks, obj = self._create_blocks()
443445
block_list = list(blocks)
444-
window_indexer = self._get_window_indexer(window)
446+
window_indexer = self._get_window_indexer(window, name)
445447

446448
results = []
447449
exclude: List[Scalar] = []
@@ -1173,6 +1175,8 @@ class _Rolling_and_Expanding(_Rolling):
11731175
)
11741176

11751177
def count(self):
1178+
if isinstance(self.window, BaseIndexer):
1179+
validate_baseindexer_support("count")
11761180

11771181
blocks, obj = self._create_blocks()
11781182
results = []
@@ -1627,6 +1631,9 @@ def quantile(self, quantile, interpolation="linear", **kwargs):
16271631
"""
16281632

16291633
def cov(self, other=None, pairwise=None, ddof=1, **kwargs):
1634+
if isinstance(self.window, BaseIndexer):
1635+
validate_baseindexer_support("cov")
1636+
16301637
if other is None:
16311638
other = self._selected_obj
16321639
# only default unset
@@ -1770,6 +1777,9 @@ def _get_cov(X, Y):
17701777
)
17711778

17721779
def corr(self, other=None, pairwise=None, **kwargs):
1780+
if isinstance(self.window, BaseIndexer):
1781+
validate_baseindexer_support("corr")
1782+
17731783
if other is None:
17741784
other = self._selected_obj
17751785
# only default unset

pandas/tests/window/test_base_indexer.py

+15
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,18 @@ def get_window_bounds(self, num_values, min_periods, center, closed):
8080
indexer = CustomIndexer()
8181
with pytest.raises(NotImplementedError, match="BaseIndexer subclasses not"):
8282
df.rolling(indexer, win_type="boxcar")
83+
84+
85+
@pytest.mark.parametrize(
86+
"func", ["min", "max", "std", "var", "count", "skew", "cov", "corr"]
87+
)
88+
def test_notimplemented_functions(func):
89+
# GH 32865
90+
class CustomIndexer(BaseIndexer):
91+
def get_window_bounds(self, num_values, min_periods, center, closed):
92+
return np.array([0, 1]), np.array([1, 2])
93+
94+
df = DataFrame({"values": range(2)})
95+
indexer = CustomIndexer()
96+
with pytest.raises(NotImplementedError, match=f"{func} is not supported"):
97+
getattr(df.rolling(indexer), func)()

0 commit comments

Comments
 (0)