Skip to content

Commit bfb868b

Browse files
committed
API: Validate keyword arguments to fillna
Closes pandas-dev#19682
1 parent df38f66 commit bfb868b

File tree

5 files changed

+62
-10
lines changed

5 files changed

+62
-10
lines changed

doc/source/whatsnew/v0.23.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ Other API Changes
570570
- Set operations (union, difference...) on :class:`IntervalIndex` with incompatible index types will now raise a ``TypeError`` rather than a ``ValueError`` (:issue:`19329`)
571571
- :class:`DateOffset` objects render more simply, e.g. "<DateOffset: days=1>" instead of "<DateOffset: kwds={'days': 1}>" (:issue:`19403`)
572572
- :func:`pandas.merge` provides a more informative error message when trying to merge on timezone-aware and timezone-naive columns (:issue:`15800`)
573+
- ``Categorical.fillna`` now validates its ``value`` and ``method`` keyword arguments. It now raises when both or none are specified, matching the behavior of :meth:`Series.fillna` (:issue:`19682`)
573574

574575
.. _whatsnew_0230.deprecations:
575576

pandas/core/arrays/categorical.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
Appender, cache_readonly, deprecate_kwarg, Substitution)
4141

4242
from pandas.io.formats.terminal import get_terminal_size
43-
from pandas.util._validators import validate_bool_kwarg
43+
from pandas.util._validators import validate_bool_kwarg, validate_fillna_kwargs
4444
from pandas.core.config import get_option
4545

4646
from .base import ExtensionArray
@@ -1607,6 +1607,9 @@ def fillna(self, value=None, method=None, limit=None):
16071607
-------
16081608
filled : Categorical with NA/NaN filled
16091609
"""
1610+
value, method = validate_fillna_kwargs(
1611+
value, method, validate_scalar_dict_value=False
1612+
)
16101613

16111614
if value is None:
16121615
value = np.nan

pandas/core/generic.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
import pandas.core.nanops as nanops
5555
from pandas.util._decorators import (Appender, Substitution,
5656
deprecate_kwarg)
57-
from pandas.util._validators import validate_bool_kwarg
57+
from pandas.util._validators import validate_bool_kwarg, validate_fillna_kwargs
5858
from pandas.core import config
5959

6060
# goal is to be able to define the docs close to function, while still being
@@ -4694,10 +4694,8 @@ def infer_objects(self):
46944694
def fillna(self, value=None, method=None, axis=None, inplace=False,
46954695
limit=None, downcast=None):
46964696
inplace = validate_bool_kwarg(inplace, 'inplace')
4697+
value, method = validate_fillna_kwargs(value, method)
46974698

4698-
if isinstance(value, (list, tuple)):
4699-
raise TypeError('"value" parameter must be a scalar or dict, but '
4700-
'you passed a "{0}"'.format(type(value).__name__))
47014699
self._consolidate_inplace()
47024700

47034701
# set the default here, so functions examining the signaure
@@ -4708,8 +4706,7 @@ def fillna(self, value=None, method=None, axis=None, inplace=False,
47084706
method = missing.clean_fill_method(method)
47094707
from pandas import DataFrame
47104708
if value is None:
4711-
if method is None:
4712-
raise ValueError('must specify a fill method or value')
4709+
47134710
if self._is_mixed_type and axis == 1:
47144711
if inplace:
47154712
raise NotImplementedError()
@@ -4743,9 +4740,6 @@ def fillna(self, value=None, method=None, axis=None, inplace=False,
47434740
coerce=True,
47444741
downcast=downcast)
47454742
else:
4746-
if method is not None:
4747-
raise ValueError('cannot specify both a fill method and value')
4748-
47494743
if len(self._get_axis(axis)) == 0:
47504744
return self
47514745

pandas/tests/categorical/test_missing.py

+20
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,23 @@ def test_set_item_nan(self):
5353

5454
exp = Categorical([1, np.nan, 3], categories=[1, 2, 3])
5555
tm.assert_categorical_equal(cat, exp)
56+
57+
def test_fillna_raises(self):
58+
# https://github.com/pandas-dev/pandas/issues/19682
59+
60+
cat = Categorical([1, 2, 3])
61+
62+
xpr = "Cannot specify both 'value' and 'method'."
63+
64+
with tm.assert_raises_regex(ValueError, xpr):
65+
cat.fillna(value=1, method='ffill')
66+
67+
xpr = "Must specify a fill 'value' or 'method'."
68+
69+
with tm.assert_raises_regex(ValueError, xpr):
70+
cat.fillna()
71+
72+
xpr = "Invalid fill method. Expecting .* bad"
73+
74+
with tm.assert_raises_regex(ValueError, xpr):
75+
cat.fillna(method='bad')

pandas/util/_validators.py

+34
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,37 @@ def validate_axis_style_args(data, args, kwargs, arg_name, method_name):
320320
msg = "Cannot specify all of '{}', 'index', 'columns'."
321321
raise TypeError(msg.format(arg_name))
322322
return out
323+
324+
325+
def validate_fillna_kwargs(value, method, validate_scalar_dict_value=True):
326+
"""Validate the keyword arguments to 'fillna'.
327+
328+
This checks that exactly one of 'value' and 'method' is specified.
329+
If 'method' is specified, this validates that it's a valid method.
330+
331+
Parameters
332+
----------
333+
value, method : object
334+
The 'value' and 'method' keyword arguments for 'fillna'.
335+
336+
Returns
337+
-------
338+
value, method : object
339+
These aren't modified in any way. Just validated.
340+
"""
341+
from pandas.core.missing import clean_fill_method
342+
343+
if value is None and method is None:
344+
raise ValueError("Must specify a fill 'value' or 'method'.")
345+
elif value is None and method is not None:
346+
clean_fill_method(method)
347+
348+
elif value is not None and method is None:
349+
if validate_scalar_dict_value and isinstance(value, (list, tuple)):
350+
raise TypeError('"value" parameter must be a scalar or dict, but '
351+
'you passed a "{0}"'.format(type(value).__name__))
352+
353+
elif value is not None and method is not None:
354+
raise ValueError("Cannot specify both 'value' and 'method'.")
355+
356+
return value, method

0 commit comments

Comments
 (0)