Skip to content

Commit eb9f154

Browse files
committed
API: add ddof to expanding/rolling_cov()
1 parent 83ff5b1 commit eb9f154

File tree

3 files changed

+49
-32
lines changed

3 files changed

+49
-32
lines changed

doc/source/v0.15.0.txt

+7
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ API changes
115115
:func:`expanding_cov`, :func:`expanding_corr`, :func:`expanding_corr_pairwise`, and :func:`expanding_apply`,
116116
as the results produced when ``center=True`` did not make much sense. (:issue:`7925`)
117117

118+
- Added optional ``ddof`` argument to :func:`expanding_cov` and :func:`rolling_cov`.
119+
The default value of ``1`` is backwards-compatible. (:issue:`8279`)
120+
121+
- Documented the ``ddof`` argument to :func:`expanding_var`, :func:`expanding_std`,
122+
:func:`rolling_var`, and :func:`rolling_std`. These functions' support of a
123+
``ddof`` argument (with a default value of ``1``) was previously undocumented. (:issue:`8064`)
124+
118125
- :func:`ewma`, :func:`ewmstd`, :func:`ewmvol`, :func:`ewmvar`, :func:`ewmcov`, and :func:`ewmcorr`
119126
now interpret ``min_periods`` in the same manner that the ``rolling_*`` and ``expanding_*`` functions do:
120127
a given result entry will be ``NaN`` if the (expanding, in this case) window does not contain

pandas/stats/moments.py

