Skip to content

Commit b895968

Browse files
rforgionejorisvandenbossche
authored andcommitted
Enforce boolean types for the inplace keyword (pandas-dev#14318)
1 parent db08da2 commit b895968

19 files changed

+250
-6
lines changed

doc/source/whatsnew/v0.20.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ Other API Changes
247247

248248
- ``pd.read_csv()`` will now issue a ``ParserWarning`` whenever there are conflicting values provided by the ``dialect`` parameter and the user (:issue:`14898`)
249249
- ``pd.read_csv()`` will now raise a ``ValueError`` for the C engine if the quote character is larger than than one byte (:issue:`11592`)
250+
- ``inplace`` arguments now require a boolean value, else a ``ValueError`` is thrown (:issue:`14189`)
250251

251252
.. _whatsnew_0200.deprecations:
252253

pandas/computation/eval.py

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pandas.computation.scope import _ensure_scope
1212
from pandas.compat import string_types
1313
from pandas.computation.engines import _engines
14+
from pandas.util.validators import validate_bool_kwarg
1415

1516

1617
def _check_engine(engine):
@@ -231,6 +232,7 @@ def eval(expr, parser='pandas', engine=None, truediv=True,
231232
pandas.DataFrame.query
232233
pandas.DataFrame.eval
233234
"""
235+
inplace = validate_bool_kwarg(inplace, 'inplace')
234236
first_expr = True
235237
if isinstance(expr, string_types):
236238
_check_expression(expr)

pandas/computation/tests/test_eval.py

+9
Original file line numberDiff line numberDiff line change
@@ -1970,6 +1970,15 @@ def test_negate_lt_eq_le():
19701970
for engine, parser in product(_engines, expr._parsers):
19711971
yield check_negate_lt_eq_le, engine, parser
19721972

1973+
class TestValidate(tm.TestCase):
1974+
1975+
def test_validate_bool_args(self):
1976+
invalid_values = [1, "True", [1,2,3], 5.0]
1977+
1978+
for value in invalid_values:
1979+
with self.assertRaises(ValueError):
1980+
pd.eval("2+2", inplace=value)
1981+
19731982

19741983
if __name__ == '__main__':
19751984
nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],

pandas/core/base.py

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pandas.types.missing import isnull
99
from pandas.types.generic import ABCDataFrame, ABCSeries, ABCIndexClass
1010
from pandas.types.common import is_object_dtype, is_list_like, is_scalar
11+
from pandas.util.validators import validate_bool_kwarg
1112

1213
from pandas.core import common as com
1314
import pandas.core.nanops as nanops
@@ -1178,6 +1179,7 @@ def searchsorted(self, value, side='left', sorter=None):
11781179
False: 'first'})
11791180
@Appender(_shared_docs['drop_duplicates'] % _indexops_doc_kwargs)
11801181
def drop_duplicates(self, keep='first', inplace=False):
1182+
inplace = validate_bool_kwarg(inplace, 'inplace')
11811183
if isinstance(self, ABCIndexClass):
11821184
if self.is_unique:
11831185
return self._shallow_copy()

pandas/core/categorical.py

+11
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
deprecate_kwarg, Substitution)
3636

3737
from pandas.util.terminal import get_terminal_size
38+
from pandas.util.validators import validate_bool_kwarg
3839
from pandas.core.config import get_option
3940

4041

@@ -615,6 +616,7 @@ def set_ordered(self, value, inplace=False):
615616
Whether or not to set the ordered attribute inplace or return a copy
616617
of this categorical with ordered set to the value
617618
"""
619+
inplace = validate_bool_kwarg(inplace, 'inplace')
618620
self._validate_ordered(value)
619621
cat = self if inplace else self.copy()
620622
cat._ordered = value
@@ -631,6 +633,7 @@ def as_ordered(self, inplace=False):
631633
Whether or not to set the ordered attribute inplace or return a copy
632634
of this categorical with ordered set to True
633635
"""
636+
inplace = validate_bool_kwarg(inplace, 'inplace')
634637
return self.set_ordered(True, inplace=inplace)
635638

636639
def as_unordered(self, inplace=False):
@@ -643,6 +646,7 @@ def as_unordered(self, inplace=False):
643646
Whether or not to set the ordered attribute inplace or return a copy
644647
of this categorical with ordered set to False
645648
"""
649+
inplace = validate_bool_kwarg(inplace, 'inplace')
646650
return self.set_ordered(False, inplace=inplace)
647651

648652
def _get_ordered(self):
@@ -702,6 +706,7 @@ def set_categories(self, new_categories, ordered=None, rename=False,
702706
remove_categories
703707
remove_unused_categories
704708
"""
709+
inplace = validate_bool_kwarg(inplace, 'inplace')
705710
new_categories = self._validate_categories(new_categories)
706711
cat = self if inplace else self.copy()
707712
if rename:
@@ -754,6 +759,7 @@ def rename_categories(self, new_categories, inplace=False):
754759
remove_unused_categories
755760
set_categories
756761
"""
762+
inplace = validate_bool_kwarg(inplace, 'inplace')
757763
cat = self if inplace else self.copy()
758764
cat.categories = new_categories
759765
if not inplace:
@@ -794,6 +800,7 @@ def reorder_categories(self, new_categories, ordered=None, inplace=False):
794800
remove_unused_categories
795801
set_categories
796802
"""
803+
inplace = validate_bool_kwarg(inplace, 'inplace')
797804
if set(self._categories) != set(new_categories):
798805
raise ValueError("items in new_categories are not the same as in "
799806
"old categories")
@@ -832,6 +839,7 @@ def add_categories(self, new_categories, inplace=False):
832839
remove_unused_categories
833840
set_categories
834841
"""
842+
inplace = validate_bool_kwarg(inplace, 'inplace')
835843
if not is_list_like(new_categories):
836844
new_categories = [new_categories]
837845
already_included = set(new_categories) & set(self._categories)
@@ -877,6 +885,7 @@ def remove_categories(self, removals, inplace=False):
877885
remove_unused_categories
878886
set_categories
879887
"""
888+
inplace = validate_bool_kwarg(inplace, 'inplace')
880889
if not is_list_like(removals):
881890
removals = [removals]
882891

@@ -917,6 +926,7 @@ def remove_unused_categories(self, inplace=False):
917926
remove_categories
918927
set_categories
919928
"""
929+
inplace = validate_bool_kwarg(inplace, 'inplace')
920930
cat = self if inplace else self.copy()
921931
idx, inv = np.unique(cat._codes, return_inverse=True)
922932

@@ -1322,6 +1332,7 @@ def sort_values(self, inplace=False, ascending=True, na_position='last'):
13221332
[NaN, NaN, 5.0, 2.0, 2.0]
13231333
Categories (2, int64): [2, 5]
13241334
"""
1335+
inplace = validate_bool_kwarg(inplace, 'inplace')
13251336
if na_position not in ['last', 'first']:
13261337
raise ValueError('invalid na_position: {!r}'.format(na_position))
13271338

pandas/core/frame.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@
2323
import numpy as np
2424
import numpy.ma as ma
2525

26-
from pandas.types.cast import (_maybe_upcast,
27-
_infer_dtype_from_scalar,
26+
from pandas.types.cast import (_maybe_upcast, _infer_dtype_from_scalar,
2827
_possibly_cast_to_datetime,
2928
_possibly_infer_to_datetimelike,
3029
_possibly_convert_platform,
@@ -79,6 +78,7 @@
7978
from pandas import compat
8079
from pandas.compat.numpy import function as nv
8180
from pandas.util.decorators import deprecate_kwarg, Appender, Substitution
81+
from pandas.util.validators import validate_bool_kwarg
8282

8383
from pandas.tseries.period import PeriodIndex
8484
from pandas.tseries.index import DatetimeIndex
@@ -2164,6 +2164,7 @@ def query(self, expr, inplace=False, **kwargs):
21642164
>>> df.query('a > b')
21652165
>>> df[df.a > df.b] # same result as the previous expression
21662166
"""
2167+
inplace = validate_bool_kwarg(inplace, 'inplace')
21672168
if not isinstance(expr, compat.string_types):
21682169
msg = "expr must be a string to be evaluated, {0} given"
21692170
raise ValueError(msg.format(type(expr)))
@@ -2230,6 +2231,7 @@ def eval(self, expr, inplace=None, **kwargs):
22302231
>>> df.eval('a + b')
22312232
>>> df.eval('c = a + b')
22322233
"""
2234+
inplace = validate_bool_kwarg(inplace, 'inplace')
22332235
resolvers = kwargs.pop('resolvers', None)
22342236
kwargs['level'] = kwargs.pop('level', 0) + 1
22352237
if resolvers is None:
@@ -2843,6 +2845,7 @@ def set_index(self, keys, drop=True, append=False, inplace=False,
28432845
-------
28442846
dataframe : DataFrame
28452847
"""
2848+
inplace = validate_bool_kwarg(inplace, 'inplace')
28462849
if not isinstance(keys, list):
28472850
keys = [keys]
28482851

@@ -2935,6 +2938,7 @@ def reset_index(self, level=None, drop=False, inplace=False, col_level=0,
29352938
-------
29362939
resetted : DataFrame
29372940
"""
2941+
inplace = validate_bool_kwarg(inplace, 'inplace')
29382942
if inplace:
29392943
new_obj = self
29402944
else:
@@ -3039,6 +3043,7 @@ def dropna(self, axis=0, how='any', thresh=None, subset=None,
30393043
-------
30403044
dropped : DataFrame
30413045
"""
3046+
inplace = validate_bool_kwarg(inplace, 'inplace')
30423047
if isinstance(axis, (tuple, list)):
30433048
result = self
30443049
for ax in axis:
@@ -3102,6 +3107,7 @@ def drop_duplicates(self, subset=None, keep='first', inplace=False):
31023107
-------
31033108
deduplicated : DataFrame
31043109
"""
3110+
inplace = validate_bool_kwarg(inplace, 'inplace')
31053111
duplicated = self.duplicated(subset, keep=keep)
31063112

31073113
if inplace:
@@ -3163,7 +3169,7 @@ def f(vals):
31633169
@Appender(_shared_docs['sort_values'] % _shared_doc_kwargs)
31643170
def sort_values(self, by, axis=0, ascending=True, inplace=False,
31653171
kind='quicksort', na_position='last'):
3166-
3172+
inplace = validate_bool_kwarg(inplace, 'inplace')
31673173
axis = self._get_axis_number(axis)
31683174
other_axis = 0 if axis == 1 else 1
31693175

@@ -3274,7 +3280,7 @@ def sort(self, columns=None, axis=0, ascending=True, inplace=False,
32743280
def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
32753281
kind='quicksort', na_position='last', sort_remaining=True,
32763282
by=None):
3277-
3283+
inplace = validate_bool_kwarg(inplace, 'inplace')
32783284
# 10726
32793285
if by is not None:
32803286
warnings.warn("by argument to sort_index is deprecated, pls use "

pandas/core/generic.py

+10
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
isidentifier, set_function_name)
5353
import pandas.core.nanops as nanops
5454
from pandas.util.decorators import Appender, Substitution, deprecate_kwarg
55+
from pandas.util.validators import validate_bool_kwarg
5556
from pandas.core import config
5657

5758
# 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):
733734
1 2 5
734735
2 3 6
735736
"""
737+
inplace = validate_bool_kwarg(inplace, 'inplace')
736738
non_mapper = is_scalar(mapper) or (is_list_like(mapper) and not
737739
is_dict_like(mapper))
738740
if non_mapper:
@@ -1950,6 +1952,7 @@ def drop(self, labels, axis=0, level=None, inplace=False, errors='raise'):
19501952
-------
19511953
dropped : type of caller
19521954
"""
1955+
inplace = validate_bool_kwarg(inplace, 'inplace')
19531956
axis = self._get_axis_number(axis)
19541957
axis_name = self._get_axis_name(axis)
19551958
axis, axis_ = self._get_axis(axis), axis
@@ -2099,6 +2102,7 @@ def sort_values(self, by, axis=0, ascending=True, inplace=False,
20992102
@Appender(_shared_docs['sort_index'] % dict(axes="axes", klass="NDFrame"))
21002103
def sort_index(self, axis=0, level=None, ascending=True, inplace=False,
21012104
kind='quicksort', na_position='last', sort_remaining=True):
2105+
inplace = validate_bool_kwarg(inplace, 'inplace')
21022106
axis = self._get_axis_number(axis)
21032107
axis_name = self._get_axis_name(axis)
21042108
labels = self._get_axis(axis)
@@ -2872,6 +2876,7 @@ def consolidate(self, inplace=False):
28722876
-------
28732877
consolidated : type of caller
28742878
"""
2879+
inplace = validate_bool_kwarg(inplace, 'inplace')
28752880
if inplace:
28762881
self._consolidate_inplace()
28772882
else:
@@ -3267,6 +3272,7 @@ def convert_objects(self, convert_dates=True, convert_numeric=False,
32673272
@Appender(_shared_docs['fillna'] % _shared_doc_kwargs)
32683273
def fillna(self, value=None, method=None, axis=None, inplace=False,
32693274
limit=None, downcast=None):
3275+
inplace = validate_bool_kwarg(inplace, 'inplace')
32703276
if isinstance(value, (list, tuple)):
32713277
raise TypeError('"value" parameter must be a scalar or dict, but '
32723278
'you passed a "{0}"'.format(type(value).__name__))
@@ -3479,6 +3485,7 @@ def replace(self, to_replace=None, value=None, inplace=False, limit=None,
34793485
and play with this method to gain intuition about how it works.
34803486
34813487
"""
3488+
inplace = validate_bool_kwarg(inplace, 'inplace')
34823489
if not is_bool(regex) and to_replace is not None:
34833490
raise AssertionError("'to_replace' must be 'None' if 'regex' is "
34843491
"not a bool")
@@ -3714,6 +3721,7 @@ def interpolate(self, method='linear', axis=0, limit=None, inplace=False,
37143721
"""
37153722
Interpolate values according to different methods.
37163723
"""
3724+
inplace = validate_bool_kwarg(inplace, 'inplace')
37173725

37183726
if self.ndim > 2:
37193727
raise NotImplementedError("Interpolate has not been implemented "
@@ -4627,6 +4635,7 @@ def _where(self, cond, other=np.nan, inplace=False, axis=None, level=None,
46274635
Equivalent to public method `where`, except that `other` is not
46284636
applied as a function even if callable. Used in __setitem__.
46294637
"""
4638+
inplace = validate_bool_kwarg(inplace, 'inplace')
46304639

46314640
cond = com._apply_if_callable(cond, self)
46324641

@@ -4894,6 +4903,7 @@ def where(self, cond, other=np.nan, inplace=False, axis=None, level=None,
48944903
def mask(self, cond, other=np.nan, inplace=False, axis=None, level=None,
48954904
try_cast=False, raise_on_error=True):
48964905

4906+
inplace = validate_bool_kwarg(inplace, 'inplace')
48974907
cond = com._apply_if_callable(cond, self)
48984908

48994909
return self.where(~cond, other=other, inplace=inplace, axis=axis,

pandas/core/internals.py

+16
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import pandas.tslib as tslib
5858
import pandas.computation.expressions as expressions
5959
from pandas.util.decorators import cache_readonly
60+
from pandas.util.validators import validate_bool_kwarg
6061

6162
from pandas.tslib import Timedelta
6263
from pandas import compat, _np_version_under1p9
@@ -360,6 +361,7 @@ def fillna(self, value, limit=None, inplace=False, downcast=None,
360361
""" fillna on the block with the value. If we fail, then convert to
361362
ObjectBlock and try again
362363
"""
364+
inplace = validate_bool_kwarg(inplace, 'inplace')
363365

364366
if not self._can_hold_na:
365367
if inplace:
@@ -626,6 +628,7 @@ def replace(self, to_replace, value, inplace=False, filter=None,
626628
compatibility.
627629
"""
628630

631+
inplace = validate_bool_kwarg(inplace, 'inplace')
629632
original_to_replace = to_replace
630633
mask = isnull(self.values)
631634
# 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,
897900
inplace=False, limit=None, limit_direction='forward',
898901
fill_value=None, coerce=False, downcast=None, mgr=None,
899902
**kwargs):
903+
904+
inplace = validate_bool_kwarg(inplace, 'inplace')
905+
900906
def check_int_bool(self, inplace):
901907
# Only FloatBlocks will contain NaNs.
902908
# timedelta subclasses IntBlock
@@ -944,6 +950,8 @@ def _interpolate_with_fill(self, method='pad', axis=0, inplace=False,
944950
downcast=None, mgr=None):
945951
""" fillna but using the interpolate machinery """
946952

953+
inplace = validate_bool_kwarg(inplace, 'inplace')
954+
947955
# if we are coercing, then don't force the conversion
948956
# if the block can't hold the type
949957
if coerce:
@@ -970,6 +978,7 @@ def _interpolate(self, method=None, index=None, values=None,
970978
mgr=None, **kwargs):
971979
""" interpolate using scipy wrappers """
972980

981+
inplace = validate_bool_kwarg(inplace, 'inplace')
973982
data = self.values if inplace else self.values.copy()
974983

975984
# only deal with floats
@@ -1514,6 +1523,7 @@ def putmask(self, mask, new, align=True, inplace=False, axis=0,
15141523
-------
15151524
a new block(s), the result of the putmask
15161525
"""
1526+
inplace = validate_bool_kwarg(inplace, 'inplace')
15171527

15181528
# use block's copy logic.
15191529
# .values may be an Index which does shallow copy by default
@@ -1801,6 +1811,7 @@ def should_store(self, value):
18011811

18021812
def replace(self, to_replace, value, inplace=False, filter=None,
18031813
regex=False, convert=True, mgr=None):
1814+
inplace = validate_bool_kwarg(inplace, 'inplace')
18041815
to_replace_values = np.atleast_1d(to_replace)
18051816
if not np.can_cast(to_replace_values, bool):
18061817
return self
@@ -1982,6 +1993,9 @@ def replace(self, to_replace, value, inplace=False, filter=None,
19821993

19831994
def _replace_single(self, to_replace, value, inplace=False, filter=None,
19841995
regex=False, convert=True, mgr=None):
1996+
1997+
inplace = validate_bool_kwarg(inplace, 'inplace')
1998+
19851999
# to_replace is regex compilable
19862000
to_rep_re = regex and is_re_compilable(to_replace)
19872001

@@ -3205,6 +3219,8 @@ def replace_list(self, src_list, dest_list, inplace=False, regex=False,
32053219
mgr=None):
32063220
""" do a list replace """
32073221

3222+
inplace = validate_bool_kwarg(inplace, 'inplace')
3223+
32083224
if mgr is None:
32093225
mgr = self
32103226

0 commit comments

Comments
 (0)