diff --git a/doc/source/whatsnew/v0.20.0.txt b/doc/source/whatsnew/v0.20.0.txt index 23f2589adde89..0148a47068beb 100644 --- a/doc/source/whatsnew/v0.20.0.txt +++ b/doc/source/whatsnew/v0.20.0.txt @@ -247,6 +247,7 @@ Other API Changes - ``pd.read_csv()`` will now issue a ``ParserWarning`` whenever there are conflicting values provided by the ``dialect`` parameter and the user (:issue:`14898`) - ``pd.read_csv()`` will now raise a ``ValueError`` for the C engine if the quote character is larger than than one byte (:issue:`11592`) +- ``inplace`` arguments now require a boolean value, else a ``ValueError`` is thrown (:issue:`14189`) .. _whatsnew_0200.deprecations: diff --git a/pandas/computation/eval.py b/pandas/computation/eval.py index fffde4d9db867..a0a08e4a968cc 100644 --- a/pandas/computation/eval.py +++ b/pandas/computation/eval.py @@ -11,6 +11,7 @@ from pandas.computation.scope import _ensure_scope from pandas.compat import string_types from pandas.computation.engines import _engines +from pandas.util.validators import validate_bool_kwarg def _check_engine(engine): @@ -231,6 +232,7 @@ def eval(expr, parser='pandas', engine=None, truediv=True, pandas.DataFrame.query pandas.DataFrame.eval """ + inplace = validate_bool_kwarg(inplace, 'inplace') first_expr = True if isinstance(expr, string_types): _check_expression(expr) diff --git a/pandas/computation/tests/test_eval.py b/pandas/computation/tests/test_eval.py index ffa2cb0684b72..1b577a574350d 100644 --- a/pandas/computation/tests/test_eval.py +++ b/pandas/computation/tests/test_eval.py @@ -1970,6 +1970,15 @@ def test_negate_lt_eq_le(): for engine, parser in product(_engines, expr._parsers): yield check_negate_lt_eq_le, engine, parser +class TestValidate(tm.TestCase): + + def test_validate_bool_args(self): + invalid_values = [1, "True", [1,2,3], 5.0] + + for value in invalid_values: + with self.assertRaises(ValueError): + pd.eval("2+2", inplace=value) + if __name__ == '__main__': nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'], diff --git a/pandas/core/base.py b/pandas/core/base.py index 49e43a60403ca..77272f7721b32 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -8,6 +8,7 @@ from pandas.types.missing import isnull from pandas.types.generic import ABCDataFrame, ABCSeries, ABCIndexClass from pandas.types.common import is_object_dtype, is_list_like, is_scalar +from pandas.util.validators import validate_bool_kwarg from pandas.core import common as com import pandas.core.nanops as nanops @@ -1178,6 +1179,7 @@ def searchsorted(self, value, side='left', sorter=None): False: 'first'}) @Appender(_shared_docs['drop_duplicates'] % _indexops_doc_kwargs) def drop_duplicates(self, keep='first', inplace=False): + inplace = validate_bool_kwarg(inplace, 'inplace') if isinstance(self, ABCIndexClass): if self.is_unique: return self._shallow_copy() diff --git a/pandas/core/categorical.py b/pandas/core/categorical.py index 0562736038483..5980f872f951f 100644 --- a/pandas/core/categorical.py +++ b/pandas/core/categorical.py @@ -35,6 +35,7 @@ deprecate_kwarg, Substitution) from pandas.util.terminal import get_terminal_size +from pandas.util.validators import validate_bool_kwarg from pandas.core.config import get_option @@ -615,6 +616,7 @@ def set_ordered(self, value, inplace=False): Whether or not to set the ordered attribute inplace or return a copy of this categorical with ordered set to the value """ + inplace = validate_bool_kwarg(inplace, 'inplace') self._validate_ordered(value) cat = self if inplace else self.copy() cat._ordered = value @@ -631,6 +633,7 @@ def as_ordered(self, inplace=False): Whether or not to set the ordered attribute inplace or return a copy of this categorical with ordered set to True """ + inplace = validate_bool_kwarg(inplace, 'inplace') return self.set_ordered(True, inplace=inplace) def as_unordered(self, inplace=False): @@ -643,6 +646,7 @@ def as_unordered(self, inplace=False): Whether or not to set the ordered attribute inplace or return a copy of this categorical with ordered set to False """ + inplace = validate_bool_kwarg(inplace, 'inplace') return self.set_ordered(False, inplace=inplace) def _get_ordered(self): @@ -702,6 +706,7 @@ def set_categories(self, new_categories, ordered=None, rename=False, remove_categories remove_unused_categories """ + inplace = validate_bool_kwarg(inplace, 'inplace') new_categories = self._validate_categories(new_categories) cat = self if inplace else self.copy() if rename: @@ -754,6 +759,7 @@ def rename_categories(self, new_categories, inplace=False): remove_unused_categories set_categories """ + inplace = validate_bool_kwarg(inplace, 'inplace') cat = self if inplace else self.copy() cat.categories = new_categories if not inplace: @@ -794,6 +800,7 @@ def reorder_categories(self, new_categories, ordered=None, inplace=False): remove_unused_categories set_categories """ + inplace = validate_bool_kwarg(inplace, 'inplace') if set(self._categories) != set(new_categories): raise ValueError("items in new_categories are not the same as in " "old categories") @@ -832,6 +839,7 @@ def add_categories(self, new_categories, inplace=False): remove_unused_categories set_categories """ + inplace = validate_bool_kwarg(inplace, 'inplace') if not is_list_like(new_categories): new_categories = [new_categories] already_included = set(new_categories) & set(self._categories) @@ -877,6 +885,7 @@ def remove_categories(self, removals, inplace=False): remove_unused_categories set_categories """ + inplace = validate_bool_kwarg(inplace, 'inplace') if not is_list_like(removals): removals = [removals] @@ -917,6 +926,7 @@ def remove_unused_categories(self, inplace=False): remove_categories set_categories """ + inplace = validate_bool_kwarg(inplace, 'inplace') cat = self if inplace else self.copy() idx, inv = np.unique(cat._codes, return_inverse=True) @@ -1322,6 +1332,7 @@ def sort_values(self, inplace=False, ascending=True, na_position='last'): [NaN, NaN, 5.0, 2.0, 2.0] Categories (2, int64): [2, 5] """ + inplace = validate_bool_kwarg(inplace, 'inplace') if na_position not in ['last', 'first']: raise ValueError('invalid na_position: {!r}'.format(na_position)) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index d96fb094f5d5c..b9290c0ce3457 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -23,8 +23,7 @@ import numpy as np import numpy.ma as ma -from pandas.types.cast import (_maybe_upcast, - _infer_dtype_from_scalar, +from pandas.types.cast import (_maybe_upcast, _infer_dtype_from_scalar, _possibly_cast_to_datetime, _possibly_infer_to_datetimelike, _possibly_convert_platform, @@ -79,6 +78,7 @@ from pandas import compat from pandas.compat.numpy import function as nv from pandas.util.decorators import deprecate_kwarg, Appender, Substitution +from pandas.util.validators import validate_bool_kwarg from pandas.tseries.period import PeriodIndex from pandas.tseries.index import DatetimeIndex @@ -2164,6 +2164,7 @@ def query(self, expr, inplace=False, **kwargs): >>> df.query('a > b') >>> df[df.a > df.b] # same result as the previous expression """ + inplace = validate_bool_kwarg(inplace, 'inplace') if not isinstance(expr, compat.string_types): msg = "expr must be a string to be evaluated, {0} given" raise ValueError(msg.format(type(expr))) @@ -2230,6 +2231,7 @@ def eval(self, expr, inplace=None, **kwargs): >>> df.eval('a + b') >>> df.eval('c = a + b') """ + inplace = validate_bool_kwarg(inplace, 'inplace') resolvers = kwargs.pop('resolvers', None) kwargs['level'] = kwargs.pop('level', 0) + 1 if resolvers is None: @@ -2843,6 +2845,7 @@ def set_index(self, keys, drop=True, append=False, inplace=False, ------- dataframe : DataFrame """ + inplace = validate_bool_kwarg(inplace, 'inplace') if not isinstance(keys, list): keys = [keys] @@ -2935,6 +2938,7 @@ def reset_index(self, level=None, drop=False, inplace=False, col_level=0, ------- resetted : DataFrame """ + inplace = validate_bool_kwarg(inplace, 'inplace') if inplace: new_obj = self else: @@ -3039,6 +3043,7 @@ def dropna(self, axis=0, how='any', thresh=None, subset=None, ------- dropped : DataFrame """ + inplace = validate_bool_kwarg(inplace, 'inplace') if isinstance(axis, (tuple, list)): result = self for ax in axis: @@ -3102,6 +3107,7 @@ def drop_duplicates(self, subset=None, keep='first', inplace=False): ------- deduplicated : DataFrame """ + inplace = validate_bool_kwarg(inplace, 'inplace') duplicated = self.duplicated(subset, keep=keep) if inplace: @@ -3163,7 +3169,7 @@ def f(vals): @Appender(_shared_docs['sort_values'] % _shared_doc_kwargs) def sort_values(self, by, axis=0, ascending=True, inplace=False, kind='quicksort', na_position='last'): - + inplace = validate_bool_kwarg(inplace, 'inplace') axis = self._get_axis_number(axis) other_axis = 0 if axis == 1 else 1 @@ -3274,7 +3280,7 @@ def sort(self, columns=None, axis=0, ascending=True, inplace=False, def sort_index(self, axis=0, level=None, ascending=True, inplace=False, kind='quicksort', na_position='last', sort_remaining=True, by=None): - + inplace = validate_bool_kwarg(inplace, 'inplace') # 10726 if by is not None: warnings.warn("by argument to sort_index is deprecated, pls use " diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 1680c061ad7d3..0b5767da74cad 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -52,6 +52,7 @@ isidentifier, set_function_name) import pandas.core.nanops as nanops from pandas.util.decorators import Appender, Substitution, deprecate_kwarg +from pandas.util.validators import validate_bool_kwarg from pandas.core import config # goal is to be able to define the docs close to function, while still being @@ -733,6 +734,7 @@ def rename_axis(self, mapper, axis=0, copy=True, inplace=False): 1 2 5 2 3 6 """ + inplace = validate_bool_kwarg(inplace, 'inplace') non_mapper = is_scalar(mapper) or (is_list_like(mapper) and not is_dict_like(mapper)) if non_mapper: @@ -1950,6 +1952,7 @@ def drop(self, labels, axis=0, level=None, inplace=False, errors='raise'): ------- dropped : type of caller """ + inplace = validate_bool_kwarg(inplace, 'inplace') axis = self._get_axis_number(axis) axis_name = self._get_axis_name(axis) axis, axis_ = self._get_axis(axis), axis @@ -2099,6 +2102,7 @@ def sort_values(self, by, axis=0, ascending=True, inplace=False, @Appender(_shared_docs['sort_index'] % dict(axes="axes", klass="NDFrame")) def sort_index(self, axis=0, level=None, ascending=True, inplace=False, kind='quicksort', na_position='last', sort_remaining=True): + inplace = validate_bool_kwarg(inplace, 'inplace') axis = self._get_axis_number(axis) axis_name = self._get_axis_name(axis) labels = self._get_axis(axis) @@ -2872,6 +2876,7 @@ def consolidate(self, inplace=False): ------- consolidated : type of caller """ + inplace = validate_bool_kwarg(inplace, 'inplace') if inplace: self._consolidate_inplace() else: @@ -3267,6 +3272,7 @@ def convert_objects(self, convert_dates=True, convert_numeric=False, @Appender(_shared_docs['fillna'] % _shared_doc_kwargs) def fillna(self, value=None, method=None, axis=None, inplace=False, limit=None, downcast=None): + inplace = validate_bool_kwarg(inplace, 'inplace') if isinstance(value, (list, tuple)): raise TypeError('"value" parameter must be a scalar or dict, but ' 'you passed a "{0}"'.format(type(value).__name__)) @@ -3479,6 +3485,7 @@ def replace(self, to_replace=None, value=None, inplace=False, limit=None, and play with this method to gain intuition about how it works. """ + inplace = validate_bool_kwarg(inplace, 'inplace') if not is_bool(regex) and to_replace is not None: raise AssertionError("'to_replace' must be 'None' if 'regex' is " "not a bool") @@ -3714,6 +3721,7 @@ def interpolate(self, method='linear', axis=0, limit=None, inplace=False, """ Interpolate values according to different methods. """ + inplace = validate_bool_kwarg(inplace, 'inplace') if self.ndim > 2: raise NotImplementedError("Interpolate has not been implemented " @@ -4627,6 +4635,7 @@ def _where(self, cond, other=np.nan, inplace=False, axis=None, level=None, Equivalent to public method `where`, except that `other` is not applied as a function even if callable. Used in __setitem__. """ + inplace = validate_bool_kwarg(inplace, 'inplace') cond = com._apply_if_callable(cond, self) @@ -4894,6 +4903,7 @@ def where(self, cond, other=np.nan, inplace=False, axis=None, level=None, def mask(self, cond, other=np.nan, inplace=False, axis=None, level=None, try_cast=False, raise_on_error=True): + inplace = validate_bool_kwarg(inplace, 'inplace') cond = com._apply_if_callable(cond, self) return self.where(~cond, other=other, inplace=inplace, axis=axis, diff --git a/pandas/core/internals.py b/pandas/core/internals.py index aa865ae430d4a..289ce150eb46b 100644 --- a/pandas/core/internals.py +++ b/pandas/core/internals.py @@ -57,6 +57,7 @@ import pandas.tslib as tslib import pandas.computation.expressions as expressions from pandas.util.decorators import cache_readonly +from pandas.util.validators import validate_bool_kwarg from pandas.tslib import Timedelta from pandas import compat, _np_version_under1p9 @@ -360,6 +361,7 @@ def fillna(self, value, limit=None, inplace=False, downcast=None, """ fillna on the block with the value. If we fail, then convert to ObjectBlock and try again """ + inplace = validate_bool_kwarg(inplace, 'inplace') if not self._can_hold_na: if inplace: @@ -626,6 +628,7 @@ def replace(self, to_replace, value, inplace=False, filter=None, compatibility. """ + inplace = validate_bool_kwarg(inplace, 'inplace') original_to_replace = to_replace mask = isnull(self.values) # try to replace, if we raise an error, convert to ObjectBlock and @@ -897,6 +900,9 @@ def interpolate(self, method='pad', axis=0, index=None, values=None, inplace=False, limit=None, limit_direction='forward', fill_value=None, coerce=False, downcast=None, mgr=None, **kwargs): + + inplace = validate_bool_kwarg(inplace, 'inplace') + def check_int_bool(self, inplace): # Only FloatBlocks will contain NaNs. # timedelta subclasses IntBlock @@ -944,6 +950,8 @@ def _interpolate_with_fill(self, method='pad', axis=0, inplace=False, downcast=None, mgr=None): """ fillna but using the interpolate machinery """ + inplace = validate_bool_kwarg(inplace, 'inplace') + # if we are coercing, then don't force the conversion # if the block can't hold the type if coerce: @@ -970,6 +978,7 @@ def _interpolate(self, method=None, index=None, values=None, mgr=None, **kwargs): """ interpolate using scipy wrappers """ + inplace = validate_bool_kwarg(inplace, 'inplace') data = self.values if inplace else self.values.copy() # only deal with floats @@ -1514,6 +1523,7 @@ def putmask(self, mask, new, align=True, inplace=False, axis=0, ------- a new block(s), the result of the putmask """ + inplace = validate_bool_kwarg(inplace, 'inplace') # use block's copy logic. # .values may be an Index which does shallow copy by default @@ -1801,6 +1811,7 @@ def should_store(self, value): def replace(self, to_replace, value, inplace=False, filter=None, regex=False, convert=True, mgr=None): + inplace = validate_bool_kwarg(inplace, 'inplace') to_replace_values = np.atleast_1d(to_replace) if not np.can_cast(to_replace_values, bool): return self @@ -1982,6 +1993,9 @@ def replace(self, to_replace, value, inplace=False, filter=None, def _replace_single(self, to_replace, value, inplace=False, filter=None, regex=False, convert=True, mgr=None): + + inplace = validate_bool_kwarg(inplace, 'inplace') + # to_replace is regex compilable to_rep_re = regex and is_re_compilable(to_replace) @@ -3205,6 +3219,8 @@ def replace_list(self, src_list, dest_list, inplace=False, regex=False, mgr=None): """ do a list replace """ + inplace = validate_bool_kwarg(inplace, 'inplace') + if mgr is None: mgr = self diff --git a/pandas/core/series.py b/pandas/core/series.py index f656d72296e3a..0b29e8c93a12d 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -66,6 +66,7 @@ import pandas.core.nanops as nanops import pandas.formats.format as fmt from pandas.util.decorators import Appender, deprecate_kwarg, Substitution +from pandas.util.validators import validate_bool_kwarg import pandas.lib as lib import pandas.tslib as tslib @@ -975,6 +976,7 @@ def reset_index(self, level=None, drop=False, name=None, inplace=False): ---------- resetted : DataFrame, or Series if drop == True """ + inplace = validate_bool_kwarg(inplace, 'inplace') if drop: new_index = _default_index(len(self)) if level is not None and isinstance(self.index, MultiIndex): @@ -1175,6 +1177,7 @@ def _set_name(self, name, inplace=False): inplace : bool whether to modify `self` directly or return a copy """ + inplace = validate_bool_kwarg(inplace, 'inplace') ser = self if inplace else self.copy() ser.name = name return ser @@ -1722,6 +1725,7 @@ def update(self, other): def sort_values(self, axis=0, ascending=True, inplace=False, kind='quicksort', na_position='last'): + inplace = validate_bool_kwarg(inplace, 'inplace') axis = self._get_axis_number(axis) # GH 5856/5853 @@ -1774,6 +1778,7 @@ def _try_kind_sort(arr): def sort_index(self, axis=0, level=None, ascending=True, inplace=False, kind='quicksort', na_position='last', sort_remaining=True): + inplace = validate_bool_kwarg(inplace, 'inplace') axis = self._get_axis_number(axis) index = self.index if level is not None: @@ -2350,6 +2355,9 @@ def align(self, other, join='outer', axis=None, level=None, copy=True, @Appender(generic._shared_docs['rename'] % _shared_doc_kwargs) def rename(self, index=None, **kwargs): + kwargs['inplace'] = validate_bool_kwarg(kwargs.get('inplace', False), + 'inplace') + non_mapping = is_scalar(index) or (is_list_like(index) and not is_dict_like(index)) if non_mapping: @@ -2646,6 +2654,7 @@ def dropna(self, axis=0, inplace=False, **kwargs): inplace : boolean, default False Do operation in place. """ + inplace = validate_bool_kwarg(inplace, 'inplace') kwargs.pop('how', None) if kwargs: raise TypeError('dropna() got an unexpected keyword ' diff --git a/pandas/sparse/list.py b/pandas/sparse/list.py index 82de8cd7d3959..d294e65bbf10c 100644 --- a/pandas/sparse/list.py +++ b/pandas/sparse/list.py @@ -5,6 +5,7 @@ from pandas.types.common import is_scalar from pandas.sparse.array import SparseArray +from pandas.util.validators import validate_bool_kwarg import pandas._sparse as splib @@ -78,6 +79,7 @@ def consolidate(self, inplace=True): If inplace=False, new object, otherwise reference to existing object """ + inplace = validate_bool_kwarg(inplace, 'inplace') if not inplace: result = self.copy() else: diff --git a/pandas/tests/frame/test_missing.py b/pandas/tests/frame/test_missing.py index 8a6cbe44465c1..d7466f5ede06f 100644 --- a/pandas/tests/frame/test_missing.py +++ b/pandas/tests/frame/test_missing.py @@ -208,7 +208,7 @@ def test_fillna(self): # empty frame (GH #2778) df = DataFrame(columns=['x']) for m in ['pad', 'backfill']: - df.x.fillna(method=m, inplace=1) + df.x.fillna(method=m, inplace=True) df.x.fillna(method=m) # with different dtype (GH3386) diff --git a/pandas/tests/frame/test_validate.py b/pandas/tests/frame/test_validate.py new file mode 100644 index 0000000000000..e1ef87bb3271a --- /dev/null +++ b/pandas/tests/frame/test_validate.py @@ -0,0 +1,33 @@ +from unittest import TestCase +from pandas.core.frame import DataFrame + + +class TestDataFrameValidate(TestCase): + """Tests for error handling related to data types of method arguments.""" + df = DataFrame({'a': [1, 2], 'b': [3, 4]}) + + def test_validate_bool_args(self): + # Tests for error handling related to boolean arguments. + invalid_values = [1, "True", [1, 2, 3], 5.0] + + for value in invalid_values: + with self.assertRaises(ValueError): + self.df.query('a > b', inplace=value) + + with self.assertRaises(ValueError): + self.df.eval('a + b', inplace=value) + + with self.assertRaises(ValueError): + self.df.set_index(keys=['a'], inplace=value) + + with self.assertRaises(ValueError): + self.df.reset_index(inplace=value) + + with self.assertRaises(ValueError): + self.df.dropna(inplace=value) + + with self.assertRaises(ValueError): + self.df.drop_duplicates(inplace=value) + + with self.assertRaises(ValueError): + self.df.sort_values(by=['a'], inplace=value) diff --git a/pandas/tests/series/test_validate.py b/pandas/tests/series/test_validate.py new file mode 100644 index 0000000000000..cf0482b41c80a --- /dev/null +++ b/pandas/tests/series/test_validate.py @@ -0,0 +1,33 @@ +from unittest import TestCase +from pandas.core.series import Series + + +class TestSeriesValidate(TestCase): + """Tests for error handling related to data types of method arguments.""" + s = Series([1, 2, 3, 4, 5]) + + def test_validate_bool_args(self): + # Tests for error handling related to boolean arguments. + invalid_values = [1, "True", [1, 2, 3], 5.0] + + for value in invalid_values: + with self.assertRaises(ValueError): + self.s.reset_index(inplace=value) + + with self.assertRaises(ValueError): + self.s._set_name(name='hello', inplace=value) + + with self.assertRaises(ValueError): + self.s.sort_values(inplace=value) + + with self.assertRaises(ValueError): + self.s.sort_index(inplace=value) + + with self.assertRaises(ValueError): + self.s.sort_index(inplace=value) + + with self.assertRaises(ValueError): + self.s.rename(inplace=value) + + with self.assertRaises(ValueError): + self.s.dropna(inplace=value) diff --git a/pandas/tests/test_base.py b/pandas/tests/test_base.py index 717eae3e59715..f750936961831 100644 --- a/pandas/tests/test_base.py +++ b/pandas/tests/test_base.py @@ -1050,6 +1050,13 @@ def test_searchsorted(self): index = np.searchsorted(o, max(o), sorter=range(len(o))) self.assertTrue(0 <= index <= len(o)) + def test_validate_bool_args(self): + invalid_values = [1, "True", [1, 2, 3], 5.0] + + for value in invalid_values: + with self.assertRaises(ValueError): + self.int_series.drop_duplicates(inplace=value) + class TestTranspose(Ops): errmsg = "the 'axes' parameter is not supported" diff --git a/pandas/tests/test_categorical.py b/pandas/tests/test_categorical.py index 23280395427fd..382f1dd1decfb 100644 --- a/pandas/tests/test_categorical.py +++ b/pandas/tests/test_categorical.py @@ -1694,6 +1694,41 @@ def test_map(self): # GH 12766: Return an index not an array tm.assert_index_equal(result, Index(np.array([1] * 5, dtype=np.int64))) + def test_validate_inplace(self): + cat = Categorical(['A','B','B','C','A']) + invalid_values = [1, "True", [1,2,3], 5.0] + + for value in invalid_values: + with self.assertRaises(ValueError): + cat.set_ordered(value=True, inplace=value) + + with self.assertRaises(ValueError): + cat.as_ordered(inplace=value) + + with self.assertRaises(ValueError): + cat.as_unordered(inplace=value) + + with self.assertRaises(ValueError): + cat.set_categories(['X','Y','Z'], rename=True, inplace=value) + + with self.assertRaises(ValueError): + cat.rename_categories(['X','Y','Z'], inplace=value) + + with self.assertRaises(ValueError): + cat.reorder_categories(['X','Y','Z'], ordered=True, inplace=value) + + with self.assertRaises(ValueError): + cat.add_categories(new_categories=['D','E','F'], inplace=value) + + with self.assertRaises(ValueError): + cat.remove_categories(removals=['D','E','F'], inplace=value) + + with self.assertRaises(ValueError): + cat.remove_unused_categories(inplace=value) + + with self.assertRaises(ValueError): + cat.sort_values(inplace=value) + class TestCategoricalAsBlock(tm.TestCase): _multiprocess_can_split_ = True diff --git a/pandas/tests/test_generic.py b/pandas/tests/test_generic.py index 3500ce913462a..f32990ff32cbe 100644 --- a/pandas/tests/test_generic.py +++ b/pandas/tests/test_generic.py @@ -642,6 +642,40 @@ def test_numpy_clip(self): np.clip, obj, lower, upper, out=col) + def test_validate_bool_args(self): + df = DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]}) + invalid_values = [1, "True", [1, 2, 3], 5.0] + + for value in invalid_values: + with self.assertRaises(ValueError): + super(DataFrame, df).rename_axis(mapper={'a': 'x', 'b': 'y'}, + axis=1, inplace=value) + + with self.assertRaises(ValueError): + super(DataFrame, df).drop('a', axis=1, inplace=value) + + with self.assertRaises(ValueError): + super(DataFrame, df).sort_index(inplace=value) + + with self.assertRaises(ValueError): + super(DataFrame, df).consolidate(inplace=value) + + with self.assertRaises(ValueError): + super(DataFrame, df).fillna(value=0, inplace=value) + + with self.assertRaises(ValueError): + super(DataFrame, df).replace(to_replace=1, value=7, + inplace=value) + + with self.assertRaises(ValueError): + super(DataFrame, df).interpolate(inplace=value) + + with self.assertRaises(ValueError): + super(DataFrame, df)._where(cond=df.a > 2, inplace=value) + + with self.assertRaises(ValueError): + super(DataFrame, df).mask(cond=df.a > 2, inplace=value) + class TestSeries(tm.TestCase, Generic): _typ = Series diff --git a/pandas/tests/test_internals.py b/pandas/tests/test_internals.py index 32e8f44e6f258..22addd4c23817 100644 --- a/pandas/tests/test_internals.py +++ b/pandas/tests/test_internals.py @@ -859,6 +859,14 @@ def test_single_mgr_ctor(self): mgr = create_single_mgr('f8', num_rows=5) self.assertEqual(mgr.as_matrix().tolist(), [0., 1., 2., 3., 4.]) + def test_validate_bool_args(self): + invalid_values = [1, "True", [1, 2, 3], 5.0] + bm1 = create_mgr('a,b,c: i8-1; d,e,f: i8-2') + + for value in invalid_values: + with self.assertRaises(ValueError): + bm1.replace_list([1], [2], inplace=value) + class TestIndexing(object): # Nosetests-style data-driven tests. diff --git a/pandas/tests/test_util.py b/pandas/tests/test_util.py index cb12048676d26..ed82604035358 100644 --- a/pandas/tests/test_util.py +++ b/pandas/tests/test_util.py @@ -8,7 +8,8 @@ from pandas.util._move import move_into_mutable_buffer, BadMove, stolenbuf from pandas.util.decorators import deprecate_kwarg from pandas.util.validators import (validate_args, validate_kwargs, - validate_args_and_kwargs) + validate_args_and_kwargs, + validate_bool_kwarg) import pandas.util.testing as tm @@ -200,6 +201,22 @@ def test_validation(self): kwargs = dict(f=None, b=1) validate_kwargs(self.fname, kwargs, compat_args) + def test_validate_bool_kwarg(self): + arg_names = ['inplace', 'copy'] + invalid_values = [1, "True", [1, 2, 3], 5.0] + valid_values = [True, False, None] + + for name in arg_names: + for value in invalid_values: + with tm.assertRaisesRegexp(ValueError, + ("For argument \"%s\" expected " + "type bool, received type %s") % + (name, type(value).__name__)): + validate_bool_kwarg(value, name) + + for value in valid_values: + tm.assert_equal(validate_bool_kwarg(value, name), value) + class TestValidateKwargsAndArgs(tm.TestCase): fname = 'func' diff --git a/pandas/util/validators.py b/pandas/util/validators.py index 964fa9d9b38d5..f22412a2bcd17 100644 --- a/pandas/util/validators.py +++ b/pandas/util/validators.py @@ -215,3 +215,12 @@ def validate_args_and_kwargs(fname, args, kwargs, kwargs.update(args_dict) validate_kwargs(fname, kwargs, compat_args) + + +def validate_bool_kwarg(value, arg_name): + """ Ensures that argument passed in arg_name is of type bool. """ + if not (is_bool(value) or value is None): + raise ValueError('For argument "%s" expected type bool, ' + 'received type %s.' % + (arg_name, type(value).__name__)) + return value