diff --git a/doc/source/whatsnew/v0.20.1.txt b/doc/source/whatsnew/v0.20.1.txt index 504f8004bc8a6..ce843d56c5ae2 100644 --- a/doc/source/whatsnew/v0.20.1.txt +++ b/doc/source/whatsnew/v0.20.1.txt @@ -78,7 +78,7 @@ Reshaping Numeric ^^^^^^^ - +- Bug in ``.rolling.quantile()`` which incorrectly used different defaults than Series.quantile() and DataFrame.quantile() Other diff --git a/pandas/_libs/window.pyx b/pandas/_libs/window.pyx index 3bb8abe26c781..275ff26a7eb35 100644 --- a/pandas/_libs/window.pyx +++ b/pandas/_libs/window.pyx @@ -1348,6 +1348,7 @@ def roll_quantile(ndarray[float64_t, cast=True] input, int64_t win, bint is_variable ndarray[int64_t] start, end ndarray[double_t] output + double qlow, qhigh, vlow, vhigh if quantile < 0.0 or quantile > 1.0: raise ValueError("quantile value {0} not in [0, 1]".format(quantile)) @@ -1391,7 +1392,19 @@ def roll_quantile(ndarray[float64_t, cast=True] input, int64_t win, if nobs >= minp: idx = int(quantile * (nobs - 1)) - output[i] = skiplist.get(idx) + + # Exactly last point + if idx == nobs - 1: + output[i] = skiplist.get(idx) + + # Interpolated quantile + else: + qlow = ( idx) / ((nobs - 1)) + qhigh = ( (idx + 1)) / ((nobs - 1)) + vlow = skiplist.get(idx) + vhigh = skiplist.get(idx + 1) + output[i] = vlow + (vhigh - vlow) * \ + (quantile - qlow) / (qhigh - qlow) else: output[i] = NaN diff --git a/pandas/tests/test_window.py b/pandas/tests/test_window.py index 634cd5fe2586b..33f0ecd001436 100644 --- a/pandas/tests/test_window.py +++ b/pandas/tests/test_window.py @@ -1078,8 +1078,19 @@ def test_rolling_quantile(self): def scoreatpercentile(a, per): values = np.sort(a, axis=0) - idx = per / 1. * (values.shape[0] - 1) - return values[int(idx)] + idx = int(per / 1. * (values.shape[0] - 1)) + + if idx == values.shape[0] - 1: + retval = values[-1] + + else: + qlow = float(idx) / float(values.shape[0] - 1) + qhig = float(idx + 1) / float(values.shape[0] - 1) + vlow = values[idx] + vhig = values[idx + 1] + retval = vlow + (vhig - vlow) * (per - qlow) / (qhig - qlow) + + return retval for q in qs: @@ -1094,6 +1105,28 @@ def alt(x): self._check_moment_func(f, alt, name='quantile', quantile=q) + def test_rolling_quantile_np_percentile(self): + # #9413 + row = 10 + col = 5 + idx = pd.date_range(20100101, periods=row, freq='B') + df = pd.DataFrame(np.random.rand(row * col).reshape((row, -1)), + index=idx) + + df_quantile = df.quantile([0.25, 0.5, 0.75], axis=0) + np_percentile = np.percentile(df, [25, 50, 75], axis=0) + + tm.assert_almost_equal(df_quantile.values, np.array(np_percentile)) + + def test_rolling_quantile_series(self): + # #16211 + arr = np.arange(100) + s = pd.Series(arr) + q1 = s.quantile(0.1) + q2 = s.rolling(100).quantile(0.1).iloc[-1] + + tm.assert_almost_equal(q1, q2) + def test_rolling_quantile_param(self): ser = Series([0.0, .1, .5, .9, 1.0]) @@ -3514,7 +3547,7 @@ def test_ragged_quantile(self): result = df.rolling(window='2s', min_periods=1).quantile(0.5) expected = df.copy() - expected['B'] = [0.0, 1, 1.0, 3.0, 3.0] + expected['B'] = [0.0, 1, 1.5, 3.0, 3.5] tm.assert_frame_equal(result, expected) def test_ragged_std(self):