Skip to content

Commit e99e35a

Browse files
committed
WIP
1 parent b27d7e4 commit e99e35a

File tree

6 files changed

+109
-13
lines changed

6 files changed

+109
-13
lines changed

doc/source/whatsnew/v1.5.0.rst

+3
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,9 @@ gained the ``numeric_only`` argument.
651651
- :meth:`.Resampler.sem`
652652
- :meth:`.Resampler.std`
653653
- :meth:`.Resampler.var`
654+
- :meth:`DataFrame.rolling` operations
655+
- :meth:`DataFrame.expanding` operations
656+
- :meth:`DataFrame.ewm` operations
654657

655658
.. _whatsnew_150.deprecations.other:
656659

pandas/core/window/ewm.py

+18-6
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626
from pandas.util._decorators import doc
2727
from pandas.util._exceptions import find_stack_level
2828

29-
from pandas.core.dtypes.common import is_datetime64_ns_dtype
29+
from pandas.core.dtypes.common import (
30+
is_datetime64_ns_dtype,
31+
is_numeric_dtype,
32+
)
3033
from pandas.core.dtypes.missing import isna
3134

3235
import pandas.core.common as common # noqa: PDF018
@@ -554,7 +557,7 @@ def mean(
554557
deltas=tuple(self._deltas),
555558
normalize=True,
556559
)
557-
return self._apply(ewm_func)
560+
return self._apply(ewm_func, name="mean")
558561
elif engine in ("cython", None):
559562
if engine_kwargs is not None:
560563
raise ValueError("cython engine does not accept engine_kwargs")
@@ -569,7 +572,7 @@ def mean(
569572
deltas=deltas,
570573
normalize=True,
571574
)
572-
return self._apply(window_func, numeric_only=numeric_only)
575+
return self._apply(window_func, name="mean", numeric_only=numeric_only)
573576
else:
574577
raise ValueError("engine must be either 'numba' or 'cython'")
575578

@@ -613,7 +616,7 @@ def sum(
613616
deltas=tuple(self._deltas),
614617
normalize=False,
615618
)
616-
return self._apply(ewm_func)
619+
return self._apply(ewm_func, name="sum")
617620
elif engine in ("cython", None):
618621
if engine_kwargs is not None:
619622
raise ValueError("cython engine does not accept engine_kwargs")
@@ -628,7 +631,7 @@ def sum(
628631
deltas=deltas,
629632
normalize=False,
630633
)
631-
return self._apply(window_func, numeric_only=numeric_only)
634+
return self._apply(window_func, name="sum", numeric_only=numeric_only)
632635
else:
633636
raise ValueError("engine must be either 'numba' or 'cython'")
634637

@@ -654,6 +657,15 @@ def sum(
654657
)
655658
def std(self, bias: bool = False, numeric_only: bool = False, *args, **kwargs):
656659
nv.validate_window_func("std", args, kwargs)
660+
if (
661+
numeric_only
662+
and self._selected_obj.ndim == 1
663+
and not is_numeric_dtype(self._selected_obj.dtype)
664+
):
665+
# Raise directly so error message says std instead of var
666+
raise NotImplementedError(
667+
f"{type(self).__name__}.std does not implement numeric_only"
668+
)
657669
return zsqrt(self.var(bias=bias, numeric_only=numeric_only, **kwargs))
658670

659671
def vol(self, bias: bool = False, *args, **kwargs):
@@ -701,7 +713,7 @@ def var(self, bias: bool = False, numeric_only: bool = False, *args, **kwargs):
701713
def var_func(values, begin, end, min_periods):
702714
return wfunc(values, begin, end, min_periods, values)
703715

704-
return self._apply(var_func, numeric_only=numeric_only)
716+
return self._apply(var_func, name="var", numeric_only=numeric_only)
705717

706718
@doc(
707719
template_header,

pandas/core/window/rolling.py

+31-4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
is_bool,
4343
is_integer,
4444
is_list_like,
45+
is_numeric_dtype,
4546
is_scalar,
4647
needs_i8_conversion,
4748
)
@@ -259,6 +260,26 @@ def _slice_axis_for_step(self, index: Index, result: Sized | None = None) -> Ind
259260
else index[:: self.step]
260261
)
261262