+35-29
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@
167167
elements, only complete pairwise observations will be used.
168168
"""
169169

170+
_ddof_kw = """ddof : int, default 1
171+
Delta Degrees of Freedom. The divisor used in calculations
172+
is ``N - ddof``, where ``N`` represents the number of elements.
173+
"""
174+
170175
_bias_kw = r"""bias : boolean, default False
171176
Use a standard estimation bias correction
172177
"""
@@ -216,10 +221,10 @@ def rolling_count(arg, window, freq=None, center=False, how=None):
216221

217222

218223
@Substitution("Unbiased moving covariance.", _binary_arg_flex,
219-
_roll_kw%'None'+_pairwise_kw, _flex_retval, _roll_notes)
224+
_roll_kw%'None'+_pairwise_kw+_ddof_kw, _flex_retval, _roll_notes)
220225
@Appender(_doc_template)
221226
def rolling_cov(arg1, arg2=None, window=None, min_periods=None, freq=None,
222-
center=False, pairwise=None, how=None):
227+
center=False, pairwise=None, how=None, ddof=1):
223228
if window is None and isinstance(arg2, (int, float)):
224229
window = arg2
225230
arg2 = arg1
@@ -233,7 +238,7 @@ def rolling_cov(arg1, arg2=None, window=None, min_periods=None, freq=None,
233238
def _get_cov(X, Y):
234239
mean = lambda x: rolling_mean(x, window, min_periods, center=center)
235240
count = rolling_count(X + Y, window, center=center)
236-
bias_adj = count / (count - 1)
241+
bias_adj = count / (count - ddof)
237242
return (mean(X * Y) - mean(X) * mean(Y)) * bias_adj
238243
rs = _flex_binary_moment(arg1, arg2, _get_cov, pairwise=bool(pairwise))
239244
return rs
@@ -620,14 +625,14 @@ def _use_window(minp, window):
620625
return minp
621626

622627

623-
def _rolling_func(func, desc, check_minp=_use_window, how=None):
628+
def _rolling_func(func, desc, check_minp=_use_window, how=None, additional_kw=''):
624629
if how is None:
625630
how_arg_str = 'None'
626631
else:
627632
how_arg_str = "'%s"%how
628633

629-
@Substitution(desc, _unary_arg, _roll_kw%how_arg_str, _type_of_input_retval,
630-
_roll_notes)
634+
@Substitution(desc, _unary_arg, _roll_kw%how_arg_str + additional_kw,
635+
_type_of_input_retval, _roll_notes)
631636
@Appender(_doc_template)
632637
@wraps(func)
633638
def f(arg, window, min_periods=None, freq=None, center=False, how=how,
@@ -648,10 +653,12 @@ def call_cython(arg, window, minp, args=(), kwargs={}, **kwds):
648653
how='median')
649654

650655
_ts_std = lambda *a, **kw: _zsqrt(algos.roll_var(*a, **kw))
651-
rolling_std = _rolling_func(_ts_std, 'Unbiased moving standard deviation.',
652-
check_minp=_require_min_periods(1))
653-
rolling_var = _rolling_func(algos.roll_var, 'Unbiased moving variance.',
654-
check_minp=_require_min_periods(1))
656+
rolling_std = _rolling_func(_ts_std, 'Moving standard deviation.',
657+
check_minp=_require_min_periods(1),
658+
additional_kw=_ddof_kw)
659+
rolling_var = _rolling_func(algos.roll_var, 'Moving variance.',
660+
check_minp=_require_min_periods(1),
661+
additional_kw=_ddof_kw)
655662
rolling_skew = _rolling_func(algos.roll_skew, 'Unbiased moving skewness.',
656663
check_minp=_require_min_periods(3))
657664
rolling_kurt = _rolling_func(algos.roll_kurt, 'Unbiased moving kurtosis.',
@@ -864,8 +871,9 @@ def _pop_args(win_type, arg_names, kwargs):
864871
return all_args
865872

866873

867-
def _expanding_func(func, desc, check_minp=_use_window):
868-
@Substitution(desc, _unary_arg, _expanding_kw, _type_of_input_retval, "")
874+
def _expanding_func(func, desc, check_minp=_use_window, additional_kw=''):
875+
@Substitution(desc, _unary_arg, _expanding_kw + additional_kw,
876+
_type_of_input_retval, "")
869877
@Appender(_doc_template)
870878
@wraps(func)
871879
def f(arg, min_periods=1, freq=None, **kwargs):
@@ -883,20 +891,18 @@ def call_cython(arg, window, minp, args=(), kwargs={}, **kwds):
883891
expanding_min = _expanding_func(algos.roll_min2, 'Expanding minimum.')
884892
expanding_sum = _expanding_func(algos.roll_sum, 'Expanding sum.')
885893
expanding_mean = _expanding_func(algos.roll_mean, 'Expanding mean.')
886-
expanding_median = _expanding_func(
887-
algos.roll_median_cython, 'Expanding median.')
888-
889-
expanding_std = _expanding_func(_ts_std,
890-
'Unbiased expanding standard deviation.',
891-
check_minp=_require_min_periods(1))
892-
expanding_var = _expanding_func(algos.roll_var, 'Unbiased expanding variance.',
893-
check_minp=_require_min_periods(1))
894-
expanding_skew = _expanding_func(
895-
algos.roll_skew, 'Unbiased expanding skewness.',
896-
check_minp=_require_min_periods(3))
897-
expanding_kurt = _expanding_func(
898-
algos.roll_kurt, 'Unbiased expanding kurtosis.',
899-
check_minp=_require_min_periods(4))
894+
expanding_median = _expanding_func(algos.roll_median_cython, 'Expanding median.')
895+
896+
expanding_std = _expanding_func(_ts_std, 'Expanding standard deviation.',
897+
check_minp=_require_min_periods(1),
898+
additional_kw=_ddof_kw)
899+
expanding_var = _expanding_func(algos.roll_var, 'Expanding variance.',
900+
check_minp=_require_min_periods(1),
901+
additional_kw=_ddof_kw)
902+
expanding_skew = _expanding_func(algos.roll_skew, 'Unbiased expanding skewness.',
903+
check_minp=_require_min_periods(3))
904+
expanding_kurt = _expanding_func(algos.roll_kurt, 'Unbiased expanding kurtosis.',
905+
check_minp=_require_min_periods(4))
900906

901907

902908
def expanding_count(arg, freq=None):
@@ -953,9 +959,9 @@ def expanding_quantile(arg, quantile, min_periods=1, freq=None):
953959

954960

955961
@Substitution("Unbiased expanding covariance.", _binary_arg_flex,
956-
_expanding_kw+_pairwise_kw, _flex_retval, "")
962+
_expanding_kw+_pairwise_kw+_ddof_kw, _flex_retval, "")
957963
@Appender(_doc_template)
958-
def expanding_cov(arg1, arg2=None, min_periods=1, freq=None, pairwise=None):
964+
def expanding_cov(arg1, arg2=None, min_periods=1, freq=None, pairwise=None, ddof=1):
959965
if arg2 is None:
960966
arg2 = arg1
961967
pairwise = True if pairwise is None else pairwise
@@ -966,7 +972,7 @@ def expanding_cov(arg1, arg2=None, min_periods=1, freq=None, pairwise=None):
966972
window = max((len(arg1) + len(arg2)), min_periods) if min_periods else (len(arg1) + len(arg2))
967973
return rolling_cov(arg1, arg2, window,
968974
min_periods=min_periods, freq=freq,
969-
pairwise=pairwise)
975+
pairwise=pairwise, ddof=ddof)
970976

971977

972978
@Substitution("Expanding sample correlation.", _binary_arg_flex,

pandas/stats/tests/test_moments.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from datetime import datetime
66
from numpy.random import randn
7+
from numpy.testing.decorators import slow
78
import numpy as np
89
from distutils.version import LooseVersion
910

@@ -813,6 +814,7 @@ def _non_null_values(x):
813814
mean_x_times_y = mean(x * y)
814815
assert_equal(cov_x_y, mean_x_times_y - (mean_x * mean_y))
815816

817+
@slow
816818
def test_ewm_consistency(self):
817819

818820
def _weights(s, com, adjust, ignore_na):
@@ -877,6 +879,7 @@ def _ewma(s, com, min_periods, adjust, ignore_na):
877879
cov_biased=lambda x, y: mom.ewmcov(x, y, com=com, min_periods=min_periods, adjust=adjust, ignore_na=ignore_na, bias=True),
878880
var_debiasing_factors=lambda x: _variance_debiasing_factors(x, com=com, adjust=adjust, ignore_na=ignore_na))
879881

882+
@slow
880883
def test_expanding_consistency(self):
881884
base_functions = [
882885
(mom.expanding_count, lambda v: Series(v).count(), None),
@@ -931,7 +934,7 @@ def test_expanding_consistency(self):
931934
cov_unbiased=lambda x, y: mom.expanding_cov(x, y, min_periods=min_periods),
932935
var_biased=lambda x: mom.expanding_var(x, min_periods=min_periods, ddof=0),
933936
std_biased=lambda x: mom.expanding_std(x, min_periods=min_periods, ddof=0),
934-
cov_biased=None,
937+
cov_biased=lambda x, y: mom.expanding_cov(x, y, min_periods=min_periods, ddof=0),
935938
var_debiasing_factors=lambda x: mom.expanding_count(x) / (mom.expanding_count(x) - 1.).replace(0., np.nan)
936939
)
937940

@@ -967,6 +970,7 @@ def test_expanding_consistency(self):
967970
expected.iloc[:, i, j] = expanding_f(x.iloc[:, i], x.iloc[:, j], min_periods=min_periods)
968971
assert_panel_equal(expanding_f_result, expected)
969972

973+
@slow
970974
def test_rolling_consistency(self):
971975
base_functions = [
972976
(mom.rolling_count, lambda v: Series(v).count(), None),
@@ -979,7 +983,7 @@ def test_rolling_consistency(self):
979983
(mom.rolling_corr, lambda v: Series(v).corr(Series(v)), None),
980984
(mom.rolling_var, lambda v: Series(v).var(), 1),
981985
#(mom.rolling_skew, lambda v: Series(v).skew(), 3), # restore once GH 8086 is fixed
982-
# (mom.rolling_kurt, lambda v: Series(v).kurt(), 4), # restore once GH 8086 is fixed
986+
#(mom.rolling_kurt, lambda v: Series(v).kurt(), 4), # restore once GH 8086 is fixed
983987
#(lambda x, window, min_periods, center: mom.rolling_quantile(x, window, 0.3, min_periods=min_periods, center=center),
984988
# lambda v: Series(v).quantile(0.3), None), # restore once GH 8084 is fixed
985989
(mom.rolling_median, lambda v: Series(v).median(), None),
@@ -1026,7 +1030,7 @@ def test_rolling_consistency(self):
10261030
cov_unbiased=lambda x, y: mom.rolling_cov(x, y, window=window, min_periods=min_periods, center=center),
10271031
var_biased=lambda x: mom.rolling_var(x, window=window, min_periods=min_periods, center=center, ddof=0),
10281032
std_biased=lambda x: mom.rolling_std(x, window=window, min_periods=min_periods, center=center, ddof=0),
1029-
cov_biased=None,
1033+
cov_biased=lambda x, y: mom.rolling_cov(x, y, window=window, min_periods=min_periods, center=center, ddof=0),
10301034
var_debiasing_factors=lambda x: mom.rolling_count(x, window=window, center=center).divide(
10311035
(mom.rolling_count(x, window=window, center=center) - 1.).replace(0., np.nan)),
10321036
)

0 commit comments

Comments
 (0)