From 821da97126b53508f0df8c13972f7c0d28780a52 Mon Sep 17 00:00:00 2001 From: Wes McKinney Date: Fri, 7 Sep 2012 14:47:36 -0400 Subject: [PATCH] BUG: fix sqrt(negative number) issue in rolling_std close #1840 --- RELEASE.rst | 2 ++ pandas/stats/moments.py | 20 +++++++++++++++++--- pandas/stats/tests/test_moments.py | 16 ++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/RELEASE.rst b/RELEASE.rst index 2518669fe1a9d..428802f952dcb 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -98,6 +98,8 @@ pandas 0.8.2 right time zone (#1777) - Fix DST issues with generating anchored date ranges (#1778) - Fix issue calling sort on result of Series.unique (#1807) + - Fix numerical issue leading to square root of negative number in + rolling_std (#1840) pandas 0.8.1 ============ diff --git a/pandas/stats/moments.py b/pandas/stats/moments.py index 29df509832ea6..f96ce98eaba4a 100644 --- a/pandas/stats/moments.py +++ b/pandas/stats/moments.py @@ -309,7 +309,7 @@ def ewmstd(arg, com=None, span=None, min_periods=0, bias=False, time_rule=None): result = ewmvar(arg, com=com, span=span, time_rule=time_rule, min_periods=min_periods, bias=bias) - return np.sqrt(result) + return _zsqrt(result) ewmvol = ewmstd @@ -343,7 +343,21 @@ def ewmcorr(arg1, arg2, com=None, span=None, min_periods=0, mean = lambda x: ewma(x, com=com, span=span, min_periods=min_periods) var = lambda x: ewmvar(x, com=com, span=span, min_periods=min_periods, bias=True) - return (mean(X*Y) - mean(X)*mean(Y)) / np.sqrt(var(X) * var(Y)) + return (mean(X*Y) - mean(X)*mean(Y)) / _zsqrt(var(X) * var(Y)) + + +def _zsqrt(x): + result = np.sqrt(x) + mask = x < 0 + + if isinstance(x, DataFrame): + if mask.values.any(): + result[mask] = 0 + else: + if mask.any(): + result[mask] = 0 + + return result def _prep_binary(arg1, arg2): if not isinstance(arg2, type(arg1)): @@ -406,7 +420,7 @@ def call_cython(arg, window, minp, **kwds): rolling_mean = _rolling_func(lib.roll_mean, 'Moving mean') rolling_median = _rolling_func(lib.roll_median_cython, 'Moving median') -_ts_std = lambda *a, **kw: np.sqrt(lib.roll_var(*a, **kw)) +_ts_std = lambda *a, **kw: _zsqrt(lib.roll_var(*a, **kw)) rolling_std = _rolling_func(_ts_std, 'Unbiased moving standard deviation', check_minp=_require_min_periods(2)) rolling_var = _rolling_func(lib.roll_var, 'Unbiased moving variance', diff --git a/pandas/stats/tests/test_moments.py b/pandas/stats/tests/test_moments.py index 1d80cb167b4cd..c4f623c34166f 100644 --- a/pandas/stats/tests/test_moments.py +++ b/pandas/stats/tests/test_moments.py @@ -89,6 +89,22 @@ def test_rolling_std(self): self._check_moment_func(functools.partial(mom.rolling_std, ddof=0), lambda x: np.std(x, ddof=0)) + def test_rolling_std_neg_sqrt(self): + # unit test from Bottleneck + + # Test move_nanstd for neg sqrt. + + a = np.array([0.0011448196318903589, + 0.00028718669878572767, + 0.00028718669878572767, + 0.00028718669878572767, + 0.00028718669878572767]) + b = mom.rolling_std(a, window=3) + self.assert_(np.isfinite(b[2:]).all()) + + b = mom.ewmstd(a, span=3) + self.assert_(np.isfinite(b[2:]).all()) + def test_rolling_var(self): self._check_moment_func(mom.rolling_var, lambda x: np.var(x, ddof=1))