Skip to content

Commit 753b7f2

Browse files
committed
BUG/ERR: better validation for .rolling parameters
closes #12669 Author: Jeff Reback <[email protected]> Closes #13002 from jreback/window and squashes the following commits: 985f324 [Jeff Reback] BUG: better validation for .rolling parameters
1 parent 0843911 commit 753b7f2

File tree

3 files changed

+144
-29
lines changed

3 files changed

+144
-29
lines changed

doc/source/whatsnew/v0.18.1.txt

+2
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ Bug Fixes
484484

485485

486486
- Bug in index coercion when falling back from ``RangeIndex`` construction (:issue:`12893`)
487+
- Better error message in window functions when invalid argument (e.g. a float window) is passed (:issue:`12669`)
487488

488489
- Bug in slicing subclassed ``DataFrame`` defined to return subclassed ``Series`` may return normal ``Series`` (:issue:`11559`)
489490

@@ -511,6 +512,7 @@ Bug Fixes
511512
- Bug in ``crosstab`` when ``margins=True`` and ``dropna=False`` which raised (:issue:`12642`)
512513

513514
- Bug in ``Series.name`` when ``name`` attribute can be a hashable type (:issue:`12610`)
515+
514516
- Bug in ``.describe()`` resets categorical columns information (:issue:`11558`)
515517
- Bug where ``loffset`` argument was not applied when calling ``resample().count()`` on a timeseries (:issue:`12725`)
516518
- ``pd.read_excel()`` now accepts column names associated with keyword argument ``names``(:issue:`12870`)

pandas/core/window.py

