diff --git a/doc/source/whatsnew/v0.18.2.txt b/doc/source/whatsnew/v0.18.2.txt index 61461be87801e..abf6ee2953f8b 100644 --- a/doc/source/whatsnew/v0.18.2.txt +++ b/doc/source/whatsnew/v0.18.2.txt @@ -46,7 +46,8 @@ API changes - Non-convertible dates in an excel date column will be returned without conversion and the column will be ``object`` dtype, rather than raising an exception (:issue:`10001`) - +- Compatibility with NumPy array methods has been expanded to timestamps (:issue: `12811`) +- An ``UnsupportedFunctionCall`` error is now raised if groupby or resample functions like ``mean`` are called via NumPy (:issue: `12811`) .. _whatsnew_0182.api.tolist: diff --git a/pandas/compat/numpy/function.py b/pandas/compat/numpy/function.py index 069cb3638fe75..274761f5d0b9c 100644 --- a/pandas/compat/numpy/function.py +++ b/pandas/compat/numpy/function.py @@ -21,7 +21,7 @@ from numpy import ndarray from pandas.util.validators import (validate_args, validate_kwargs, validate_args_and_kwargs) -from pandas.core.common import is_integer +from pandas.core.common import is_integer, UnsupportedFunctionCall from pandas.compat import OrderedDict @@ -245,3 +245,77 @@ def validate_transpose_for_generic(inst, kwargs): msg += " for {klass} instances".format(klass=klass) raise ValueError(msg) + + +def validate_window_func(name, args, kwargs): + numpy_args = ('axis', 'dtype', 'out') + msg = ("numpy operations are not " + "valid with window objects. " + "Use .{func}() directly instead ".format(func=name)) + + if len(args) > 0: + raise UnsupportedFunctionCall(msg) + + for arg in numpy_args: + if arg in kwargs: + raise UnsupportedFunctionCall(msg) + + +def validate_rolling_func(name, args, kwargs): + numpy_args = ('axis', 'dtype', 'out') + msg = ("numpy operations are not " + "valid with window objects. " + "Use .rolling(...).{func}() instead ".format(func=name)) + + if len(args) > 0: + raise UnsupportedFunctionCall(msg) + + for arg in numpy_args: + if arg in kwargs: + raise UnsupportedFunctionCall(msg) + + +def validate_expanding_func(name, args, kwargs): + numpy_args = ('axis', 'dtype', 'out') + msg = ("numpy operations are not " + "valid with window objects. " + "Use .expanding(...).{func}() instead ".format(func=name)) + + if len(args) > 0: + raise UnsupportedFunctionCall(msg) + + for arg in numpy_args: + if arg in kwargs: + raise UnsupportedFunctionCall(msg) + + +def validate_groupby_func(name, args, kwargs): + """ + 'args' and 'kwargs' should be empty because all of + their necessary parameters are explicitly listed in + the function signature + """ + if len(args) + len(kwargs) > 0: + raise UnsupportedFunctionCall(( + "numpy operations are not valid " + "with groupby. Use .groupby(...)." + "{func}() instead".format(func=name))) + +RESAMPLER_NUMPY_OPS = ('min', 'max', 'sum', 'prod', + 'mean', 'std', 'var') + + +def validate_resampler_func(method, args, kwargs): + """ + 'args' and 'kwargs' should be empty because all of + their necessary parameters are explicitly listed in + the function signature + """ + if len(args) + len(kwargs) > 0: + if method in RESAMPLER_NUMPY_OPS: + raise UnsupportedFunctionCall(( + "numpy operations are not valid " + "with resample. Use .resample(...)." + "{func}() instead".format(func=method))) + else: + raise TypeError("too many arguments passed in") diff --git a/pandas/core/common.py b/pandas/core/common.py index c64cfa77b9e62..64bfbdde0c5c3 100644 --- a/pandas/core/common.py +++ b/pandas/core/common.py @@ -41,6 +41,10 @@ class AmbiguousIndexError(PandasError, KeyError): pass +class UnsupportedFunctionCall(ValueError): + pass + + class AbstractMethodError(NotImplementedError): """Raise this error instead of NotImplementedError for abstract methods while keeping compatibility with Python 2 and Python 3. diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 6c80ab9d87e33..99599d2b04a45 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -5299,7 +5299,7 @@ def _make_stat_function(cls, name, name1, name2, axis_descr, desc, f): @Appender(_num_doc) def stat_func(self, axis=None, skipna=None, level=None, numeric_only=None, **kwargs): - nv.validate_stat_func(tuple(), kwargs) + nv.validate_stat_func(tuple(), kwargs, fname=name) if skipna is None: skipna = True if axis is None: @@ -5319,7 +5319,7 @@ def _make_stat_function_ddof(cls, name, name1, name2, axis_descr, desc, f): @Appender(_num_ddof_doc) def stat_func(self, axis=None, skipna=None, level=None, ddof=1, numeric_only=None, **kwargs): - nv.validate_stat_ddof_func(tuple(), kwargs) + nv.validate_stat_ddof_func(tuple(), kwargs, fname=name) if skipna is None: skipna = True if axis is None: @@ -5340,7 +5340,7 @@ def _make_cum_function(cls, name, name1, name2, axis_descr, desc, accum_func, @Appender("Return cumulative {0} over requested axis.".format(name) + _cnum_doc) def cum_func(self, axis=None, dtype=None, out=None, skipna=True, **kwargs): - nv.validate_cum_func(tuple(), kwargs) + nv.validate_cum_func(tuple(), kwargs, fname=name) if axis is None: axis = self._stat_axis_number else: @@ -5374,7 +5374,7 @@ def _make_logical_function(cls, name, name1, name2, axis_descr, desc, f): @Appender(_bool_doc) def logical_func(self, axis=None, bool_only=None, skipna=None, level=None, **kwargs): - nv.validate_logical_func(tuple(), kwargs) + nv.validate_logical_func(tuple(), kwargs, fname=name) if skipna is None: skipna = True if axis is None: diff --git a/pandas/core/groupby.py b/pandas/core/groupby.py index 424859da82877..2346be5c854f5 100644 --- a/pandas/core/groupby.py +++ b/pandas/core/groupby.py @@ -11,6 +11,7 @@ callable, map ) from pandas import compat +from pandas.compat.numpy import function as nv from pandas.compat.numpy import _np_version_under1p8 from pandas.core.base import (PandasObject, SelectionMixin, GroupByError, DataError, SpecificationError) @@ -954,12 +955,13 @@ def count(self): @Substitution(name='groupby') @Appender(_doc_template) - def mean(self): + def mean(self, *args, **kwargs): """ Compute mean of groups, excluding missing values For multiple groupings, the result index will be a MultiIndex """ + nv.validate_groupby_func('mean', args, kwargs) try: return self._cython_agg_general('mean') except GroupByError: @@ -993,7 +995,7 @@ def f(x): @Substitution(name='groupby') @Appender(_doc_template) - def std(self, ddof=1): + def std(self, ddof=1, *args, **kwargs): """ Compute standard deviation of groups, excluding missing values @@ -1005,12 +1007,13 @@ def std(self, ddof=1): degrees of freedom """ - # todo, implement at cython level? + # TODO: implement at Cython level? + nv.validate_groupby_func('std', args, kwargs) return np.sqrt(self.var(ddof=ddof)) @Substitution(name='groupby') @Appender(_doc_template) - def var(self, ddof=1): + def var(self, ddof=1, *args, **kwargs): """ Compute variance of groups, excluding missing values @@ -1021,7 +1024,7 @@ def var(self, ddof=1): ddof : integer, default 1 degrees of freedom """ - + nv.validate_groupby_func('var', args, kwargs) if ddof == 1: return self._cython_agg_general('var') else: @@ -1317,8 +1320,9 @@ def cumcount(self, ascending=True): @Substitution(name='groupby') @Appender(_doc_template) - def cumprod(self, axis=0): + def cumprod(self, axis=0, *args, **kwargs): """Cumulative product for each group""" + nv.validate_groupby_func('cumprod', args, kwargs) if axis != 0: return self.apply(lambda x: x.cumprod(axis=axis)) @@ -1326,8 +1330,9 @@ def cumprod(self, axis=0): @Substitution(name='groupby') @Appender(_doc_template) - def cumsum(self, axis=0): + def cumsum(self, axis=0, *args, **kwargs): """Cumulative sum for each group""" + nv.validate_groupby_func('cumsum', args, kwargs) if axis != 0: return self.apply(lambda x: x.cumprod(axis=axis)) diff --git a/pandas/core/window.py b/pandas/core/window.py index eb0d996436661..cd66d4e30c351 100644 --- a/pandas/core/window.py +++ b/pandas/core/window.py @@ -18,6 +18,7 @@ import pandas.core.common as com import pandas.algos as algos from pandas import compat +from pandas.compat.numpy import function as nv from pandas.util.decorators import Substitution, Appender from textwrap import dedent @@ -435,13 +436,15 @@ def aggregate(self, arg, *args, **kwargs): @Substitution(name='window') @Appender(_doc_template) @Appender(_shared_docs['sum']) - def sum(self, **kwargs): + def sum(self, *args, **kwargs): + nv.validate_window_func('sum', args, kwargs) return self._apply_window(mean=False, **kwargs) @Substitution(name='window') @Appender(_doc_template) @Appender(_shared_docs['mean']) - def mean(self, **kwargs): + def mean(self, *args, **kwargs): + nv.validate_window_func('mean', args, kwargs) return self._apply_window(mean=True, **kwargs) @@ -620,7 +623,8 @@ def f(arg, window, min_periods): return self._apply(f, func, args=args, kwargs=kwargs, center=False) - def sum(self, **kwargs): + def sum(self, *args, **kwargs): + nv.validate_window_func('sum', args, kwargs) return self._apply('roll_sum', 'sum', **kwargs) _shared_docs['max'] = dedent(""" @@ -631,7 +635,8 @@ def sum(self, **kwargs): how : string, default 'max' (DEPRECATED) Method for down- or re-sampling""") - def max(self, how=None, **kwargs): + def max(self, how=None, *args, **kwargs): + nv.validate_window_func('max', args, kwargs) if self.freq is not None and how is None: how = 'max' return self._apply('roll_max', 'max', how=how, **kwargs) @@ -644,12 +649,14 @@ def max(self, how=None, **kwargs): how : string, default 'min' (DEPRECATED) Method for down- or re-sampling""") - def min(self, how=None, **kwargs): + def min(self, how=None, *args, **kwargs): + nv.validate_window_func('min', args, kwargs) if self.freq is not None and how is None: how = 'min' return self._apply('roll_min', 'min', how=how, **kwargs) - def mean(self, **kwargs): + def mean(self, *args, **kwargs): + nv.validate_window_func('mean', args, kwargs) return self._apply('roll_mean', 'mean', **kwargs) _shared_docs['median'] = dedent(""" @@ -674,7 +681,8 @@ def median(self, how=None, **kwargs): Delta Degrees of Freedom. The divisor used in calculations is ``N - ddof``, where ``N`` represents the number of elements.""") - def std(self, ddof=1, **kwargs): + def std(self, ddof=1, *args, **kwargs): + nv.validate_window_func('std', args, kwargs) window = self._get_window() def f(arg, *args, **kwargs): @@ -693,7 +701,8 @@ def f(arg, *args, **kwargs): Delta Degrees of Freedom. The divisor used in calculations is ``N - ddof``, where ``N`` represents the number of elements.""") - def var(self, ddof=1, **kwargs): + def var(self, ddof=1, *args, **kwargs): + nv.validate_window_func('var', args, kwargs) return self._apply('roll_var', 'var', check_minp=_require_min_periods(1), ddof=ddof, **kwargs) @@ -865,26 +874,30 @@ def apply(self, func, args=(), kwargs={}): @Substitution(name='rolling') @Appender(_doc_template) @Appender(_shared_docs['sum']) - def sum(self, **kwargs): - return super(Rolling, self).sum(**kwargs) + def sum(self, *args, **kwargs): + nv.validate_rolling_func('sum', args, kwargs) + return super(Rolling, self).sum(*args, **kwargs) @Substitution(name='rolling') @Appender(_doc_template) @Appender(_shared_docs['max']) - def max(self, **kwargs): - return super(Rolling, self).max(**kwargs) + def max(self, *args, **kwargs): + nv.validate_rolling_func('max', args, kwargs) + return super(Rolling, self).max(*args, **kwargs) @Substitution(name='rolling') @Appender(_doc_template) @Appender(_shared_docs['min']) - def min(self, **kwargs): - return super(Rolling, self).min(**kwargs) + def min(self, *args, **kwargs): + nv.validate_rolling_func('min', args, kwargs) + return super(Rolling, self).min(*args, **kwargs) @Substitution(name='rolling') @Appender(_doc_template) @Appender(_shared_docs['mean']) - def mean(self, **kwargs): - return super(Rolling, self).mean(**kwargs) + def mean(self, *args, **kwargs): + nv.validate_rolling_func('mean', args, kwargs) + return super(Rolling, self).mean(*args, **kwargs) @Substitution(name='rolling') @Appender(_doc_template) @@ -895,13 +908,15 @@ def median(self, **kwargs): @Substitution(name='rolling') @Appender(_doc_template) @Appender(_shared_docs['std']) - def std(self, ddof=1, **kwargs): + def std(self, ddof=1, *args, **kwargs): + nv.validate_rolling_func('std', args, kwargs) return super(Rolling, self).std(ddof=ddof, **kwargs) @Substitution(name='rolling') @Appender(_doc_template) @Appender(_shared_docs['var']) - def var(self, ddof=1, **kwargs): + def var(self, ddof=1, *args, **kwargs): + nv.validate_rolling_func('var', args, kwargs) return super(Rolling, self).var(ddof=ddof, **kwargs) @Substitution(name='rolling') @@ -1023,26 +1038,30 @@ def apply(self, func, args=(), kwargs={}): @Substitution(name='expanding') @Appender(_doc_template) @Appender(_shared_docs['sum']) - def sum(self, **kwargs): - return super(Expanding, self).sum(**kwargs) + def sum(self, *args, **kwargs): + nv.validate_expanding_func('sum', args, kwargs) + return super(Expanding, self).sum(*args, **kwargs) @Substitution(name='expanding') @Appender(_doc_template) @Appender(_shared_docs['max']) - def max(self, **kwargs): - return super(Expanding, self).max(**kwargs) + def max(self, *args, **kwargs): + nv.validate_expanding_func('max', args, kwargs) + return super(Expanding, self).max(*args, **kwargs) @Substitution(name='expanding') @Appender(_doc_template) @Appender(_shared_docs['min']) - def min(self, **kwargs): - return super(Expanding, self).min(**kwargs) + def min(self, *args, **kwargs): + nv.validate_expanding_func('min', args, kwargs) + return super(Expanding, self).min(*args, **kwargs) @Substitution(name='expanding') @Appender(_doc_template) @Appender(_shared_docs['mean']) - def mean(self, **kwargs): - return super(Expanding, self).mean(**kwargs) + def mean(self, *args, **kwargs): + nv.validate_expanding_func('mean', args, kwargs) + return super(Expanding, self).mean(*args, **kwargs) @Substitution(name='expanding') @Appender(_doc_template) @@ -1053,13 +1072,15 @@ def median(self, **kwargs): @Substitution(name='expanding') @Appender(_doc_template) @Appender(_shared_docs['std']) - def std(self, ddof=1, **kwargs): + def std(self, ddof=1, *args, **kwargs): + nv.validate_expanding_func('std', args, kwargs) return super(Expanding, self).std(ddof=ddof, **kwargs) @Substitution(name='expanding') @Appender(_doc_template) @Appender(_shared_docs['var']) - def var(self, ddof=1, **kwargs): + def var(self, ddof=1, *args, **kwargs): + nv.validate_expanding_func('var', args, kwargs) return super(Expanding, self).var(ddof=ddof, **kwargs) @Substitution(name='expanding') @@ -1273,15 +1294,17 @@ def func(arg): @Substitution(name='ewm') @Appender(_doc_template) - def mean(self, **kwargs): + def mean(self, *args, **kwargs): """exponential weighted moving average""" + nv.validate_window_func('mean', args, kwargs) return self._apply('ewma', **kwargs) @Substitution(name='ewm') @Appender(_doc_template) @Appender(_bias_template) - def std(self, bias=False, **kwargs): + def std(self, bias=False, *args, **kwargs): """exponential weighted moving stddev""" + nv.validate_window_func('std', args, kwargs) return _zsqrt(self.var(bias=bias, **kwargs)) vol = std @@ -1289,8 +1312,9 @@ def std(self, bias=False, **kwargs): @Substitution(name='ewm') @Appender(_doc_template) @Appender(_bias_template) - def var(self, bias=False, **kwargs): + def var(self, bias=False, *args, **kwargs): """exponential weighted moving variance""" + nv.validate_window_func('var', args, kwargs) def f(arg): return algos.ewmcov(arg, arg, self.com, int(self.adjust), diff --git a/pandas/tests/test_groupby.py b/pandas/tests/test_groupby.py index 571b0fa1ee78f..74048536bd1f3 100644 --- a/pandas/tests/test_groupby.py +++ b/pandas/tests/test_groupby.py @@ -8,6 +8,7 @@ from pandas import date_range, bdate_range, Timestamp from pandas.core.index import Index, MultiIndex, CategoricalIndex from pandas.core.api import Categorical, DataFrame +from pandas.core.common import UnsupportedFunctionCall from pandas.core.groupby import (SpecificationError, DataError, _nargsort, _lexsort_indexer) from pandas.core.series import Series @@ -6393,6 +6394,19 @@ def test_transform_with_non_scalar_group(self): (axis=1, level=1).transform, lambda z: z.div(z.sum(axis=1), axis=0)) + def test_numpy_compat(self): + # see gh-12811 + df = pd.DataFrame({'A': [1, 2, 1], 'B': [1, 2, 3]}) + g = df.groupby('A') + + msg = "numpy operations are not valid with groupby" + + for func in ('mean', 'var', 'std', 'cumprod', 'cumsum'): + tm.assertRaisesRegexp(UnsupportedFunctionCall, msg, + getattr(g, func), 1, 2, 3) + tm.assertRaisesRegexp(UnsupportedFunctionCall, msg, + getattr(g, func), foo=1) + def assert_fp_equal(a, b): assert (np.abs(a - b) < 1e-12).all() diff --git a/pandas/tests/test_window.py b/pandas/tests/test_window.py index a043e92bd2c76..8d9a55bade30d 100644 --- a/pandas/tests/test_window.py +++ b/pandas/tests/test_window.py @@ -20,6 +20,7 @@ import pandas.stats.moments as mom import pandas.core.window as rwindow from pandas.core.base import SpecificationError +from pandas.core.common import UnsupportedFunctionCall import pandas.util.testing as tm from pandas.compat import range, zip, PY3 @@ -296,6 +297,18 @@ def test_constructor(self): with self.assertRaises(ValueError): c(win_type=wt, window=2) + def test_numpy_compat(self): + # see gh-12811 + w = rwindow.Window(Series([2, 4, 6]), window=[0, 2]) + + msg = "numpy operations are not valid with window objects" + + for func in ('sum', 'mean'): + tm.assertRaisesRegexp(UnsupportedFunctionCall, msg, + getattr(w, func), 1, 2, 3) + tm.assertRaisesRegexp(UnsupportedFunctionCall, msg, + getattr(w, func), dtype=np.float64) + class TestRolling(Base): @@ -323,6 +336,18 @@ def test_constructor(self): with self.assertRaises(ValueError): c(window=2, min_periods=1, center=w) + def test_numpy_compat(self): + # see gh-12811 + r = rwindow.Rolling(Series([2, 4, 6]), window=2) + + msg = "numpy operations are not valid with window objects" + + for func in ('std', 'mean', 'sum', 'max', 'min', 'var'): + tm.assertRaisesRegexp(UnsupportedFunctionCall, msg, + getattr(r, func), 1, 2, 3) + tm.assertRaisesRegexp(UnsupportedFunctionCall, msg, + getattr(r, func), dtype=np.float64) + class TestExpanding(Base): @@ -347,6 +372,74 @@ def test_constructor(self): with self.assertRaises(ValueError): c(min_periods=1, center=w) + def test_numpy_compat(self): + # see gh-12811 + e = rwindow.Expanding(Series([2, 4, 6]), window=2) + + msg = "numpy operations are not valid with window objects" + + for func in ('std', 'mean', 'sum', 'max', 'min', 'var'): + tm.assertRaisesRegexp(UnsupportedFunctionCall, msg, + getattr(e, func), 1, 2, 3) + tm.assertRaisesRegexp(UnsupportedFunctionCall, msg, + getattr(e, func), dtype=np.float64) + + +class TestEWM(Base): + + def setUp(self): + self._create_data() + + def test_constructor(self): + for o in [self.series, self.frame]: + c = o.ewm + + # valid + c(com=0.5) + c(span=1.5) + c(alpha=0.5) + c(halflife=0.75) + c(com=0.5, span=None) + c(alpha=0.5, com=None) + c(halflife=0.75, alpha=None) + + # not valid: mutually exclusive + with self.assertRaises(ValueError): + c(com=0.5, alpha=0.5) + with self.assertRaises(ValueError): + c(span=1.5, halflife=0.75) + with self.assertRaises(ValueError): + c(alpha=0.5, span=1.5) + + # not valid: com < 0 + with self.assertRaises(ValueError): + c(com=-0.5) + + # not valid: span < 1 + with self.assertRaises(ValueError): + c(span=0.5) + + # not valid: halflife <= 0 + with self.assertRaises(ValueError): + c(halflife=0) + + # not valid: alpha <= 0 or alpha > 1 + for alpha in (-0.5, 1.5): + with self.assertRaises(ValueError): + c(alpha=alpha) + + def test_numpy_compat(self): + # see gh-12811 + e = rwindow.EWM(Series([2, 4, 6]), alpha=0.5) + + msg = "numpy operations are not valid with window objects" + + for func in ('std', 'mean', 'var'): + tm.assertRaisesRegexp(UnsupportedFunctionCall, msg, + getattr(e, func), 1, 2, 3) + tm.assertRaisesRegexp(UnsupportedFunctionCall, msg, + getattr(e, func), dtype=np.float64) + class TestDeprecations(Base): """ test that we are catching deprecation warnings """ diff --git a/pandas/tseries/resample.py b/pandas/tseries/resample.py index bb7915e978c3e..ac30db35c0f85 100644 --- a/pandas/tseries/resample.py +++ b/pandas/tseries/resample.py @@ -16,7 +16,9 @@ from pandas.tseries.period import PeriodIndex, period_range import pandas.core.common as com import pandas.core.algorithms as algos + import pandas.compat as compat +from pandas.compat.numpy import function as nv from pandas.lib import Timestamp import pandas.lib as lib @@ -480,7 +482,7 @@ def asfreq(self): """ return self._upsample('asfreq') - def std(self, ddof=1): + def std(self, ddof=1, *args, **kwargs): """ Compute standard deviation of groups, excluding missing values @@ -489,9 +491,10 @@ def std(self, ddof=1): ddof : integer, default 1 degrees of freedom """ + nv.validate_resampler_func('std', args, kwargs) return self._downsample('std', ddof=ddof) - def var(self, ddof=1): + def var(self, ddof=1, *args, **kwargs): """ Compute variance of groups, excluding missing values @@ -500,6 +503,7 @@ def var(self, ddof=1): ddof : integer, default 1 degrees of freedom """ + nv.validate_resampler_func('var', args, kwargs) return self._downsample('var', ddof=ddof) Resampler._deprecated_valids += dir(Resampler) @@ -507,7 +511,8 @@ def var(self, ddof=1): for method in ['min', 'max', 'first', 'last', 'sum', 'mean', 'sem', 'median', 'prod', 'ohlc']: - def f(self, _method=method): + def f(self, _method=method, *args, **kwargs): + nv.validate_resampler_func(_method, args, kwargs) return self._downsample(_method) f.__doc__ = getattr(GroupBy, method).__doc__ setattr(Resampler, method, f) diff --git a/pandas/tseries/tests/test_resample.py b/pandas/tseries/tests/test_resample.py index dd5ab36d10a45..27b15a412ae37 100644 --- a/pandas/tseries/tests/test_resample.py +++ b/pandas/tseries/tests/test_resample.py @@ -13,7 +13,8 @@ notnull, Timestamp) from pandas.compat import range, lrange, zip, product, OrderedDict from pandas.core.base import SpecificationError -from pandas.core.common import ABCSeries, ABCDataFrame +from pandas.core.common import (ABCSeries, ABCDataFrame, + UnsupportedFunctionCall) from pandas.core.groupby import DataError from pandas.tseries.frequencies import MONTHS, DAYS from pandas.tseries.index import date_range @@ -746,6 +747,22 @@ def _ohlc(group): exc.args += ('how=%s' % arg,) raise + def test_numpy_compat(self): + # see gh-12811 + s = Series([1, 2, 3, 4, 5], index=date_range( + '20130101', periods=5, freq='s')) + r = s.resample('2s') + + msg = "numpy operations are not valid with resample" + + for func in ('min', 'max', 'sum', 'prod', + 'mean', 'var', 'std'): + tm.assertRaisesRegexp(UnsupportedFunctionCall, msg, + getattr(r, func), + func, 1, 2, 3) + tm.assertRaisesRegexp(UnsupportedFunctionCall, msg, + getattr(r, func), axis=1) + def test_resample_how_callables(self): # GH 7929 data = np.arange(5, dtype=np.int64) diff --git a/pandas/tseries/tests/test_tslib.py b/pandas/tseries/tests/test_tslib.py index 4543047a8a72a..79f9c60c9deb7 100644 --- a/pandas/tseries/tests/test_tslib.py +++ b/pandas/tseries/tests/test_tslib.py @@ -1290,6 +1290,34 @@ def test_shift_months(self): years=years, months=months) for x in s]) tm.assert_index_equal(actual, expected) + def test_round(self): + # see gh-12811 + stamp = Timestamp('2000-01-05 05:09:15.13') + + def _check_round(freq, expected): + result = stamp.round(freq=freq) + npResult = np.round(stamp, freq) + self.assertEqual(result, expected) + self.assertEqual(npResult, expected) + + for freq, expected in [ + ('D', Timestamp('2000-01-05 00:00:00')), + ('H', Timestamp('2000-01-05 05:00:00')), + ('S', Timestamp('2000-01-05 05:09:15')) + ]: + _check_round(freq, expected) + + msg = "the 'out' parameter is not supported" + tm.assertRaisesRegexp(ValueError, msg, np.round, + stamp, 'D', out=[]) + + # 'freq' is a required parameter, so we cannot + # assign a default should the user accidentally + # assign a 'decimals' input instead + msg = "Could not evaluate" + tm.assertRaisesRegexp(ValueError, msg, np.round, + stamp, 2) + class TestTimestampOps(tm.TestCase): def test_timestamp_and_datetime(self): diff --git a/pandas/tslib.pyx b/pandas/tslib.pyx index a240558025090..281a74d640292 100644 --- a/pandas/tslib.pyx +++ b/pandas/tslib.pyx @@ -330,7 +330,7 @@ class Timestamp(_Timestamp): result = result.tz_localize(self.tz) return result - def round(self, freq): + def round(self, freq, *args, **kwargs): """ Round the Timestamp to the specified resolution @@ -346,6 +346,8 @@ class Timestamp(_Timestamp): ------ ValueError if the freq cannot be converted """ + from pandas.compat.numpy.function import validate_round + validate_round(args, kwargs) return self._round(freq, np.round) def floor(self, freq):