From 910bef487e4de1a8576bc0cc06bf0c07725a1638 Mon Sep 17 00:00:00 2001 From: Skipper Seabold Date: Thu, 6 Feb 2014 14:36:30 -0500 Subject: [PATCH 1/3] ENH: rolling_/expanding_apply take args, kwargs for func --- pandas/algos.pyx | 7 +++-- pandas/stats/moments.py | 49 +++++++++++++++++++----------- pandas/stats/tests/test_moments.py | 15 +++++++++ 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/pandas/algos.pyx b/pandas/algos.pyx index 64df9df0205ff..0be238117fe4e 100644 --- a/pandas/algos.pyx +++ b/pandas/algos.pyx @@ -1627,7 +1627,7 @@ def roll_quantile(ndarray[float64_t, cast=True] input, int win, return output def roll_generic(ndarray[float64_t, cast=True] input, int win, - int minp, object func): + int minp, object func, object args, object kwargs): cdef ndarray[double_t] output, counts, bufarr cdef Py_ssize_t i, n cdef float64_t *buf @@ -1652,7 +1652,8 @@ def roll_generic(ndarray[float64_t, cast=True] input, int win, n = len(input) for i from 0 <= i < int_min(win, n): if counts[i] >= minp: - output[i] = func(input[int_max(i - win + 1, 0) : i + 1]) + output[i] = func(input[int_max(i - win + 1, 0) : i + 1], *args, + **kwargs) else: output[i] = NaN @@ -1660,7 +1661,7 @@ def roll_generic(ndarray[float64_t, cast=True] input, int win, buf = buf + 1 bufarr.data = buf if counts[i] >= minp: - output[i] = func(bufarr) + output[i] = func(bufarr, *args, **kwargs) else: output[i] = NaN diff --git a/pandas/stats/moments.py b/pandas/stats/moments.py index 904e6018dba1f..464af681bafdc 100644 --- a/pandas/stats/moments.py +++ b/pandas/stats/moments.py @@ -141,7 +141,7 @@ def rolling_count(arg, window, freq=None, center=False, time_rule=None): center : boolean, default False Whether the label should correspond with center of window time_rule : Legacy alias for freq - + Returns ------- rolling_count : type of caller @@ -255,8 +255,8 @@ def rolling_corr_pairwise(df, window, min_periods=None): return Panel.from_dict(all_results).swapaxes('items', 'major') -def _rolling_moment(arg, window, func, minp, axis=0, freq=None, - center=False, time_rule=None, **kwargs): +def _rolling_moment(arg, window, func, minp, axis=0, freq=None, center=False, + time_rule=None, args=(), kwargs={}, **kwds): """ Rolling statistical measure using supplied function. Designed to be used with passed-in Cython array-based functions. @@ -274,13 +274,18 @@ def _rolling_moment(arg, window, func, minp, axis=0, freq=None, center : boolean, default False Whether the label should correspond with center of window time_rule : Legacy alias for freq - + args : tuple + Passed on to func + kwargs : dict + Passed on to func + Returns ------- y : type of input """ arg = _conv_timerule(arg, freq, time_rule) - calc = lambda x: func(x, window, minp=minp, **kwargs) + calc = lambda x: func(x, window, minp=minp, args=args, kwargs=kwargs, + **kwds) return_hook, values = _process_data_structure(arg) # actually calculate the moment. Faster way to do this? if values.ndim > 1: @@ -551,7 +556,7 @@ def rolling_quantile(arg, window, quantile, min_periods=None, freq=None, center : boolean, default False Whether the label should correspond with center of window time_rule : Legacy alias for freq - + Returns ------- y : type of input argument @@ -565,7 +570,7 @@ def call_cython(arg, window, minp): def rolling_apply(arg, window, func, min_periods=None, freq=None, - center=False, time_rule=None): + center=False, time_rule=None, args=(), kwargs={}): """Generic moving function application Parameters @@ -581,16 +586,21 @@ def rolling_apply(arg, window, func, min_periods=None, freq=None, center : boolean, default False Whether the label should correspond with center of window time_rule : Legacy alias for freq - + args : tuple + Passed on to func + kwargs : dict + Passed on to func + Returns ------- y : type of input argument """ - def call_cython(arg, window, minp): + def call_cython(arg, window, minp, args, kwargs): minp = _use_window(minp, window) - return algos.roll_generic(arg, window, minp, func) + return algos.roll_generic(arg, window, minp, func, args, kwargs) return _rolling_moment(arg, window, call_cython, min_periods, - freq=freq, center=center, time_rule=time_rule) + freq=freq, center=center, time_rule=time_rule, + args=args, kwargs=kwargs) def rolling_window(arg, window=None, win_type=None, min_periods=None, @@ -618,7 +628,7 @@ def rolling_window(arg, window=None, win_type=None, min_periods=None, If True computes weighted mean, else weighted sum time_rule : Legacy alias for freq axis : {0, 1}, default 0 - + Returns ------- y : type of input argument @@ -744,7 +754,7 @@ def expanding_count(arg, freq=None, center=False, time_rule=None): center : boolean, default False Whether the label should correspond with center of window time_rule : Legacy alias for freq - + Returns ------- expanding_count : type of caller @@ -768,7 +778,7 @@ def expanding_quantile(arg, quantile, min_periods=1, freq=None, center : boolean, default False Whether the label should correspond with center of window time_rule : Legacy alias for freq - + Returns ------- y : type of input argument @@ -818,7 +828,7 @@ def expanding_corr_pairwise(df, min_periods=1): def expanding_apply(arg, func, min_periods=1, freq=None, center=False, - time_rule=None): + time_rule=None, args=(), kwargs={}): """Generic expanding function application Parameters @@ -833,11 +843,16 @@ def expanding_apply(arg, func, min_periods=1, freq=None, center=False, center : boolean, default False Whether the label should correspond with center of window time_rule : Legacy alias for freq - + args : tuple + Passed on to func + kwargs : dict + Passed on to func + Returns ------- y : type of input argument """ window = len(arg) return rolling_apply(arg, window, func, min_periods=min_periods, freq=freq, - center=center, time_rule=time_rule) + center=center, time_rule=time_rule, args=args, + kwargs=kwargs) diff --git a/pandas/stats/tests/test_moments.py b/pandas/stats/tests/test_moments.py index 970adeace1e0f..50b1854f0e24b 100644 --- a/pandas/stats/tests/test_moments.py +++ b/pandas/stats/tests/test_moments.py @@ -694,6 +694,21 @@ def expanding_mean(x, min_periods=1, freq=None): freq=freq) self._check_expanding(expanding_mean, np.mean) + def test_expanding_apply_args_kwargs(self): + def mean_w_arg(x, const): + return np.mean(x) + const + + df = DataFrame(np.random.rand(20, 3)) + + expected = mom.expanding_apply(df, np.mean) + 20. + + assert_frame_equal(mom.expanding_apply(df, mean_w_arg, args=(20,)), + expected) + assert_frame_equal(mom.expanding_apply(df, mean_w_arg, + kwargs={'const' : 20}), + expected) + + def test_expanding_corr(self): A = self.series.dropna() B = (A + randn(len(A)))[:-5] From 64729511ead21de3916419fa1a1f825b9d30d051 Mon Sep 17 00:00:00 2001 From: Skipper Seabold Date: Thu, 6 Feb 2014 14:38:05 -0500 Subject: [PATCH 2/3] DOC: Add rolling_apply/expanding_apply ENH to release notes. --- doc/source/release.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/release.rst b/doc/source/release.rst index ae95c882fe356..a56f13a17d1ab 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -65,6 +65,7 @@ Improvements to existing features - pd.read_clipboard will, if 'sep' is unspecified, try to detect data copied from a spreadsheet and parse accordingly. (:issue:`6223`) +- pd.expanding_apply and pd.rolling_apply now take args and kwargs that are passed on to the func. .. _release.bug_fixes-0.14.0: From 1e6c8dcb45e6b2f5c95d98b33d1935d015ba4b94 Mon Sep 17 00:00:00 2001 From: Skipper Seabold Date: Thu, 6 Feb 2014 15:32:48 -0500 Subject: [PATCH 3/3] BUG: Use dummy args to accomodate most general case. --- pandas/stats/moments.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/stats/moments.py b/pandas/stats/moments.py index 464af681bafdc..ca4bbc3c8868a 100644 --- a/pandas/stats/moments.py +++ b/pandas/stats/moments.py @@ -514,7 +514,7 @@ def _rolling_func(func, desc, check_minp=_use_window): @wraps(func) def f(arg, window, min_periods=None, freq=None, center=False, time_rule=None, **kwargs): - def call_cython(arg, window, minp, **kwds): + def call_cython(arg, window, minp, args=(), kwargs={}, **kwds): minp = check_minp(minp, window) return func(arg, window, minp, **kwds) return _rolling_moment(arg, window, call_cython, min_periods, @@ -562,7 +562,7 @@ def rolling_quantile(arg, window, quantile, min_periods=None, freq=None, y : type of input argument """ - def call_cython(arg, window, minp): + def call_cython(arg, window, minp, args=(), kwargs={}): minp = _use_window(minp, window) return algos.roll_quantile(arg, window, minp, quantile) return _rolling_moment(arg, window, call_cython, min_periods, @@ -713,7 +713,7 @@ def f(arg, min_periods=1, freq=None, center=False, time_rule=None, **kwargs): window = len(arg) - def call_cython(arg, window, minp, **kwds): + def call_cython(arg, window, minp, args=(), kwargs={}, **kwds): minp = check_minp(minp, window) return func(arg, window, minp, **kwds) return _rolling_moment(arg, window, call_cython, min_periods,