Skip to content
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Other API changes
- Added :meth:`DataFrame.value_counts` (:issue:`5377`)
- :meth:`Groupby.groups` now returns an abbreviated representation when called on large dataframes (:issue:`1135`)
- ``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`)
- Using a :func:`pandas.api.indexers.BaseIndexer` with ``min``, ``max``, ``std``, ``var``, ``count``, ``skew``, ``cov``, ``corr`` will now raise a ``NotImplementedError`` (:issue:`32865`)
-

Backwards incompatible API changes
Expand Down
28 changes: 25 additions & 3 deletions pandas/core/window/rolling.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
)
from pandas.core.window.numba_ import generate_numba_apply_func

# GH 32865: These functions work correctly with a BaseIndexer subclass
BASEINDEXER_WHITELIST = {"mean", "sum", "median", "kurt", "quantile"}


class _Window(PandasObject, ShallowMixin, SelectionMixin):
_attributes: List[str] = [
Expand Down Expand Up @@ -391,11 +394,16 @@ def _get_cython_func_type(self, func: str) -> Callable:
return self._get_roll_func(f"{func}_variable")
return partial(self._get_roll_func(f"{func}_fixed"), win=self._get_window())

def _get_window_indexer(self, window: int) -> BaseIndexer:
def _get_window_indexer(self, window: int, func_name: Optional[str]) -> BaseIndexer:
"""
Return an indexer class that will compute the window start and end bounds
"""
if isinstance(self.window, BaseIndexer):
if isinstance(func_name, str) and func_name not in BASEINDEXER_WHITELIST:
raise NotImplementedError(
f"{func_name} is not supported with using a BaseIndexer "
f"subclasses. You can use .apply() with {func_name}."
)
return self.window
if self.is_freq_type:
return VariableWindowIndexer(index_array=self._on.asi8, window_size=window)
Expand Down Expand Up @@ -441,7 +449,7 @@ def _apply(

blocks, obj = self._create_blocks()
block_list = list(blocks)
window_indexer = self._get_window_indexer(window)
window_indexer = self._get_window_indexer(window, name)

results = []
exclude: List[Scalar] = []
Expand Down Expand Up @@ -1173,7 +1181,11 @@ class _Rolling_and_Expanding(_Rolling):
)

def count(self):

if isinstance(self.window, BaseIndexer):
raise NotImplementedError(
f"count is not supported with using a BaseIndexer subclasses. "
f"You can use .apply() with count."
)
blocks, obj = self._create_blocks()
results = []
for b in blocks:
Expand Down Expand Up @@ -1627,6 +1639,11 @@ def quantile(self, quantile, interpolation="linear", **kwargs):
"""

def cov(self, other=None, pairwise=None, ddof=1, **kwargs):
if isinstance(self.window, BaseIndexer):
raise NotImplementedError(
f"cov is not supported with using a BaseIndexer subclasses. "
f"You can use .apply() with cov."
)
if other is None:
other = self._selected_obj
# only default unset
Expand Down Expand Up @@ -1770,6 +1787,11 @@ def _get_cov(X, Y):
)

def corr(self, other=None, pairwise=None, **kwargs):
if isinstance(self.window, BaseIndexer):
raise NotImplementedError(
f"corr is not supported with using a BaseIndexer subclasses. "
f"You can use .apply() with corr."
)
if other is None:
other = self._selected_obj
# only default unset
Expand Down
15 changes: 15 additions & 0 deletions pandas/tests/window/test_base_indexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,18 @@ def get_window_bounds(self, num_values, min_periods, center, closed):
indexer = CustomIndexer()
with pytest.raises(NotImplementedError, match="BaseIndexer subclasses not"):
df.rolling(indexer, win_type="boxcar")


@pytest.mark.parametrize(
"func", ["min", "max", "std", "var", "count", "skew", "cov", "corr"]
)
def test_notimplemented_functions(func):
# GH 32865
class CustomIndexer(BaseIndexer):
def get_window_bounds(self, num_values, min_periods, center, closed):
return np.array([0, 1]), np.array([1, 2])

df = DataFrame({"values": range(2)})
indexer = CustomIndexer()
with pytest.raises(NotImplementedError, match=f"{func} is not supported"):
getattr(df.rolling(indexer), func)()