Skip to content

Commit 2885c3e

Browse files
committed
Merge branch 'BUG-allow-map-with-abc-mapping' of https://github.com/ohad83/pandas into ohad83-BUG-allow-map-with-abc-mapping
2 parents 765d8db + 5c6bb3c commit 2885c3e

File tree

6 files changed

+55
-5
lines changed

6 files changed

+55
-5
lines changed

doc/source/whatsnew/v1.0.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -216,13 +216,15 @@ Other enhancements
216216
(:meth:`~DataFrame.to_parquet` / :func:`read_parquet`) using the `'pyarrow'` engine
217217
now preserve those data types with pyarrow >= 1.0.0 (:issue:`20612`).
218218
- The ``partition_cols`` argument in :meth:`DataFrame.to_parquet` now accepts a string (:issue:`27117`)
219+
<<<<<<< HEAD
219220
- The ``pandas.np`` submodule is now deprecated. Import numpy directly instead (:issue:`30296`)
220221
- :func:`to_parquet` now appropriately handles the ``schema`` argument for user defined schemas in the pyarrow engine. (:issue: `30270`)
221222
- DataFrame constructor preserve `ExtensionArray` dtype with `ExtensionArray` (:issue:`11363`)
222223
- :meth:`DataFrame.sort_values` and :meth:`Series.sort_values` have gained ``ignore_index`` keyword to be able to reset index after sorting (:issue:`30114`)
223224
- :meth:`DataFrame.to_markdown` and :meth:`Series.to_markdown` added (:issue:`11052`)
224225
- :meth:`DataFrame.drop_duplicates` has gained ``ignore_index`` keyword to reset index (:issue:`30114`)
225226
- Added new writer for exporting Stata dta files in version 118, ``StataWriter118``. This format supports exporting strings containing Unicode characters (:issue:`23573`)
227+
- :meth:`Series.map` now accepts ``collections.abc.Mapping`` subclasses as a mapper (:issue:`29733`)
226228

227229
Build Changes
228230
^^^^^^^^^^^^^

pandas/core/base.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
is_categorical_dtype,
2020
is_datetime64_ns_dtype,
2121
is_datetime64tz_dtype,
22+
is_dict_like,
2223
is_extension_array_dtype,
2324
is_list_like,
2425
is_object_dtype,
@@ -1107,8 +1108,8 @@ def _map_values(self, mapper, na_action=None):
11071108
# we can fastpath dict/Series to an efficient map
11081109
# as we know that we are not going to have to yield
11091110
# python types
1110-
if isinstance(mapper, dict):
1111-
if hasattr(mapper, "__missing__"):
1111+
if is_dict_like(mapper):
1112+
if isinstance(mapper, dict) and hasattr(mapper, "__missing__"):
11121113
# If a dictionary subclass defines a default value method,
11131114
# convert mapper to a lookup function (GH #15999).
11141115
dict_with_default = mapper

pandas/core/series.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ class Series(base.IndexOpsMixin, generic.NDFrame):
182182
def __init__(
183183
self, data=None, index=None, dtype=None, name=None, copy=False, fastpath=False
184184
):
185+
185186
# we are called internally, so short-circuit
186187
if fastpath:
187188

@@ -250,7 +251,7 @@ def __init__(
250251
else:
251252
data = data.reindex(index, copy=copy)
252253
data = data._data
253-
elif isinstance(data, dict):
254+
elif is_dict_like(data):
254255
data, index = self._init_dict(data, index, dtype)
255256
dtype = None
256257
copy = False
@@ -3513,7 +3514,7 @@ def map(self, arg, na_action=None):
35133514
35143515
Parameters
35153516
----------
3516-
arg : function, dict, or Series
3517+
arg : function, collections.abc.Mapping subclass or Series
35173518
Mapping correspondence.
35183519
na_action : {None, 'ignore'}, default None
35193520
If 'ignore', propagate NaN values, without passing them to the

pandas/tests/series/test_apply.py

+24
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,30 @@ class DictWithoutMissing(dict):
627627
expected = Series([np.nan, np.nan, "three"])
628628
tm.assert_series_equal(result, expected)
629629

630+
def test_map_abc_mapping(self):
631+
# https://github.com/pandas-dev/pandas/issues/29733
632+
# Check collections.abc.Mapping support as mapper for Series.map
633+
s = Series([1, 2, 3])
634+
not_a_dictionary = tm.TestNonDictMapping({3: "three"})
635+
result = s.map(not_a_dictionary)
636+
expected = Series([np.nan, np.nan, "three"])
637+
tm.assert_series_equal(result, expected)
638+
639+
def test_map_abc_mapping_with_missing(self):
640+
# https://github.com/pandas-dev/pandas/issues/29733
641+
# Check collections.abc.Mapping support as mapper for Series.map
642+
class NonDictMappingWithMissing(tm.TestNonDictMapping):
643+
def __missing__(self, key):
644+
return "missing"
645+
646+
s = Series([1, 2, 3])
647+
not_a_dictionary = NonDictMappingWithMissing({3: "three"})
648+
result = s.map(not_a_dictionary)
649+
# __missing__ is a dict concept, not a Mapping concept,
650+
# so it should not change the result!
651+
expected = Series([np.nan, np.nan, "three"])
652+
tm.assert_series_equal(result, expected)
653+
630654
def test_map_box(self):
631655
vals = [pd.Timestamp("2011-01-01"), pd.Timestamp("2011-01-02")]
632656
s = pd.Series(vals)

pandas/tests/series/test_constructors.py

+8
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,14 @@ def create_data(constructor):
10891089
tm.assert_series_equal(result_datetime, expected)
10901090
tm.assert_series_equal(result_Timestamp, expected)
10911091

1092+
def test_constructor_mapping(self):
1093+
# GH 29788
1094+
ndm = tm.TestNonDictMapping({3: "three"})
1095+
result = Series(ndm)
1096+
expected = Series(["three"], index=[3])
1097+
1098+
tm.assert_series_equal(result, expected)
1099+
10921100
def test_constructor_list_of_tuples(self):
10931101
data = [(1, 1), (2, 2), (2, 3)]
10941102
s = Series(data)

pandas/util/testing.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import bz2
2-
from collections import Counter
2+
from collections import Counter, abc
33
from contextlib import contextmanager
44
from datetime import datetime
55
from functools import wraps
@@ -2128,6 +2128,20 @@ def __init__(self, *args, **kwargs):
21282128
dict.__init__(self, *args, **kwargs)
21292129

21302130

2131+
class TestNonDictMapping(abc.Mapping):
2132+
def __init__(self, underlying_dict):
2133+
self._data = underlying_dict
2134+
2135+
def __getitem__(self, key):
2136+
return self._data.__getitem__(key)
2137+
2138+
def __iter__(self):
2139+
return self._data.__iter__()
2140+
2141+
def __len__(self):
2142+
return self._data.__len__()
2143+
2144+
21312145
def optional_args(decorator):
21322146
"""allows a decorator to take optional positional and keyword arguments.
21332147
Assumes that taking a single, callable, positional argument means that

0 commit comments

Comments
 (0)