diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 495c8244142f9..c0518cb4b913b 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -171,14 +171,26 @@ and ``na_action="ignore"`` did not work correctly for any :class:`.ExtensionArra *New behavior*: -.. ipython:: python +.. code-block:: ipython - ser = pd.Series(["a", "b", np.nan], dtype="category") - ser.map(str.upper, na_action="ignore") - df = pd.DataFrame(ser) - df.map(str.upper, na_action="ignore") - idx = pd.Index(ser) - idx.map(str.upper, na_action="ignore") + In [1]: ser = pd.Series(["a", "b", np.nan], dtype="category") + In [2]: ser.map(str.upper, na_action="ignore") + Out[2]: + 0 A + 1 B + 2 NaN + dtype: category + Categories (2, object): ['A', 'B'] + In [3]: df = pd.DataFrame(ser) + In [4]: df.map(str.upper, na_action="ignore") + Out[4]: + 0 + 0 A + 1 B + 2 NaN + In [5]: idx = pd.Index(ser) + In [6]: idx.map(str.upper, na_action="ignore") + Out[6]: CategoricalIndex(['A', 'B', nan], categories=['A', 'B'], ordered=False, dtype='category') Also, note that :meth:`Categorical.map` implicitly has had its ``na_action`` set to ``"ignore"`` by default. This has been deprecated and the default for :meth:`Categorical.map` will change diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 37fe29b53e32f..232b120441bcb 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -30,6 +30,7 @@ Other enhancements ^^^^^^^^^^^^^^^^^^ - :class:`pandas.api.typing.FrozenList` is available for typing the outputs of :attr:`MultiIndex.names`, :attr:`MultiIndex.codes` and :attr:`MultiIndex.levels` (:issue:`58237`) - :class:`pandas.api.typing.SASReader` is available for typing the output of :func:`read_sas` (:issue:`55689`) +- :meth:`Series.map`, :meth:`DataFrame.map` and :meth:`Index.map` got a new ``skipna`` boolean parameter that replaces the existing ``na_action`` parameter (:issue:`61128`) - :meth:`pandas.api.interchange.from_dataframe` now uses the `PyCapsule Interface `_ if available, only falling back to the Dataframe Interchange Protocol if that fails (:issue:`60739`) - Added :meth:`.Styler.to_typst` to write Styler objects to file, buffer or string in Typst format (:issue:`57617`) - Added missing :meth:`pandas.Series.info` to API reference (:issue:`60926`) diff --git a/pandas/conftest.py b/pandas/conftest.py index f9c10a7758bd2..ef28f4b5c44b8 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -402,14 +402,6 @@ def nselect_method(request): return request.param -@pytest.fixture(params=[None, "ignore"]) -def na_action(request): - """ - Fixture for 'na_action' argument in map. - """ - return request.param - - @pytest.fixture(params=[True, False]) def ascending(request): """ diff --git a/pandas/core/algorithms.py b/pandas/core/algorithms.py index 7fc391d3ffb51..6e7872fe58388 100644 --- a/pandas/core/algorithms.py +++ b/pandas/core/algorithms.py @@ -1629,9 +1629,7 @@ def union_with_duplicates( def map_array( - arr: ArrayLike, - mapper, - na_action: Literal["ignore"] | None = None, + arr: ArrayLike, mapper, skipna: bool = False ) -> np.ndarray | ExtensionArray | Index: """ Map values using an input mapping or function. @@ -1640,8 +1638,8 @@ def map_array( ---------- mapper : function, dict, or Series Mapping correspondence. - na_action : {None, 'ignore'}, default None - If 'ignore', propagate NA values, without passing them to the + skipna : bool, default False + If ``True``, propagate NA values, without passing them to the mapping correspondence. Returns @@ -1653,10 +1651,6 @@ def map_array( """ from pandas import Index - if na_action not in (None, "ignore"): - msg = f"na_action must either be 'ignore' or None, {na_action} was passed" - raise ValueError(msg) - # we can fastpath dict/Series to an efficient map # as we know that we are not going to have to yield # python types @@ -1690,7 +1684,7 @@ def map_array( mapper = Series(mapper) if isinstance(mapper, ABCSeries): - if na_action == "ignore": + if skipna: mapper = mapper[mapper.index.notna()] # Since values were input this means we came from either @@ -1705,7 +1699,7 @@ def map_array( # we must convert to python types values = arr.astype(object, copy=False) - if na_action is None: + if not skipna: return lib.map_infer(values, mapper) else: return lib.map_infer_mask(values, mapper, mask=isna(values).view(np.uint8)) diff --git a/pandas/core/apply.py b/pandas/core/apply.py index 2c96f1ef020ac..5f5e77bad80ee 100644 --- a/pandas/core/apply.py +++ b/pandas/core/apply.py @@ -126,8 +126,7 @@ def map( executor the user wants to use. skip_na : bool Whether the function should be called for missing values or not. - This is specified by the pandas user as ``map(na_action=None)`` - or ``map(na_action='ignore')``. + This is specified by the pandas user as ``map(skipna=)``. """ @staticmethod diff --git a/pandas/core/arrays/arrow/array.py b/pandas/core/arrays/arrow/array.py index 0b90bcea35100..13023c65db7f5 100644 --- a/pandas/core/arrays/arrow/array.py +++ b/pandas/core/arrays/arrow/array.py @@ -1477,11 +1477,11 @@ def to_numpy( result[~mask] = data[~mask]._pa_array.to_numpy() return result - def map(self, mapper, na_action: Literal["ignore"] | None = None): + def map(self, mapper, skipna: bool = False): if is_numeric_dtype(self.dtype): - return map_array(self.to_numpy(), mapper, na_action=na_action) + return map_array(self.to_numpy(), mapper, skipna=skipna) else: - return super().map(mapper, na_action) + return super().map(mapper, skipna) @doc(ExtensionArray.duplicated) def duplicated( diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index d0048e122051a..fa808519533ca 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -2541,7 +2541,7 @@ def __array_ufunc__(self, ufunc: np.ufunc, method: str, *inputs, **kwargs): return arraylike.default_array_ufunc(self, ufunc, method, *inputs, **kwargs) - def map(self, mapper, na_action: Literal["ignore"] | None = None): + def map(self, mapper, skipna: bool = False): """ Map values using an input mapping or function. @@ -2549,9 +2549,9 @@ def map(self, mapper, na_action: Literal["ignore"] | None = None): ---------- mapper : function, dict, or Series Mapping correspondence. - na_action : {None, 'ignore'}, default None - If 'ignore', propagate NA values, without passing them to the - mapping correspondence. If 'ignore' is not supported, a + skipna : bool, default False + If ``True``, propagate NA values, without passing them to the + mapping correspondence. If ``True`` is not supported, a ``NotImplementedError`` should be raised. Returns @@ -2561,7 +2561,7 @@ def map(self, mapper, na_action: Literal["ignore"] | None = None): If the function returns a tuple with more than one element a MultiIndex will be returned. """ - return map_array(self, mapper, na_action=na_action) + return map_array(self, mapper, skipna=skipna) # ------------------------------------------------------------------------ # GroupBy Methods diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index df1aa21e9203c..e2829f7429a46 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -1497,7 +1497,7 @@ def remove_unused_categories(self) -> Self: def map( self, mapper, - na_action: Literal["ignore"] | None = None, + skipna: bool = False, ): """ Map categories using an input mapping or function. @@ -1515,8 +1515,8 @@ def map( ---------- mapper : function, dict, or Series Mapping correspondence. - na_action : {None, 'ignore'}, default None - If 'ignore', propagate NaN values, without passing them to the + skipna : bool, default False + If ``True``, propagate NA values, without passing them to the mapping correspondence. Returns @@ -1541,10 +1541,10 @@ def map( >>> cat ['a', 'b', 'c'] Categories (3, object): ['a', 'b', 'c'] - >>> cat.map(lambda x: x.upper(), na_action=None) + >>> cat.map(lambda x: x.upper(), skipna=False) ['A', 'B', 'C'] Categories (3, object): ['A', 'B', 'C'] - >>> cat.map({"a": "first", "b": "second", "c": "third"}, na_action=None) + >>> cat.map({"a": "first", "b": "second", "c": "third"}, skipna=False) ['first', 'second', 'third'] Categories (3, object): ['first', 'second', 'third'] @@ -1555,19 +1555,19 @@ def map( >>> cat ['a', 'b', 'c'] Categories (3, object): ['a' < 'b' < 'c'] - >>> cat.map({"a": 3, "b": 2, "c": 1}, na_action=None) + >>> cat.map({"a": 3, "b": 2, "c": 1}, skipna=False) [3, 2, 1] Categories (3, int64): [3 < 2 < 1] If the mapping is not one-to-one an :class:`~pandas.Index` is returned: - >>> cat.map({"a": "first", "b": "second", "c": "first"}, na_action=None) + >>> cat.map({"a": "first", "b": "second", "c": "first"}, skipna=False) Index(['first', 'second', 'first'], dtype='object') If a `dict` is used, all unmapped categories are mapped to `NaN` and the result is an :class:`~pandas.Index`: - >>> cat.map({"a": "first", "b": "second"}, na_action=None) + >>> cat.map({"a": "first", "b": "second"}, skipna=False) Index(['first', 'second', nan], dtype='object') """ assert callable(mapper) or is_dict_like(mapper) @@ -1577,7 +1577,7 @@ def map( has_nans = np.any(self._codes == -1) na_val = np.nan - if na_action is None and has_nans: + if not skipna and has_nans: na_val = mapper(np.nan) if callable(mapper) else mapper.get(np.nan, np.nan) if new_categories.is_unique and not new_categories.hasnans and na_val is np.nan: diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 994d7b1d0081c..32fe6d822e1a8 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -743,10 +743,10 @@ def _unbox(self, other) -> np.int64 | np.datetime64 | np.timedelta64 | np.ndarra # pandas assumes they're there. @ravel_compat - def map(self, mapper, na_action: Literal["ignore"] | None = None): + def map(self, mapper, skipna: bool = False): from pandas import Index - result = map_array(self, mapper, na_action=na_action) + result = map_array(self, mapper, skipna=skipna) result = Index(result) if isinstance(result, ABCMultiIndex): diff --git a/pandas/core/arrays/masked.py b/pandas/core/arrays/masked.py index e7a6b207363c3..fcd99dfe7675f 100644 --- a/pandas/core/arrays/masked.py +++ b/pandas/core/arrays/masked.py @@ -1324,8 +1324,8 @@ def max(self, *, skipna: bool = True, axis: AxisInt | None = 0, **kwargs): ) return self._wrap_reduction_result("max", result, skipna=skipna, axis=axis) - def map(self, mapper, na_action: Literal["ignore"] | None = None): - return map_array(self.to_numpy(), mapper, na_action=na_action) + def map(self, mapper, skipna: bool = False): + return map_array(self.to_numpy(), mapper, skipna=skipna) @overload def any( diff --git a/pandas/core/arrays/sparse/array.py b/pandas/core/arrays/sparse/array.py index 137dbb6e4d139..2c4b9b5e3281e 100644 --- a/pandas/core/arrays/sparse/array.py +++ b/pandas/core/arrays/sparse/array.py @@ -1309,7 +1309,7 @@ def astype(self, dtype: AstypeArg | None = None, copy: bool = True): return self._simple_new(sp_values, self.sp_index, dtype) - def map(self, mapper, na_action: Literal["ignore"] | None = None) -> Self: + def map(self, mapper, skipna: bool = False) -> Self: """ Map categories using an input mapping or function. @@ -1317,8 +1317,8 @@ def map(self, mapper, na_action: Literal["ignore"] | None = None) -> Self: ---------- mapper : dict, Series, callable The correspondence from old values to new. - na_action : {None, 'ignore'}, default None - If 'ignore', propagate NA values, without passing them to the + skipna : bool, default False + If ``True``, propagate NA values, without passing them to the mapping correspondence. Returns @@ -1353,7 +1353,7 @@ def map(self, mapper, na_action: Literal["ignore"] | None = None) -> Self: fill_val = self.fill_value - if na_action is None or notna(fill_val): + if not skipna or notna(fill_val): fill_val = mapper.get(fill_val, fill_val) if is_map else mapper(fill_val) def func(sp_val): diff --git a/pandas/core/base.py b/pandas/core/base.py index 6cc28d4e46634..3d481e0e8bb3d 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -945,7 +945,7 @@ def hasnans(self) -> bool: return bool(isna(self).any()) # type: ignore[union-attr] @final - def _map_values(self, mapper, na_action=None): + def _map_values(self, mapper, skipna=False): """ An internal function that maps values using the input correspondence (which can be a dict, Series, or function). @@ -954,8 +954,8 @@ def _map_values(self, mapper, na_action=None): ---------- mapper : function, dict, or Series The input correspondence object - na_action : {None, 'ignore'} - If 'ignore', propagate NA values, without passing them to the + skipna : bool, default False + If ``True``, propagate NA values, without passing them to the mapping function Returns @@ -968,9 +968,9 @@ def _map_values(self, mapper, na_action=None): arr = self._values if isinstance(arr, ExtensionArray): - return arr.map(mapper, na_action=na_action) + return arr.map(mapper, skipna=skipna) - return algorithms.map_array(arr, mapper, na_action=na_action) + return algorithms.map_array(arr, mapper, skipna=skipna) def value_counts( self, diff --git a/pandas/core/frame.py b/pandas/core/frame.py index bf919c6fe8a42..14b8fede53e56 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -10641,7 +10641,11 @@ def apply( raise ValueError(f"Unknown engine {engine}") def map( - self, func: PythonFuncType, na_action: Literal["ignore"] | None = None, **kwargs + self, + func: PythonFuncType, + na_action: Literal["ignore"] | None | lib.NoDefault = lib.no_default, + skipna: bool = False, + **kwargs, ) -> DataFrame: """ Apply a function to a Dataframe elementwise. @@ -10659,6 +10663,11 @@ def map( Python function, returns a single value from a single value. na_action : {None, 'ignore'}, default None If 'ignore', propagate NaN values, without passing them to func. + + .. deprecated:: 3.0.0 + Use ``skipna`` instead. + skipna : bool = False + If ``True``, propagate missing values without passing them to ``func``. **kwargs Additional keyword arguments to pass as keywords arguments to `func`. @@ -10691,7 +10700,7 @@ def map( >>> df_copy = df.copy() >>> df_copy.iloc[0, 0] = pd.NA - >>> df_copy.map(lambda x: len(str(x)), na_action="ignore") + >>> df_copy.map(lambda x: len(str(x)), skipna=True) 0 1 0 NaN 4 1 5.0 5 @@ -10719,8 +10728,20 @@ def map( 0 1.000000 4.494400 1 11.262736 20.857489 """ - if na_action not in {"ignore", None}: - raise ValueError(f"na_action must be 'ignore' or None. Got {na_action!r}") + if na_action != lib.no_default: + warnings.warn( + "The ``na_action`` parameter has been deprecated and it will be " + "removed in a future version of pandas. Use ``skipna`` instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + if na_action == "ignore": + skipna = True + elif na_action not in (None, "ignore"): + raise ValueError( + "na_action must either be 'ignore' or None, " + f"{na_action!r} was passed" + ) if self.empty: return self.copy() @@ -10728,7 +10749,7 @@ def map( func = functools.partial(func, **kwargs) def infer(x): - return x._map_values(func, na_action=na_action) + return x._map_values(func, skipna=skipna) return self.apply(infer).__finalize__(self, "map") diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 4e1ea07907cdb..10aef25bd4c15 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -6350,7 +6350,12 @@ def groupby(self, values) -> PrettyDict[Hashable, Index]: return PrettyDict(result) - def map(self, mapper, na_action: Literal["ignore"] | None = None): + def map( + self, + mapper, + na_action: Literal["ignore"] | None | lib.NoDefault = lib.no_default, + skipna: bool = False, + ): """ Map values using an input mapping or function. @@ -6362,6 +6367,12 @@ def map(self, mapper, na_action: Literal["ignore"] | None = None): If 'ignore', propagate NA values, without passing them to the mapping correspondence. + .. deprecated:: 3.0.0 + Use ``skipna`` instead. + skipna : bool = False + If ``True``, propagate NA values, without passing them to the + mapping correspondence. + Returns ------- Union[Index, MultiIndex] @@ -6391,7 +6402,22 @@ def map(self, mapper, na_action: Literal["ignore"] | None = None): """ from pandas.core.indexes.multi import MultiIndex - new_values = self._map_values(mapper, na_action=na_action) + if na_action != lib.no_default: + warnings.warn( + "The ``na_action`` parameter has been deprecated and it will be " + "removed in a future version of pandas. Use ``skipna`` instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + if na_action == "ignore": + skipna = True + elif na_action not in (None, "ignore"): + raise ValueError( + "na_action must either be 'ignore' or None, " + f"{na_action!r} was passed" + ) + + new_values = self._map_values(mapper, skipna=skipna) # we can return a MultiIndex if new_values.size and isinstance(new_values[0], tuple): diff --git a/pandas/core/indexes/category.py b/pandas/core/indexes/category.py index d20a84449fb85..9ef341a242059 100644 --- a/pandas/core/indexes/category.py +++ b/pandas/core/indexes/category.py @@ -6,15 +6,22 @@ Literal, cast, ) +import warnings import numpy as np -from pandas._libs import index as libindex +from pandas._libs import ( + index as libindex, + lib, +) from pandas.util._decorators import ( cache_readonly, doc, set_module, ) +from pandas.util._exceptions import ( + find_stack_level, +) from pandas.core.dtypes.common import is_scalar from pandas.core.dtypes.concat import concat_compat @@ -448,7 +455,12 @@ def _maybe_cast_listlike_indexer(self, values) -> CategoricalIndex: def _is_comparable_dtype(self, dtype: DtypeObj) -> bool: return self.categories._is_comparable_dtype(dtype) - def map(self, mapper, na_action: Literal["ignore"] | None = None): + def map( + self, + mapper, + na_action: Literal["ignore"] | None | lib.NoDefault = lib.no_default, + skipna: bool = False, + ): """ Map values using input an input mapping or function. @@ -469,6 +481,12 @@ def map(self, mapper, na_action: Literal["ignore"] | None = None): If 'ignore', propagate NaN values, without passing them to the mapping correspondence. + .. deprecated:: 3.0.0 + Use ``skipna`` instead. + skipna : bool, default False + If ``True``, propagate NaN values, without passing them to + the mapping correspondence. + Returns ------- pandas.CategoricalIndex or pandas.Index @@ -518,7 +536,22 @@ def map(self, mapper, na_action: Literal["ignore"] | None = None): >>> idx.map({"a": "first", "b": "second"}) Index(['first', 'second', nan], dtype='object') """ - mapped = self._values.map(mapper, na_action=na_action) + if na_action != lib.no_default: + warnings.warn( + "The ``na_action`` parameter has been deprecated and it will be " + "removed in a future version of pandas. Use ``skipna`` instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + if na_action == "ignore": + skipna = True + elif na_action not in (None, "ignore"): + raise ValueError( + "na_action must either be 'ignore' or None, " + f"{na_action!r} was passed" + ) + + mapped = self._values.map(mapper, skipna=skipna) return Index(mapped, name=self.name) def _concat(self, to_concat: list[Index], name: Hashable) -> Index: diff --git a/pandas/core/series.py b/pandas/core/series.py index 7a26be875e7b5..1acf31e79d2dd 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -4327,7 +4327,8 @@ def unstack( def map( self, func: Callable | Mapping | Series | None = None, - na_action: Literal["ignore"] | None = None, + na_action: Literal["ignore"] | None | lib.NoDefault = lib.no_default, + skipna: bool = False, engine: Callable | None = None, **kwargs, ) -> Series: @@ -4345,6 +4346,12 @@ def map( na_action : {None, 'ignore'}, default None If 'ignore', propagate NaN values, without passing them to the mapping correspondence. + + .. deprecated:: 3.0.0 + Use ``skipna`` instead. + skipna : bool, default False + If ``True``, do not pass missing values to the function, and + propagate them to the result directly instead. engine : decorator, optional Choose the execution engine to use to run the function. Only used for functions. If ``map`` is called with a mapping or ``Series``, an @@ -4421,15 +4428,30 @@ def map( dtype: object To avoid applying the function to missing values (and keep them as - ``NaN``) ``na_action='ignore'`` can be used: + ``NaN``) ``skipna=True`` can be used: - >>> s.map("I am a {}".format, na_action="ignore") + >>> s.map("I am a {}".format, skipna=True) 0 I am a cat 1 I am a dog 2 NaN 3 I am a rabbit dtype: object """ + if na_action != lib.no_default: + warnings.warn( + "The ``na_action`` parameter has been deprecated and it will be " + "removed in a future version of pandas. Use ``skipna`` instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + if na_action == "ignore": + skipna = True + elif na_action not in (None, "ignore"): + raise ValueError( + "na_action must either be 'ignore' or None, " + f"{na_action!r} was passed" + ) + if func is None: if "arg" in kwargs: # `.map(arg=my_func)` @@ -4464,7 +4486,7 @@ def map( if callable(func): func = functools.partial(func, **kwargs) - new_values = self._map_values(func, na_action=na_action) + new_values = self._map_values(func, skipna=skipna) return self._constructor(new_values, index=self.index, copy=False).__finalize__( self, method="map" ) diff --git a/pandas/tests/apply/conftest.py b/pandas/tests/apply/conftest.py index aecf82f5a9419..b70bfb1eb0546 100644 --- a/pandas/tests/apply/conftest.py +++ b/pandas/tests/apply/conftest.py @@ -23,7 +23,7 @@ class MockExecutionEngine(BaseExecutionEngine): def map(data, func, args, kwargs, decorator, skip_na): kwargs_to_pass = kwargs if isinstance(data, DataFrame) else {} - return data.map(func, na_action="ignore" if skip_na else None, **kwargs_to_pass) + return data.map(func, skipna=skip_na, **kwargs_to_pass) def apply(data, func, args, kwargs, decorator, axis): if isinstance(data, Series): diff --git a/pandas/tests/apply/test_invalid_arg.py b/pandas/tests/apply/test_invalid_arg.py index 0503bf9166ec7..8c2be672fd310 100644 --- a/pandas/tests/apply/test_invalid_arg.py +++ b/pandas/tests/apply/test_invalid_arg.py @@ -54,23 +54,6 @@ def test_agg_raises(): df.agg() -def test_map_with_invalid_na_action_raises(): - # https://github.com/pandas-dev/pandas/issues/32815 - s = Series([1, 2, 3]) - msg = "na_action must either be 'ignore' or None" - with pytest.raises(ValueError, match=msg): - s.map(lambda x: x, na_action="____") - - -@pytest.mark.parametrize("input_na_action", ["____", True]) -def test_map_arg_is_dict_with_invalid_na_action_raises(input_na_action): - # https://github.com/pandas-dev/pandas/issues/46588 - s = Series([1, 2, 3]) - msg = f"na_action must either be 'ignore' or None, {input_na_action} was passed" - with pytest.raises(ValueError, match=msg): - s.map({1: 2}, na_action=input_na_action) - - @pytest.mark.parametrize("method", ["apply", "agg", "transform"]) @pytest.mark.parametrize("func", [{"A": {"B": "sum"}}, {"A": {"B": ["sum"]}}]) def test_nested_renamer(frame_or_series, method, func): diff --git a/pandas/tests/arrays/categorical/test_analytics.py b/pandas/tests/arrays/categorical/test_analytics.py index 47fa354e12393..c9847e3b5c9d3 100644 --- a/pandas/tests/arrays/categorical/test_analytics.py +++ b/pandas/tests/arrays/categorical/test_analytics.py @@ -318,16 +318,16 @@ def test_memory_usage(self, using_infer_string): def test_map(self): c = Categorical(list("ABABC"), categories=list("CBA"), ordered=True) - result = c.map(lambda x: x.lower(), na_action=None) + result = c.map(lambda x: x.lower(), skipna=False) exp = Categorical(list("ababc"), categories=list("cba"), ordered=True) tm.assert_categorical_equal(result, exp) c = Categorical(list("ABABC"), categories=list("ABC"), ordered=False) - result = c.map(lambda x: x.lower(), na_action=None) + result = c.map(lambda x: x.lower(), skipna=False) exp = Categorical(list("ababc"), categories=list("abc"), ordered=False) tm.assert_categorical_equal(result, exp) - result = c.map(lambda x: 1, na_action=None) + result = c.map(lambda x: 1, skipna=False) # GH 12766: Return an index not an array tm.assert_index_equal(result, Index(np.array([1] * 5, dtype=np.int64))) diff --git a/pandas/tests/arrays/categorical/test_map.py b/pandas/tests/arrays/categorical/test_map.py index 585b207c9b241..0159a1d8e19ac 100644 --- a/pandas/tests/arrays/categorical/test_map.py +++ b/pandas/tests/arrays/categorical/test_map.py @@ -18,29 +18,29 @@ ], ids=["string", "interval"], ) -def test_map_str(data, categories, ordered, na_action): +def test_map_str(data, categories, ordered, skipna): # GH 31202 - override base class since we want to maintain categorical/ordered cat = Categorical(data, categories=categories, ordered=ordered) - result = cat.map(str, na_action=na_action) + result = cat.map(str, skipna=skipna) expected = Categorical( map(str, data), categories=map(str, categories), ordered=ordered ) tm.assert_categorical_equal(result, expected) -def test_map(na_action): +def test_map(skipna): cat = Categorical(list("ABABC"), categories=list("CBA"), ordered=True) - result = cat.map(lambda x: x.lower(), na_action=na_action) + result = cat.map(lambda x: x.lower(), skipna=skipna) exp = Categorical(list("ababc"), categories=list("cba"), ordered=True) tm.assert_categorical_equal(result, exp) cat = Categorical(list("ABABC"), categories=list("BAC"), ordered=False) - result = cat.map(lambda x: x.lower(), na_action=na_action) + result = cat.map(lambda x: x.lower(), skipna=skipna) exp = Categorical(list("ababc"), categories=list("bac"), ordered=False) tm.assert_categorical_equal(result, exp) # GH 12766: Return an index not an array - result = cat.map(lambda x: 1, na_action=na_action) + result = cat.map(lambda x: 1, skipna=skipna) exp = Index(np.array([1] * 5, dtype=np.int64)) tm.assert_index_equal(result, exp) @@ -50,15 +50,15 @@ def test_map(na_action): def f(x): return {"A": 10, "B": 20, "C": 30}.get(x) - result = cat.map(f, na_action=na_action) + result = cat.map(f, skipna=skipna) exp = Categorical([10, 20, 10, 20, 30], categories=[20, 10, 30], ordered=False) tm.assert_categorical_equal(result, exp) mapper = Series([10, 20, 30], index=["A", "B", "C"]) - result = cat.map(mapper, na_action=na_action) + result = cat.map(mapper, skipna=skipna) tm.assert_categorical_equal(result, exp) - result = cat.map({"A": 10, "B": 20, "C": 30}, na_action=na_action) + result = cat.map({"A": 10, "B": 20, "C": 30}, skipna=skipna) tm.assert_categorical_equal(result, exp) @@ -83,7 +83,7 @@ def f(x): ) def test_map_with_nan_none(data, f, expected): # GH 24241 values = Categorical(data) - result = values.map(f, na_action=None) + result = values.map(f, skipna=False) if isinstance(expected, Categorical): tm.assert_categorical_equal(result, expected) else: @@ -111,26 +111,26 @@ def test_map_with_nan_none(data, f, expected): # GH 24241 ) def test_map_with_nan_ignore(data, f, expected): # GH 24241 values = Categorical(data) - result = values.map(f, na_action="ignore") + result = values.map(f, skipna=True) if data[1] == 1: tm.assert_categorical_equal(result, expected) else: tm.assert_index_equal(result, expected) -def test_map_with_dict_or_series(na_action): +def test_map_with_dict_or_series(skipna): orig_values = ["a", "B", 1, "a"] new_values = ["one", 2, 3.0, "one"] cat = Categorical(orig_values) mapper = Series(new_values[:-1], index=orig_values[:-1]) - result = cat.map(mapper, na_action=na_action) + result = cat.map(mapper, skipna=skipna) # Order of categories in result can be different expected = Categorical(new_values, categories=[3.0, 2, "one"]) tm.assert_categorical_equal(result, expected) mapper = dict(zip(orig_values[:-1], new_values[:-1])) - result = cat.map(mapper, na_action=na_action) + result = cat.map(mapper, skipna=skipna) # Order of categories in result can be different tm.assert_categorical_equal(result, expected) diff --git a/pandas/tests/arrays/categorical/test_subclass.py b/pandas/tests/arrays/categorical/test_subclass.py index 5b0c0a44e655d..585b2f9591ee6 100644 --- a/pandas/tests/arrays/categorical/test_subclass.py +++ b/pandas/tests/arrays/categorical/test_subclass.py @@ -20,7 +20,7 @@ def test_from_codes(self): def test_map(self): sc = SubclassedCategorical(["a", "b", "c"]) - res = sc.map(lambda x: x.upper(), na_action=None) + res = sc.map(lambda x: x.upper(), skipna=False) assert isinstance(res, SubclassedCategorical) exp = Categorical(["A", "B", "C"]) tm.assert_categorical_equal(res, exp) diff --git a/pandas/tests/extension/base/methods.py b/pandas/tests/extension/base/methods.py index fd9fec0cb490c..dd268f0061170 100644 --- a/pandas/tests/extension/base/methods.py +++ b/pandas/tests/extension/base/methods.py @@ -97,9 +97,9 @@ def test_apply_simple_series(self, data): result = pd.Series(data).apply(id) assert isinstance(result, pd.Series) - @pytest.mark.parametrize("na_action", [None, "ignore"]) - def test_map(self, data_missing, na_action): - result = data_missing.map(lambda x: x, na_action=na_action) + @pytest.mark.parametrize("skipna", [False, True]) + def test_map(self, data_missing, skipna): + result = data_missing.map(lambda x: x, skipna=skipna) expected = data_missing.to_numpy() tm.assert_numpy_array_equal(result, expected) diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index fc5930ebcd8ac..18362e2e30c64 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -278,14 +278,14 @@ def test_compare_scalar(self, data, comparison_op): ser = pd.Series(data) self._compare_other(ser, data, comparison_op, data[0]) - @pytest.mark.parametrize("na_action", [None, "ignore"]) - def test_map(self, data_missing, na_action): + @pytest.mark.parametrize("skipna", [True, False]) + def test_map(self, data_missing, skipna): if data_missing.dtype.kind in "mM": - result = data_missing.map(lambda x: x, na_action=na_action) + result = data_missing.map(lambda x: x, skipna=skipna) expected = data_missing.to_numpy(dtype=object) tm.assert_numpy_array_equal(result, expected) else: - result = data_missing.map(lambda x: x, na_action=na_action) + result = data_missing.map(lambda x: x, skipna=skipna) if data_missing.dtype == "float32[pyarrow]": # map roundtrips through objects, which converts to float64 expected = data_missing.to_numpy(dtype="float64", na_value=np.nan) @@ -3532,9 +3532,9 @@ def test_cast_dictionary_different_value_dtype(arrow_type): assert result.dtypes.iloc[0] == data_type -def test_map_numeric_na_action(): +def test_map_numeric_skipna(): ser = pd.Series([32, 40, None], dtype="int64[pyarrow]") - result = ser.map(lambda x: 42, na_action="ignore") + result = ser.map(lambda x: 42, skipna=True) expected = pd.Series([42.0, 42.0, np.nan], dtype="float64") tm.assert_series_equal(result, expected) diff --git a/pandas/tests/extension/test_categorical.py b/pandas/tests/extension/test_categorical.py index 8f8af607585df..8940732612c00 100644 --- a/pandas/tests/extension/test_categorical.py +++ b/pandas/tests/extension/test_categorical.py @@ -135,9 +135,9 @@ def test_combine_add(self, data_repeated): expected = pd.Series([a + val for a in list(orig_data1)]) tm.assert_series_equal(result, expected) - @pytest.mark.parametrize("na_action", [None, "ignore"]) - def test_map(self, data, na_action): - result = data.map(lambda x: x, na_action=na_action) + @pytest.mark.parametrize("skipna", [False, True]) + def test_map(self, data, skipna): + result = data.map(lambda x: x, skipna=skipna) tm.assert_extension_array_equal(result, data) def test_arith_frame_with_scalar(self, data, all_arithmetic_operators, request): diff --git a/pandas/tests/extension/test_datetime.py b/pandas/tests/extension/test_datetime.py index 356d5352f41f4..250c7c9b21b2c 100644 --- a/pandas/tests/extension/test_datetime.py +++ b/pandas/tests/extension/test_datetime.py @@ -118,9 +118,9 @@ def test_series_constructor(self, data): data = data._with_freq(None) super().test_series_constructor(data) - @pytest.mark.parametrize("na_action", [None, "ignore"]) - def test_map(self, data, na_action): - result = data.map(lambda x: x, na_action=na_action) + @pytest.mark.parametrize("skipna", [False, True]) + def test_map(self, data, skipna): + result = data.map(lambda x: x, skipna=skipna) tm.assert_extension_array_equal(result, data) def check_reduce(self, ser: pd.Series, op_name: str, skipna: bool): diff --git a/pandas/tests/extension/test_masked.py b/pandas/tests/extension/test_masked.py index 3b9079d06e231..fda45d5680738 100644 --- a/pandas/tests/extension/test_masked.py +++ b/pandas/tests/extension/test_masked.py @@ -168,9 +168,9 @@ def data_for_grouping(dtype): class TestMaskedArrays(base.ExtensionTests): - @pytest.mark.parametrize("na_action", [None, "ignore"]) - def test_map(self, data_missing, na_action): - result = data_missing.map(lambda x: x, na_action=na_action) + @pytest.mark.parametrize("skipna", [False, True]) + def test_map(self, data_missing, skipna): + result = data_missing.map(lambda x: x, skipna=skipna) if data_missing.dtype == Float32Dtype(): # map roundtrips through objects, which converts to float64 expected = data_missing.to_numpy(dtype="float64", na_value=np.nan) @@ -178,9 +178,9 @@ def test_map(self, data_missing, na_action): expected = data_missing.to_numpy() tm.assert_numpy_array_equal(result, expected) - def test_map_na_action_ignore(self, data_missing_for_sorting): + def test_map_skipna(self, data_missing_for_sorting): zero = data_missing_for_sorting[2] - result = data_missing_for_sorting.map(lambda x: zero, na_action="ignore") + result = data_missing_for_sorting.map(lambda x: zero, skipna=True) if data_missing_for_sorting.dtype.kind == "b": expected = np.array([False, pd.NA, False], dtype=object) else: diff --git a/pandas/tests/extension/test_period.py b/pandas/tests/extension/test_period.py index 142bad6db4f95..4522dcb42d37a 100644 --- a/pandas/tests/extension/test_period.py +++ b/pandas/tests/extension/test_period.py @@ -110,9 +110,9 @@ def test_diff(self, data, periods): else: super().test_diff(data, periods) - @pytest.mark.parametrize("na_action", [None, "ignore"]) - def test_map(self, data, na_action): - result = data.map(lambda x: x, na_action=na_action) + @pytest.mark.parametrize("skipna", [False, True]) + def test_map(self, data, skipna): + result = data.map(lambda x: x, skipna=skipna) tm.assert_extension_array_equal(result, data) diff --git a/pandas/tests/extension/test_sparse.py b/pandas/tests/extension/test_sparse.py index b7685a61d4937..5074c3b2b1997 100644 --- a/pandas/tests/extension/test_sparse.py +++ b/pandas/tests/extension/test_sparse.py @@ -351,26 +351,26 @@ def test_equals_same_data_different_object(self, data): super().test_equals_same_data_different_object(data) @pytest.mark.parametrize( - "func, na_action, expected", + "func, skipna, expected", [ - (lambda x: x, None, SparseArray([1.0, np.nan])), - (lambda x: x, "ignore", SparseArray([1.0, np.nan])), - (str, None, SparseArray(["1.0", "nan"], fill_value="nan")), - (str, "ignore", SparseArray(["1.0", np.nan])), + (lambda x: x, False, SparseArray([1.0, np.nan])), + (lambda x: x, True, SparseArray([1.0, np.nan])), + (str, False, SparseArray(["1.0", "nan"], fill_value="nan")), + (str, True, SparseArray(["1.0", np.nan])), ], ) - def test_map(self, func, na_action, expected): + def test_map(self, func, skipna, expected): # GH52096 data = SparseArray([1, np.nan]) - result = data.map(func, na_action=na_action) + result = data.map(func, skipna=skipna) tm.assert_extension_array_equal(result, expected) - @pytest.mark.parametrize("na_action", [None, "ignore"]) - def test_map_raises(self, data, na_action): + @pytest.mark.parametrize("skipna", [False, True]) + def test_map_raises(self, data, skipna): # GH52096 msg = "fill value in the sparse values not supported" with pytest.raises(ValueError, match=msg): - data.map(lambda x: np.nan, na_action=na_action) + data.map(lambda x: np.nan, skipna=skipna) @pytest.mark.xfail(raises=TypeError, reason="no sparse StringDtype") def test_astype_string(self, data, nullable_string_dtype): diff --git a/pandas/tests/frame/methods/test_map.py b/pandas/tests/frame/methods/test_map.py index 9850de14b2092..1da737122b454 100644 --- a/pandas/tests/frame/methods/test_map.py +++ b/pandas/tests/frame/methods/test_map.py @@ -33,7 +33,7 @@ def test_map_float_object_conversion(val): assert result == object -def test_map_keeps_dtype(na_action): +def test_map_keeps_dtype(skipna): # GH52219 arr = Series(["a", np.nan, "b"]) sparse_arr = arr.astype(pd.SparseDtype(object)) @@ -42,7 +42,7 @@ def test_map_keeps_dtype(na_action): def func(x): return str.upper(x) if not pd.isna(x) else x - result = df.map(func, na_action=na_action) + result = df.map(func, skipna=skipna) expected_sparse = pd.array(["A", np.nan, "B"], dtype=pd.SparseDtype(object)) expected_arr = expected_sparse.astype(object) @@ -50,7 +50,7 @@ def func(x): tm.assert_frame_equal(result, expected) - result_empty = df.iloc[:0, :].map(func, na_action=na_action) + result_empty = df.iloc[:0, :].map(func, skipna=skipna) expected_empty = expected.iloc[:0, :] tm.assert_frame_equal(result_empty, expected_empty) @@ -109,9 +109,7 @@ def test_map_na_ignore(float_frame): float_frame_with_na = float_frame.copy() mask = np.random.default_rng(2).integers(0, 2, size=float_frame.shape, dtype=bool) float_frame_with_na[mask] = pd.NA - strlen_frame_na_ignore = float_frame_with_na.map( - lambda x: len(str(x)), na_action="ignore" - ) + strlen_frame_na_ignore = float_frame_with_na.map(lambda x: len(str(x)), skipna=True) # Set float64 type to avoid upcast when setting NA below strlen_frame_with_na = strlen_frame.copy().astype("float64") strlen_frame_with_na[mask] = pd.NA @@ -202,7 +200,28 @@ def test_map_type(): tm.assert_frame_equal(result, expected) +def test_map_na_action_none(float_frame): + with tm.assert_produces_warning( + FutureWarning, match="``na_action`` parameter has been deprecated" + ): + result = DataFrame({"a": [1, np.nan]}).map(lambda x: 10, na_action=None) + expected = DataFrame({"a": [10, 10]}) + tm.assert_frame_equal(result, expected) + + +def test_map_na_action_ignore(float_frame): + with tm.assert_produces_warning( + FutureWarning, match="``na_action`` parameter has been deprecated" + ): + result = DataFrame({"a": [1, np.nan]}).map(lambda x: 10, na_action="ignore") + expected = DataFrame({"a": [10, np.nan]}) + tm.assert_frame_equal(result, expected) + + def test_map_invalid_na_action(float_frame): # GH 23803 - with pytest.raises(ValueError, match="na_action must be .*Got 'abc'"): - float_frame.map(lambda x: len(str(x)), na_action="abc") + with tm.assert_produces_warning( + FutureWarning, match="``na_action`` parameter has been deprecated" + ): + with pytest.raises(ValueError, match="na_action must .* 'abc' was passed"): + float_frame.map(lambda x: len(str(x)), na_action="abc") diff --git a/pandas/tests/indexes/categorical/test_map.py b/pandas/tests/indexes/categorical/test_map.py index baf836594dfb5..68e9c16f29b0b 100644 --- a/pandas/tests/indexes/categorical/test_map.py +++ b/pandas/tests/indexes/categorical/test_map.py @@ -98,7 +98,7 @@ def test_map_with_categorical_series(): ) def test_map_with_nan_ignore(data, f, expected): # GH 24241 values = CategoricalIndex(data) - result = values.map(f, na_action="ignore") + result = values.map(f, skipna=True) tm.assert_index_equal(result, expected) @@ -123,7 +123,7 @@ def test_map_with_nan_ignore(data, f, expected): # GH 24241 ) def test_map_with_nan_none(data, f, expected): # GH 24241 values = CategoricalIndex(data) - result = values.map(f, na_action=None) + result = values.map(f, skipna=False) tm.assert_index_equal(result, expected) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 5b75bd9afd6df..3072769b3c123 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -617,7 +617,7 @@ def test_map_with_non_function_missing_values(self, mapper): def test_map_na_exclusion(self): index = Index([1.5, np.nan, 3, np.nan, 5]) - result = index.map(lambda x: x * 2, na_action="ignore") + result = index.map(lambda x: x * 2, skipna=True) expected = index * 2 tm.assert_index_equal(result, expected) @@ -1717,3 +1717,29 @@ def test_is_monotonic_pyarrow_list_type(): idx = Index([[1], [2, 3]], dtype=pd.ArrowDtype(pa.list_(pa.int64()))) assert not idx.is_monotonic_increasing assert not idx.is_monotonic_decreasing + + +def test_map_na_action_none(): + with tm.assert_produces_warning( + FutureWarning, match="``na_action`` parameter has been deprecated" + ): + result = Index([1, np.nan]).map(lambda x: 10, na_action=None) + expected = Index([10, 10]) + tm.assert_index_equal(result, expected) + + +def test_map_na_action_ignore(): + with tm.assert_produces_warning( + FutureWarning, match="``na_action`` parameter has been deprecated" + ): + result = Index([1, np.nan]).map(lambda x: 10, na_action="ignore") + expected = Index([10, np.nan]) + tm.assert_index_equal(result, expected) + + +def test_map_invalid_na_action(): + with tm.assert_produces_warning( + FutureWarning, match="``na_action`` parameter has been deprecated" + ): + with pytest.raises(ValueError, match="na_action must .* 'abc' was passed"): + Index([1, 2]).map(lambda x: x, na_action="abc") diff --git a/pandas/tests/series/methods/test_map.py b/pandas/tests/series/methods/test_map.py index 0ec973dea23d5..0502edce3d16b 100644 --- a/pandas/tests/series/methods/test_map.py +++ b/pandas/tests/series/methods/test_map.py @@ -125,7 +125,7 @@ def func(val): s = Series(data, dtype="category") - result = s.map(func, na_action="ignore") + result = s.map(func, skipna=True) expected = Series(["1", "1", np.nan], dtype=expected_dtype) tm.assert_series_equal(result, expected) @@ -280,7 +280,7 @@ def test_map_decimal(string_series): def test_map_na_exclusion(engine): # noqa: F811 s = Series([1.5, np.nan, 3, np.nan, 5]) - result = s.map(lambda x: x * 2, na_action="ignore", engine=engine) + result = s.map(lambda x: x * 2, skipna=True, engine=engine) exp = s * 2 tm.assert_series_equal(result, exp) @@ -330,30 +330,30 @@ def test_map_dict_na_key(): tm.assert_series_equal(result, expected) -def test_map_defaultdict_na_key(na_action): +def test_map_defaultdict_na_key(skipna): # GH 48813 s = Series([1, 2, np.nan]) default_map = defaultdict(lambda: "missing", {1: "a", 2: "b", np.nan: "c"}) - result = s.map(default_map, na_action=na_action) - expected = Series({0: "a", 1: "b", 2: "c" if na_action is None else np.nan}) + result = s.map(default_map, skipna=skipna) + expected = Series({0: "a", 1: "b", 2: "c" if not skipna else np.nan}) tm.assert_series_equal(result, expected) -def test_map_defaultdict_missing_key(na_action): +def test_map_defaultdict_missing_key(skipna): # GH 48813 s = Series([1, 2, np.nan]) default_map = defaultdict(lambda: "missing", {1: "a", 2: "b", 3: "c"}) - result = s.map(default_map, na_action=na_action) - expected = Series({0: "a", 1: "b", 2: "missing" if na_action is None else np.nan}) + result = s.map(default_map, skipna=skipna) + expected = Series({0: "a", 1: "b", 2: "missing" if not skipna else np.nan}) tm.assert_series_equal(result, expected) -def test_map_defaultdict_unmutated(na_action): +def test_map_defaultdict_unmutated(skipna): # GH 48813 s = Series([1, 2, np.nan]) default_map = defaultdict(lambda: "missing", {1: "a", 2: "b", np.nan: "c"}) expected_default_map = default_map.copy() - s.map(default_map, na_action=na_action) + s.map(default_map, skipna=skipna) assert default_map == expected_default_map @@ -362,7 +362,7 @@ def test_map_dict_ignore_na(arg_func): # GH#47527 mapping = arg_func({1: 10, np.nan: 42}) ser = Series([1, np.nan, 2]) - result = ser.map(mapping, na_action="ignore") + result = ser.map(mapping, skipna=True) expected = Series([10, np.nan, np.nan]) tm.assert_series_equal(result, expected) @@ -377,14 +377,14 @@ def test_map_defaultdict_ignore_na(): @pytest.mark.parametrize( - "na_action, expected", - [(None, Series([10.0, 42.0, np.nan])), ("ignore", Series([10, np.nan, np.nan]))], + "skipna, expected", + [(False, Series([10.0, 42.0, np.nan])), (True, Series([10, np.nan, np.nan]))], ) -def test_map_categorical_na_ignore(na_action, expected): +def test_map_categorical_na_ignore(skipna, expected): # GH#47527 values = pd.Categorical([1, np.nan, 2], categories=[10, 1, 2]) ser = Series(values) - result = ser.map({1: 10, np.nan: 42}, na_action=na_action) + result = ser.map({1: 10, np.nan: 42}, skipna=skipna) tm.assert_series_equal(result, expected) @@ -484,28 +484,28 @@ def test_map_box_period(): tm.assert_series_equal(res, exp) -def test_map_categorical(na_action, using_infer_string): +def test_map_categorical(skipna, using_infer_string): values = pd.Categorical(list("ABBABCD"), categories=list("DCBA"), ordered=True) s = Series(values, name="XX", index=list("abcdefg")) - result = s.map(lambda x: x.lower(), na_action=na_action) + result = s.map(lambda x: x.lower(), skipna=skipna) exp_values = pd.Categorical(list("abbabcd"), categories=list("dcba"), ordered=True) exp = Series(exp_values, name="XX", index=list("abcdefg")) tm.assert_series_equal(result, exp) tm.assert_categorical_equal(result.values, exp_values) - result = s.map(lambda x: "A", na_action=na_action) + result = s.map(lambda x: "A", skipna=skipna) exp = Series(["A"] * 7, name="XX", index=list("abcdefg")) tm.assert_series_equal(result, exp) assert result.dtype == object if not using_infer_string else "str" @pytest.mark.parametrize( - "na_action, expected", + "skipna, expected", ( - [None, Series(["A", "B", "nan"], name="XX")], + [False, Series(["A", "B", "nan"], name="XX")], [ - "ignore", + True, Series( ["A", "B", np.nan], name="XX", @@ -514,11 +514,11 @@ def test_map_categorical(na_action, using_infer_string): ], ), ) -def test_map_categorical_na_action(na_action, expected): +def test_map_categorical_skipna(skipna, expected): dtype = pd.CategoricalDtype(list("DCBA"), ordered=True) values = pd.Categorical(list("AB") + [np.nan], dtype=dtype) s = Series(values, name="XX") - result = s.map(str, na_action=na_action) + result = s.map(str, skipna=skipna) tm.assert_series_equal(result, expected) @@ -651,3 +651,29 @@ def test_map_engine_not_executor(): with pytest.raises(ValueError, match="Not a valid engine: 'something'"): s.map(lambda x: x, engine="something") + + +def test_map_na_action_none(): + with tm.assert_produces_warning( + FutureWarning, match="``na_action`` parameter has been deprecated" + ): + result = Series([1, np.nan]).map(lambda x: 10, na_action=None) + expected = Series([10, 10]) + tm.assert_series_equal(result, expected) + + +def test_map_na_action_ignore(): + with tm.assert_produces_warning( + FutureWarning, match="``na_action`` parameter has been deprecated" + ): + result = Series([1, np.nan]).map(lambda x: 10, na_action="ignore") + expected = Series([10, np.nan]) + tm.assert_series_equal(result, expected) + + +def test_map_invalid_na_action(): + with tm.assert_produces_warning( + FutureWarning, match="``na_action`` parameter has been deprecated" + ): + with pytest.raises(ValueError, match="na_action must .* 'abc' was passed"): + Series([1, 2]).map(func=lambda x: x, na_action="abc")