diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 07be496a95adc..54735b4ffed45 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -29,7 +29,7 @@ enhancement2 Other enhancements ^^^^^^^^^^^^^^^^^^ - DataFrame.apply now allows the usage of numba (via ``engine="numba"``) to JIT compile the passed function, allowing for potential speedups (:issue:`54666`) -- +- It is now possible to pass an object that either directly inherits from ``abc.Mapping`` or has it in its inheritance chain to initialize a DataFrame. The expected behaviour is the same as if you pass a ``dict`` object. (:issue:`55109`) .. --------------------------------------------------------------------------- .. _whatsnew_220.notable_bug_fixes: diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 8fcb91c846826..b6d552ba1f6dc 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -703,7 +703,7 @@ def __init__( raise ValueError("columns cannot be a set") if copy is None: - if isinstance(data, dict): + if isinstance(data, abc.Mapping): # retain pre-GH#38939 default behavior copy = True elif ( @@ -732,7 +732,7 @@ def __init__( data, axes={"index": index, "columns": columns}, dtype=dtype, copy=copy ) - elif isinstance(data, dict): + elif isinstance(data, abc.Mapping): # GH#38939 de facto copy defaults to False only in non-dict cases mgr = dict_to_mgr(data, index, columns, dtype=dtype, copy=copy, typ=manager) elif isinstance(data, ma.MaskedArray): diff --git a/pandas/core/internals/construction.py b/pandas/core/internals/construction.py index 6f30bc650aa36..61afc7784d94a 100644 --- a/pandas/core/internals/construction.py +++ b/pandas/core/internals/construction.py @@ -421,7 +421,7 @@ def _check_values_indices_shape_match( def dict_to_mgr( - data: dict, + data: abc.Mapping, index, columns, *, diff --git a/pandas/tests/frame/constructors/test_from_dict.py b/pandas/tests/frame/constructors/test_from_dict.py index d78924ff9d046..64743113f6cf3 100644 --- a/pandas/tests/frame/constructors/test_from_dict.py +++ b/pandas/tests/frame/constructors/test_from_dict.py @@ -1,4 +1,7 @@ -from collections import OrderedDict +from collections import ( + OrderedDict, + UserDict, +) import numpy as np import pytest @@ -200,3 +203,15 @@ def test_from_dict_orient_invalid(self): ) with pytest.raises(ValueError, match=msg): DataFrame.from_dict({"foo": 1, "baz": 3, "bar": 2}, orient="abc") + + def test_constructor_user_dict(self): + class CustomUserDict(UserDict): + pass + + d_1 = CustomUserDict(foo=1, bar=2) + df_1 = DataFrame(d_1, index=["a"]) + + d_2 = {"foo": 1, "bar": 2} + df_2 = DataFrame(d_2, index=["a"]) + + tm.assert_frame_equal(df_1, df_2)