Skip to content

Commit 9088f5e

Browse files
ihsansecerjreback
authored andcommitted
BUG: Fix rolling median and quantile with closed='left' and closed='neither' (#26005) (#26910)
1 parent dfcd2b2 commit 9088f5e

File tree

3 files changed

+46
-24
lines changed

3 files changed

+46
-24
lines changed

doc/source/whatsnew/v0.25.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,7 @@ Groupby/Resample/Rolling
747747
- Bug in :meth:`pandas.core.frame.DataFrame.groupby` where passing a :class:`pandas.core.groupby.grouper.Grouper` would return incorrect groups when using the ``.groups`` accessor (:issue:`26326`)
748748
- Bug in :meth:`pandas.core.groupby.GroupBy.agg` where incorrect results are returned for uint64 columns. (:issue:`26310`)
749749
- Bug in :meth:`pandas.core.window.Rolling.median` and :meth:`pandas.core.window.Rolling.quantile` where MemoryError is raised with empty window (:issue:`26005`)
750+
- Bug in :meth:`pandas.core.window.Rolling.median` and :meth:`pandas.core.window.Rolling.quantile` where incorrect results are returned with ``closed='left'`` and ``closed='neither'`` (:issue:`26005`)
750751

751752
Reshaping
752753
^^^^^^^^^

pandas/_libs/window.pyx

+26-24
Original file line numberDiff line numberDiff line change
@@ -1116,21 +1116,15 @@ def roll_median_c(ndarray[float64_t] values, int64_t win, int64_t minp,
11161116
if i == 0:
11171117

11181118
# setup
1119-
val = values[i]
1120-
if notnan(val):
1121-
nobs += 1
1122-
err = skiplist_insert(sl, val) != 1
1123-
if err:
1124-
break
1125-
1126-
else:
1127-
1128-
# calculate deletes
1129-
for j in range(start[i - 1], s):
1119+
for j in range(s, e):
11301120
val = values[j]
11311121
if notnan(val):
1132-
skiplist_remove(sl, val)
1133-
nobs -= 1
1122+
nobs += 1
1123+
err = skiplist_insert(sl, val) != 1
1124+
if err:
1125+
break
1126+
1127+
else:
11341128

11351129
# calculate adds
11361130
for j in range(end[i - 1], e):
@@ -1141,6 +1135,13 @@ def roll_median_c(ndarray[float64_t] values, int64_t win, int64_t minp,
11411135
if err:
11421136
break
11431137

1138+
# calculate deletes
1139+
for j in range(start[i - 1], s):
1140+
val = values[j]
1141+
if notnan(val):
1142+
skiplist_remove(sl, val)
1143+
nobs -= 1
1144+
11441145
if nobs >= minp:
11451146
midpoint = <int>(nobs / 2)
11461147
if nobs % 2:
@@ -1507,19 +1508,13 @@ def roll_quantile(ndarray[float64_t, cast=True] values, int64_t win,
15071508
if i == 0:
15081509

15091510
# setup
1510-
val = values[i]
1511-
if notnan(val):
1512-
nobs += 1
1513-
skiplist_insert(skiplist, val)
1514-
1515-
else:
1516-
1517-
# calculate deletes
1518-
for j in range(start[i - 1], s):
1511+
for j in range(s, e):
15191512
val = values[j]
15201513
if notnan(val):
1521-
skiplist_remove(skiplist, val)
1522-
nobs -= 1
1514+
nobs += 1
1515+
skiplist_insert(skiplist, val)
1516+
1517+
else:
15231518

15241519
# calculate adds
15251520
for j in range(end[i - 1], e):
@@ -1528,6 +1523,13 @@ def roll_quantile(ndarray[float64_t, cast=True] values, int64_t win,
15281523
nobs += 1
15291524
skiplist_insert(skiplist, val)
15301525

1526+
# calculate deletes
1527+
for j in range(start[i - 1], s):
1528+
val = values[j]
1529+
if notnan(val):
1530+
skiplist_remove(skiplist, val)
1531+
nobs -= 1
1532+
15311533
if nobs >= minp:
15321534
if nobs == 1:
15331535
# Single value in skip list

pandas/tests/test_window.py

+19
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,25 @@ def test_closed_min_max_minp(self, func, closed, expected):
594594
expected = pd.Series(expected, index=ser.index)
595595
tm.assert_series_equal(result, expected)
596596

597+
@pytest.mark.parametrize("closed,expected", [
598+
('right', [0, 0.5, 1, 2, 3, 4, 5, 6, 7, 8]),
599+
('both', [0, 0.5, 1, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5]),
600+
('neither', [np.nan, 0, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5]),
601+
('left', [np.nan, 0, 0.5, 1, 2, 3, 4, 5, 6, 7])
602+
])
603+
def test_closed_median_quantile(self, closed, expected):
604+
# GH 26005
605+
ser = pd.Series(data=np.arange(10),
606+
index=pd.date_range('2000', periods=10))
607+
roll = ser.rolling('3D', closed=closed)
608+
expected = pd.Series(expected, index=ser.index)
609+
610+
result = roll.median()
611+
tm.assert_series_equal(result, expected)
612+
613+
result = roll.quantile(0.5)
614+
tm.assert_series_equal(result, expected)
615+
597616
@pytest.mark.parametrize('roller', ['1s', 1])
598617
def tests_empty_df_rolling(self, roller):
599618
# GH 15819 Verifies that datetime and integer rolling windows can be

0 commit comments

Comments
 (0)