Skip to content

Commit daae221

Browse files
Backport PR #32386: BUG: Fix rolling functions with variable windows on decreasing index (#32606)
Co-authored-by: Jan Škoda <[email protected]>
1 parent e91f46d commit daae221

File tree

4 files changed

+29
-16
lines changed

4 files changed

+29
-16
lines changed

doc/source/whatsnew/v1.0.2.rst

+4
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ Bug fixes
9090

9191
- Using ``pd.NA`` with :meth:`Series.str.repeat` now correctly outputs a null value instead of raising error for vector inputs (:issue:`31632`)
9292

93+
**Rolling**
94+
95+
- Fixed rolling operations with variable window (defined by time duration) on decreasing time index (:issue:`32385`).
96+
9397
.. ---------------------------------------------------------------------------
9498
9599
.. _whatsnew_102.contributors:

pandas/_libs/window/aggregations.pyx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,7 @@ def roll_max_variable(ndarray[float64_t] values, ndarray[int64_t] start,
10081008
def roll_min_fixed(ndarray[float64_t] values, ndarray[int64_t] start,
10091009
ndarray[int64_t] end, int64_t minp, int64_t win):
10101010
"""
1011-
Moving max of 1d array of any numeric type along axis=0 ignoring NaNs.
1011+
Moving min of 1d array of any numeric type along axis=0 ignoring NaNs.
10121012
10131013
Parameters
10141014
----------
@@ -1025,7 +1025,7 @@ def roll_min_fixed(ndarray[float64_t] values, ndarray[int64_t] start,
10251025
def roll_min_variable(ndarray[float64_t] values, ndarray[int64_t] start,
10261026
ndarray[int64_t] end, int64_t minp):
10271027
"""
1028-
Moving max of 1d array of any numeric type along axis=0 ignoring NaNs.
1028+
Moving min of 1d array of any numeric type along axis=0 ignoring NaNs.
10291029
10301030
Parameters
10311031
----------

pandas/_libs/window/indexers.pyx

+7-3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def calculate_variable_window_bounds(
4444
cdef:
4545
bint left_closed = False
4646
bint right_closed = False
47+
int index_growth_sign = 1
4748
ndarray[int64_t, ndim=1] start, end
4849
int64_t start_bound, end_bound
4950
Py_ssize_t i, j
@@ -58,6 +59,9 @@ def calculate_variable_window_bounds(
5859
if closed in ['left', 'both']:
5960
left_closed = True
6061

62+
if index[num_values - 1] < index[0]:
63+
index_growth_sign = -1
64+
6165
start = np.empty(num_values, dtype='int64')
6266
start.fill(-1)
6367
end = np.empty(num_values, dtype='int64')
@@ -78,7 +82,7 @@ def calculate_variable_window_bounds(
7882
# end is end of slice interval (not including)
7983
for i in range(1, num_values):
8084
end_bound = index[i]
81-
start_bound = index[i] - window_size
85+
start_bound = index[i] - index_growth_sign * window_size
8286

8387
# left endpoint is closed
8488
if left_closed:
@@ -88,13 +92,13 @@ def calculate_variable_window_bounds(
8892
# within the constraint
8993
start[i] = i
9094
for j in range(start[i - 1], i):
91-
if index[j] > start_bound:
95+
if (index[j] - start_bound) * index_growth_sign > 0:
9296
start[i] = j
9397
break
9498

9599
# end bound is previous end
96100
# or current index
97-
if index[end[i - 1]] <= end_bound:
101+
if (index[end[i - 1]] - end_bound) * index_growth_sign <= 0:
98102
end[i] = i + 1
99103
else:
100104
end[i] = end[i - 1]

pandas/tests/window/test_timeseries_window.py

+16-11
Original file line numberDiff line numberDiff line change
@@ -709,20 +709,25 @@ def test_rolling_cov_offset(self):
709709
tm.assert_series_equal(result, expected2)
710710

711711
def test_rolling_on_decreasing_index(self):
712-
# GH-19248
712+
# GH-19248, GH-32385
713713
index = [
714-
Timestamp("20190101 09:00:00"),
715-
Timestamp("20190101 09:00:02"),
716-
Timestamp("20190101 09:00:03"),
717-
Timestamp("20190101 09:00:05"),
718-
Timestamp("20190101 09:00:06"),
714+
Timestamp("20190101 09:00:30"),
715+
Timestamp("20190101 09:00:27"),
716+
Timestamp("20190101 09:00:20"),
717+
Timestamp("20190101 09:00:18"),
718+
Timestamp("20190101 09:00:10"),
719719
]
720720

721-
df = DataFrame({"column": [3, 4, 4, 2, 1]}, index=reversed(index))
722-
result = df.rolling("2s").min()
723-
expected = DataFrame(
724-
{"column": [3.0, 3.0, 3.0, 2.0, 1.0]}, index=reversed(index)
725-
)
721+
df = DataFrame({"column": [3, 4, 4, 5, 6]}, index=index)
722+
result = df.rolling("5s").min()
723+
expected = DataFrame({"column": [3.0, 3.0, 4.0, 4.0, 6.0]}, index=index)
724+
tm.assert_frame_equal(result, expected)
725+
726+
def test_rolling_on_empty(self):
727+
# GH-32385
728+
df = DataFrame({"column": []}, index=[])
729+
result = df.rolling("5s").min()
730+
expected = DataFrame({"column": []}, index=[])
726731
tm.assert_frame_equal(result, expected)
727732

728733
def test_rolling_on_multi_index_level(self):

0 commit comments

Comments
 (0)