263+
def _validate_numeric_only(self, name: str, numeric_only: bool) -> None:
264+
"""
265+
Validate numeric_only argument, raising if invalid for the input.
266+
267+
Parameters
268+
----------
269+
name : str
270+
Name of the operator (kernel).
271+
numeric_only : bool
272+
Value passed by user.
273+
"""
274+
if (
275+
self._selected_obj.ndim == 1
276+
and numeric_only
277+
and not is_numeric_dtype(self._selected_obj.dtype)
278+
):
279+
raise NotImplementedError(
280+
f"{type(self).__name__}.{name} does not implement numeric_only"
281+
)
282+
262283
def _make_numeric_only(self, obj: NDFrameT) -> NDFrameT:
263284
"""Subset DataFrame to numeric columns.
264285
@@ -469,13 +490,14 @@ def _apply_series(
469490
def _apply_blockwise(
470491
self,
471492
homogeneous_func: Callable[..., ArrayLike],
472-
name: str | None = None,
493+
name: str,
473494
numeric_only: bool = False,
474495
) -> DataFrame | Series:
475496
"""
476497
Apply the given function to the DataFrame broken down into homogeneous
477498
sub-frames.
478499
"""
500+
self._validate_numeric_only(name, numeric_only)
479501
if self._selected_obj.ndim == 1:
480502
return self._apply_series(homogeneous_func, name)
481503

@@ -583,7 +605,7 @@ def _apply_pairwise(
583605
def _apply(
584606
self,
585607
func: Callable[..., Any],
586-
name: str | None = None,
608+
name: str,
587609
numeric_only: bool = False,
588610
numba_args: tuple[Any, ...] = (),
589611
**kwargs,
@@ -726,7 +748,7 @@ def __init__(
726748
def _apply(
727749
self,
728750
func: Callable[..., Any],
729-
name: str | None = None,
751+
name: str,
730752
numeric_only: bool = False,
731753
numba_args: tuple[Any, ...] = (),
732754
**kwargs,
@@ -1166,7 +1188,7 @@ def _center_window(self, result: np.ndarray, offset: int) -> np.ndarray:
11661188
def _apply(
11671189
self,
11681190
func: Callable[[np.ndarray, int, int], np.ndarray],
1169-
name: str | None = None,
1191+
name: str,
11701192
numeric_only: bool = False,
11711193
numba_args: tuple[Any, ...] = (),
11721194
**kwargs,
@@ -1398,6 +1420,7 @@ def apply(
13981420

13991421
return self._apply(
14001422
apply_func,
1423+
name="apply",
14011424
numba_args=numba_args,
14021425
)
14031426

@@ -1617,6 +1640,8 @@ def skew(self, numeric_only: bool = False, **kwargs):
16171640

16181641
def sem(self, ddof: int = 1, numeric_only: bool = False, *args, **kwargs):
16191642
nv.validate_rolling_func("sem", args, kwargs)
1643+
# Raise here so error message says sem instead of std
1644+
self._validate_numeric_only("sem", numeric_only)
16201645
return self.std(numeric_only=numeric_only, **kwargs) / (
16211646
self.count(numeric_only=numeric_only) - ddof
16221647
).pow(0.5)
@@ -2428,6 +2453,8 @@ def skew(self, numeric_only: bool = False, **kwargs):
24282453
)
24292454
def sem(self, ddof: int = 1, numeric_only: bool = False, *args, **kwargs):
24302455
nv.validate_rolling_func("sem", args, kwargs)
2456+
# Raise here so error message says sem instead of std
2457+
self._validate_numeric_only("sem", numeric_only)
24312458
return self.std(numeric_only=numeric_only, **kwargs) / (
24322459
self.count(numeric_only) - ddof
24332460
).pow(0.5)

pandas/tests/window/test_ewm.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,7 @@ def test_ewm_pairwise_cov_corr(func, frame):
668668
tm.assert_series_equal(result, expected, check_names=False)
669669

670670

671-
def test_numeric_only(arithmetic_win_operators, numeric_only):
671+
def test_numeric_only_frame(arithmetic_win_operators, numeric_only):
672672
# GH#46560
673673
kernel = arithmetic_win_operators
674674
df = DataFrame({"a": [1], "b": 2, "c": 3})
@@ -683,3 +683,23 @@ def test_numeric_only(arithmetic_win_operators, numeric_only):
683683
assert list(expected.columns) == columns
684684

685685
tm.assert_frame_equal(result, expected)
686+
687+
688+
@pytest.mark.parametrize("dtype", [int, object])
689+
def test_numeric_only_series(arithmetic_win_operators, numeric_only, dtype):
690+
# GH#46560
691+
kernel = arithmetic_win_operators
692+
ser = Series([1], dtype=dtype)
693+
ewm = ser.ewm(span=2, min_periods=1)
694+
op = getattr(ewm, kernel, None)
695+
if op is None:
696+
# Nothing to test
697+
return
698+
if numeric_only and dtype is object:
699+
msg = f"ExponentialMovingWindow.{kernel} does not implement numeric_only"
700+
with pytest.raises(NotImplementedError, match=msg):
701+
op(numeric_only=numeric_only)
702+
else:
703+
result = op(numeric_only=numeric_only)
704+
expected = ser.agg([kernel]).reset_index(drop=True).astype(float)
705+
tm.assert_series_equal(result, expected)

pandas/tests/window/test_expanding.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ def mean_w_arg(x, const):
653653
tm.assert_frame_equal(result, expected)
654654

655655

656-
def test_numeric_only(arithmetic_win_operators, numeric_only):
656+
def test_numeric_only_frame(arithmetic_win_operators, numeric_only):
657657
# GH#46560
658658
kernel = arithmetic_win_operators
659659
df = DataFrame({"a": [1], "b": 2, "c": 3})
@@ -668,3 +668,20 @@ def test_numeric_only(arithmetic_win_operators, numeric_only):
668668
assert list(expected.columns) == columns
669669

670670
tm.assert_frame_equal(result, expected)
671+
672+
673+
@pytest.mark.parametrize("dtype", [int, object])
674+
def test_numeric_only_series(arithmetic_win_operators, numeric_only, dtype):
675+
# GH#46560
676+
kernel = arithmetic_win_operators
677+
ser = Series([1], dtype=dtype)
678+
expanding = ser.expanding()
679+
op = getattr(expanding, kernel)
680+
if numeric_only and dtype is object:
681+
msg = f"Expanding.{kernel} does not implement numeric_only"
682+
with pytest.raises(NotImplementedError, match=msg):
683+
op(numeric_only=numeric_only)
684+
else:
685+
result = op(numeric_only=numeric_only)
686+
expected = ser.agg([kernel]).reset_index(drop=True).astype(float)
687+
tm.assert_series_equal(result, expected)

pandas/tests/window/test_rolling.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -1873,7 +1873,7 @@ def test_rolling_skew_kurt_floating_artifacts():
18731873
assert (result[-2:] == -3).all()
18741874

18751875

1876-
def test_numeric_only(arithmetic_win_operators, numeric_only):
1876+
def test_numeric_only_frame(arithmetic_win_operators, numeric_only):
18771877
# GH#46560
18781878
kernel = arithmetic_win_operators
18791879
df = DataFrame({"a": [1], "b": 2, "c": 3})
@@ -1887,3 +1887,20 @@ def test_numeric_only(arithmetic_win_operators, numeric_only):
18871887
assert list(expected.columns) == columns
18881888

18891889
tm.assert_frame_equal(result, expected)
1890+
1891+
1892+
@pytest.mark.parametrize("dtype", [int, object])
1893+
def test_numeric_only_series(arithmetic_win_operators, numeric_only, dtype):
1894+
# GH#46560
1895+
kernel = arithmetic_win_operators
1896+
ser = Series([1], dtype=dtype)
1897+
rolling = ser.rolling(2, min_periods=1)
1898+
op = getattr(rolling, kernel)
1899+
if numeric_only and dtype is object:
1900+
msg = f"Rolling.{kernel} does not implement numeric_only"
1901+
with pytest.raises(NotImplementedError, match=msg):
1902+
op(numeric_only=numeric_only)
1903+
else:
1904+
result = op(numeric_only=numeric_only)
1905+
expected = ser.agg([kernel]).reset_index(drop=True).astype(float)
1906+
tm.assert_series_equal(result, expected)

0 commit comments

Comments
 (0)