Skip to content

Commit 3b135c3

Browse files
TomAugspurgerjreback
authored andcommitted
API: Validate keyword arguments to fillna (#19684)
1 parent aa68c06 commit 3b135c3

File tree

5 files changed

+61
-11
lines changed

5 files changed

+61
-11
lines changed

doc/source/whatsnew/v0.23.0.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ Datetimelike API Changes
573573
- Subtracting ``NaT`` from a :class:`Series` with ``dtype='datetime64[ns]'`` returns a ``Series`` with ``dtype='timedelta64[ns]'`` instead of ``dtype='datetime64[ns]'`` (:issue:`18808`)
574574
- Operations between a :class:`Series` with dtype ``dtype='datetime64[ns]'`` and a :class:`PeriodIndex` will correctly raises ``TypeError`` (:issue:`18850`)
575575
- Subtraction of :class:`Series` with timezone-aware ``dtype='datetime64[ns]'`` with mis-matched timezones will raise ``TypeError`` instead of ``ValueError`` (:issue:`18817`)
576+
- :func:`pandas.merge` provides a more informative error message when trying to merge on timezone-aware and timezone-naive columns (:issue:`15800`)
576577

577578
.. _whatsnew_0230.api.other:
578579

@@ -592,7 +593,6 @@ Other API Changes
592593
- :func:`Series.fillna` now raises a ``TypeError`` instead of a ``ValueError`` when passed a list, tuple or DataFrame as a ``value`` (:issue:`18293`)
593594
- :func:`pandas.DataFrame.merge` no longer casts a ``float`` column to ``object`` when merging on ``int`` and ``float`` columns (:issue:`16572`)
594595
- :func:`pandas.merge` now raises a ``ValueError`` when trying to merge on incompatible data types (:issue:`9780`)
595-
- :func:`pandas.merge` provides a more informative error message when trying to merge on timezone-aware and timezone-naive columns (:issue:`15800`)
596596
- The default NA value for :class:`UInt64Index` has changed from 0 to ``NaN``, which impacts methods that mask with NA, such as ``UInt64Index.where()`` (:issue:`18398`)
597597
- Refactored ``setup.py`` to use ``find_packages`` instead of explicitly listing out all subpackages (:issue:`18535`)
598598
- Rearranged the order of keyword arguments in :func:`read_excel()` to align with :func:`read_csv()` (:issue:`16672`)
@@ -606,6 +606,7 @@ Other API Changes
606606
- :func:`Series.to_csv` now accepts a ``compression`` argument that works in the same way as the ``compression`` argument in :func:`DataFrame.to_csv` (:issue:`18958`)
607607
- Set operations (union, difference...) on :class:`IntervalIndex` with incompatible index types will now raise a ``TypeError`` rather than a ``ValueError`` (:issue:`19329`)
608608
- :class:`DateOffset` objects render more simply, e.g. ``<DateOffset: days=1>`` instead of ``<DateOffset: kwds={'days': 1}>`` (:issue:`19403`)
609+
- ``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`)
609610

610611
.. _whatsnew_0230.deprecations:
611612

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
@@ -1610,6 +1610,9 @@ def fillna(self, value=None, method=None, limit=None):
16101610
-------
16111611
filled : Categorical with NA/NaN filled
16121612
"""
1613+
value, method = validate_fillna_kwargs(
1614+
value, method, validate_scalar_dict_value=False
1615+
)
16131616

16141617
if value is None:
16151618
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
@@ -4697,10 +4697,8 @@ def infer_objects(self):
46974697
def fillna(self, value=None, method=None, axis=None, inplace=False,
46984698
limit=None, downcast=None):
46994699
inplace = validate_bool_kwarg(inplace, 'inplace')
4700+
value, method = validate_fillna_kwargs(value, method)
47004701

4701-
if isinstance(value, (list, tuple)):
4702-
raise TypeError('"value" parameter must be a scalar or dict, but '
4703-
'you passed a "{0}"'.format(type(value).__name__))
47044702
self._consolidate_inplace()
47054703

47064704
# set the default here, so functions examining the signaure
@@ -4711,8 +4709,7 @@ def fillna(self, value=None, method=None, axis=None, inplace=False,
47114709
method = missing.clean_fill_method(method)
47124710
from pandas import DataFrame
47134711
if value is None:
4714-
if method is None:
4715-
raise ValueError('must specify a fill method or value')
4712+
47164713
if self._is_mixed_type and axis == 1:
47174714
if inplace:
47184715
raise NotImplementedError()
@@ -4746,9 +4743,6 @@ def fillna(self, value=None, method=None, axis=None, inplace=False,
47464743
coerce=True,
47474744
downcast=downcast)
47484745
else:
4749-
if method is not None:
4750-
raise ValueError('cannot specify both a fill method and value')
4751-
47524746
if len(self._get_axis(axis)) == 0:
47534747
return self
47544748

pandas/tests/categorical/test_missing.py

+16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
import numpy as np
4+
import pytest
45

56
import pandas.util.testing as tm
67
from pandas import (Categorical, Index, isna)
@@ -53,3 +54,18 @@ def test_set_item_nan(self):
5354

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

pandas/util/_validators.py

+36
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,39 @@ 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+
validate_scalar_dict_value : bool, default True
336+
Whether to validate that 'value' is a scalar or dict. Specifically,
337+
validate that it is not a list or tuple.
338+
339+
Returns
340+
-------
341+
value, method : object
342+
"""
343+
from pandas.core.missing import clean_fill_method
344+
345+
if value is None and method is None:
346+
raise ValueError("Must specify a fill 'value' or 'method'.")
347+
elif value is None and method is not None:
348+
method = clean_fill_method(method)
349+
350+
elif value is not None and method is None:
351+
if validate_scalar_dict_value and isinstance(value, (list, tuple)):
352+
raise TypeError('"value" parameter must be a scalar or dict, but '
353+
'you passed a "{0}"'.format(type(value).__name__))
354+
355+
elif value is not None and method is not None:
356+
raise ValueError("Cannot specify both 'value' and 'method'.")
357+
358+
return value, method

0 commit comments

Comments
 (0)