+58-29
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,20 @@ def __init__(self, obj, window=None, min_periods=None, freq=None,
5555
self.freq = freq
5656
self.center = center
5757
self.win_type = win_type
58-
self.axis = axis
58+
self.axis = obj._get_axis_number(axis) if axis is not None else None
59+
self.validate()
5960

6061
@property
6162
def _constructor(self):
6263
return Window
6364

65+
def validate(self):
66+
if self.center is not None and not com.is_bool(self.center):
67+
raise ValueError("center must be a boolean")
68+
if self.min_periods is not None and not \
69+
com.is_integer(self.min_periods):
70+
raise ValueError("min_periods must be an integer")
71+
6472
def _convert_freq(self, how=None):
6573
""" resample according to the how, return a new object """
6674

@@ -305,24 +313,62 @@ class Window(_Window):
305313
* ``slepian`` (needs width).
306314
"""
307315

308-
def _prep_window(self, **kwargs):
309-
""" provide validation for our window type, return the window """
310-
window = self._get_window()
316+
def validate(self):
317+
super(Window, self).validate()
311318

319+
window = self.window
312320
if isinstance(window, (list, tuple, np.ndarray)):
313-
return com._asarray_tuplesafe(window).astype(float)
321+
pass
314322
elif com.is_integer(window):
315323
try:
316324
import scipy.signal as sig
317325
except ImportError:
318326
raise ImportError('Please install scipy to generate window '
319327
'weight')
328+
329+
if not isinstance(self.win_type, compat.string_types):
330+
raise ValueError('Invalid win_type {0}'.format(self.win_type))
331+
if getattr(sig, self.win_type, None) is None:
332+
raise ValueError('Invalid win_type {0}'.format(self.win_type))
333+
else:
334+
raise ValueError('Invalid window {0}'.format(window))
335+
336+
def _prep_window(self, **kwargs):
337+
"""
338+
provide validation for our window type, return the window
339+
we have already been validated
340+
"""
341+
342+
window = self._get_window()
343+
if isinstance(window, (list, tuple, np.ndarray)):
344+
return com._asarray_tuplesafe(window).astype(float)
345+
elif com.is_integer(window):
346+
import scipy.signal as sig
347+
320348
# the below may pop from kwargs
349+
def _validate_win_type(win_type, kwargs):
350+
arg_map = {'kaiser': ['beta'],
351+
'gaussian': ['std'],
352+
'general_gaussian': ['power', 'width'],
353+
'slepian': ['width']}
354+
if win_type in arg_map:
355+
return tuple([win_type] + _pop_args(win_type,
356+
arg_map[win_type],
357+
kwargs))
358+
return win_type
359+
360+
def _pop_args(win_type, arg_names, kwargs):
361+
msg = '%s window requires %%s' % win_type
362+
all_args = []
363+
for n in arg_names:
364+
if n not in kwargs:
365+
raise ValueError(msg % n)
366+
all_args.append(kwargs.pop(n))
367+
return all_args
368+
321369
win_type = _validate_win_type(self.win_type, kwargs)
322370
return sig.get_window(win_type, window).astype(float)
323371

324-
raise ValueError('Invalid window %s' % str(window))
325-
326372
def _apply_window(self, mean=True, how=None, **kwargs):
327373
"""
328374
Applies a moving window of type ``window_type`` on the data.
@@ -791,6 +837,11 @@ class Rolling(_Rolling_and_Expanding):
791837
of :meth:`~pandas.Series.resample` (i.e. using the `mean`).
792838
"""
793839

840+
def validate(self):
841+
super(Rolling, self).validate()
842+
if not com.is_integer(self.window):
843+
raise ValueError("window must be an integer")
844+
794845
@Substitution(name='rolling')
795846
@Appender(SelectionMixin._see_also_template)
796847
@Appender(SelectionMixin._agg_doc)
@@ -1459,28 +1510,6 @@ def _prep_binary(arg1, arg2):
14591510
return X, Y
14601511

14611512

1462-
def _validate_win_type(win_type, kwargs):
1463-
# may pop from kwargs
1464-
arg_map = {'kaiser': ['beta'],
1465-
'gaussian': ['std'],
1466-
'general_gaussian': ['power', 'width'],
1467-
'slepian': ['width']}
1468-
if win_type in arg_map:
1469-
return tuple([win_type] + _pop_args(win_type, arg_map[win_type],
1470-
kwargs))
1471-
return win_type
1472-
1473-
1474-
def _pop_args(win_type, arg_names, kwargs):
1475-
msg = '%s window requires %%s' % win_type
1476-
all_args = []
1477-
for n in arg_names:
1478-
if n not in kwargs:
1479-
raise ValueError(msg % n)
1480-
all_args.append(kwargs.pop(n))
1481-
return all_args
1482-
1483-
14841513
# Top-level exports
14851514

14861515

pandas/tests/test_window.py

+84
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,90 @@ def test_how_compat(self):
264264
assert_series_equal(result, expected)
265265

266266

267+
class TestWindow(Base):
268+
269+
def setUp(self):
270+
self._create_data()
271+
272+
def test_constructor(self):
273+
# GH 12669
274+
tm._skip_if_no_scipy()
275+
276+
for o in [self.series, self.frame]:
277+
c = o.rolling
278+
279+
# valid
280+
c(win_type='boxcar', window=2, min_periods=1)
281+
c(win_type='boxcar', window=2, min_periods=1, center=True)
282+
c(win_type='boxcar', window=2, min_periods=1, center=False)
283+
284+
for wt in ['boxcar', 'triang', 'blackman', 'hamming', 'bartlett',
285+
'bohman', 'blackmanharris', 'nuttall', 'barthann']:
286+
c(win_type=wt, window=2)
287+
288+
# not valid
289+
for w in [2., 'foo', np.array([2])]:
290+
with self.assertRaises(ValueError):
291+
c(win_type='boxcar', window=2, min_periods=w)
292+
with self.assertRaises(ValueError):
293+
c(win_type='boxcar', window=2, min_periods=1, center=w)
294+
295+
for wt in ['foobar', 1]:
296+
with self.assertRaises(ValueError):
297+
c(win_type=wt, window=2)
298+
299+
300+
class TestRolling(Base):
301+
302+
def setUp(self):
303+
self._create_data()
304+
305+
def test_constructor(self):
306+
# GH 12669
307+
308+
for o in [self.series, self.frame]:
309+
c = o.rolling
310+
311+
# valid
312+
c(window=2)
313+
c(window=2, min_periods=1)
314+
c(window=2, min_periods=1, center=True)
315+
c(window=2, min_periods=1, center=False)
316+
317+
# not valid
318+
for w in [2., 'foo', np.array([2])]:
319+
with self.assertRaises(ValueError):
320+
c(window=w)
321+
with self.assertRaises(ValueError):
322+
c(window=2, min_periods=w)
323+
with self.assertRaises(ValueError):
324+
c(window=2, min_periods=1, center=w)
325+
326+
327+
class TestExpanding(Base):
328+
329+
def setUp(self):
330+
self._create_data()
331+
332+
def test_constructor(self):
333+
# GH 12669
334+
335+
for o in [self.series, self.frame]:
336+
c = o.expanding
337+
338+
# valid
339+
c(min_periods=1)
340+
c(min_periods=1, center=True)
341+
c(min_periods=1, center=False)
342+
343+
# not valid
344+
for w in [2., 'foo', np.array([2])]:
345+
with self.assertRaises(ValueError):
346+
c(min_periods=w)
347+
with self.assertRaises(ValueError):
348+
c(min_periods=1, center=w)
349+
350+
267351
class TestDeprecations(Base):
268352
""" test that we are catching deprecation warnings """
269353

0 commit comments

Comments
 (0)