Skip to content

Commit 4788def

Browse files
mroeschkeJulianWgs
authored andcommitted
ENH: Support closed for fixed windows in rolling (pandas-dev#37207)
1 parent bac7c3c commit 4788def

File tree

6 files changed

+45
-26
lines changed

6 files changed

+45
-26
lines changed

doc/source/user_guide/computation.rst

+2-5
Original file line numberDiff line numberDiff line change
@@ -652,9 +652,9 @@ parameter:
652652
:header: "``closed``", "Description", "Default for"
653653
:widths: 20, 30, 30
654654

655-
``right``, close right endpoint, time-based windows
655+
``right``, close right endpoint,
656656
``left``, close left endpoint,
657-
``both``, close both endpoints, fixed windows
657+
``both``, close both endpoints,
658658
``neither``, open endpoints,
659659

660660
For example, having the right endpoint open is useful in many problems that require that there is no contamination
@@ -681,9 +681,6 @@ from present information back to past information. This allows the rolling windo
681681
682682
df
683683
684-
Currently, this feature is only implemented for time-based windows.
685-
For fixed windows, the closed parameter cannot be set and the rolling window will always have both endpoints closed.
686-
687684
.. _stats.iter_rolling_window:
688685

689686
Iteration over window:

doc/source/whatsnew/v1.2.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ Other enhancements
222222
- :meth:`DataFrame.plot` now recognizes ``xlabel`` and ``ylabel`` arguments for plots of type ``scatter`` and ``hexbin`` (:issue:`37001`)
223223
- :class:`DataFrame` now supports ``divmod`` operation (:issue:`37165`)
224224
- :meth:`DataFrame.to_parquet` now returns a ``bytes`` object when no ``path`` argument is passed (:issue:`37105`)
225+
- :class:`Rolling` now supports the ``closed`` argument for fixed windows (:issue:`34315`)
225226

226227
.. _whatsnew_120.api_breaking.python:
227228

pandas/_libs/window/indexers.pyx

+4-6
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,14 @@ def calculate_variable_window_bounds(
4343
(ndarray[int64], ndarray[int64])
4444
"""
4545
cdef:
46-
bint left_closed = False
47-
bint right_closed = False
48-
int index_growth_sign = 1
46+
bint left_closed = False, right_closed = False
4947
ndarray[int64_t, ndim=1] start, end
50-
int64_t start_bound, end_bound
48+
int64_t start_bound, end_bound, index_growth_sign = 1
5149
Py_ssize_t i, j
5250

53-
# if windows is variable, default is 'right', otherwise default is 'both'
51+
# default is 'right'
5452
if closed is None:
55-
closed = 'right' if index is not None else 'both'
53+
closed = 'right'
5654

5755
if closed in ['right', 'both']:
5856
right_closed = True

pandas/core/window/indexers.py

+4
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ def get_window_bounds(
8585

8686
end = np.arange(1 + offset, num_values + 1 + offset, dtype="int64")
8787
start = end - self.window_size
88+
if closed in ["left", "both"]:
89+
start -= 1
90+
if closed in ["left", "neither"]:
91+
end -= 1
8892

8993
end = np.clip(end, 0, num_values)
9094
start = np.clip(start, 0, num_values)

pandas/core/window/rolling.py

+5-9
Original file line numberDiff line numberDiff line change
@@ -850,10 +850,11 @@ class Window(BaseWindow):
850850
axis : int or str, default 0
851851
closed : str, default None
852852
Make the interval closed on the 'right', 'left', 'both' or
853-
'neither' endpoints.
854-
For offset-based windows, it defaults to 'right'.
855-
For fixed windows, defaults to 'both'. Remaining cases not implemented
856-
for fixed windows.
853+
'neither' endpoints. Defaults to 'right'.
854+
855+
.. versionchanged:: 1.2.0
856+
857+
The closed parameter with fixed windows is now supported.
857858
858859
Returns
859860
-------
@@ -1976,11 +1977,6 @@ def validate(self):
19761977
elif self.window < 0:
19771978
raise ValueError("window must be non-negative")
19781979

1979-
if not self.is_datetimelike and self.closed is not None:
1980-
raise ValueError(
1981-
"closed only implemented for datetimelike and offset based windows"
1982-
)
1983-
19841980
def _determine_window_length(self) -> Union[int, float]:
19851981
"""
19861982
Calculate freq for PeriodIndexes based on Index freq. Can not use

pandas/tests/window/test_rolling.py

+29-6
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,37 @@ def test_numpy_compat(method):
122122
getattr(r, method)(dtype=np.float64)
123123

124124

125-
def test_closed():
126-
df = DataFrame({"A": [0, 1, 2, 3, 4]})
127-
# closed only allowed for datetimelike
125+
@pytest.mark.parametrize("closed", ["left", "right", "both", "neither"])
126+
def test_closed_fixed(closed, arithmetic_win_operators):
127+
# GH 34315
128+
func_name = arithmetic_win_operators
129+
df_fixed = DataFrame({"A": [0, 1, 2, 3, 4]})
130+
df_time = DataFrame({"A": [0, 1, 2, 3, 4]}, index=date_range("2020", periods=5))
128131

129-
msg = "closed only implemented for datetimelike and offset based windows"
132+
result = getattr(df_fixed.rolling(2, closed=closed, min_periods=1), func_name)()
133+
expected = getattr(df_time.rolling("2D", closed=closed), func_name)().reset_index(
134+
drop=True
135+
)
130136

131-
with pytest.raises(ValueError, match=msg):
132-
df.rolling(window=3, closed="neither")
137+
tm.assert_frame_equal(result, expected)
138+
139+
140+
def test_closed_fixed_binary_col():
141+
# GH 34315
142+
data = [0, 1, 1, 0, 0, 1, 0, 1]
143+
df = DataFrame(
144+
{"binary_col": data},
145+
index=pd.date_range(start="2020-01-01", freq="min", periods=len(data)),
146+
)
147+
148+
rolling = df.rolling(window=len(df), closed="left", min_periods=1)
149+
result = rolling.mean()
150+
expected = DataFrame(
151+
[np.nan, 0, 0.5, 2 / 3, 0.5, 0.4, 0.5, 0.428571],
152+
columns=["binary_col"],
153+
index=pd.date_range(start="2020-01-01", freq="min", periods=len(data)),
154+
)
155+
tm.assert_frame_equal(result, expected)
133156

134157

135158
@pytest.mark.parametrize("closed", ["neither", "left"])

0 commit comments

Comments
 (0)