diff --git a/doc/source/whatsnew/v1.6.0.rst b/doc/source/whatsnew/v1.6.0.rst index aff950c6933dd..266480f2e28e2 100644 --- a/doc/source/whatsnew/v1.6.0.rst +++ b/doc/source/whatsnew/v1.6.0.rst @@ -31,6 +31,7 @@ Other enhancements - :meth:`.GroupBy.quantile` now preserving nullable dtypes instead of casting to numpy dtypes (:issue:`37493`) - :meth:`Series.add_suffix`, :meth:`DataFrame.add_suffix`, :meth:`Series.add_prefix` and :meth:`DataFrame.add_prefix` support an ``axis`` argument. If ``axis`` is set, the default behaviour of which axis to consider can be overwritten (:issue:`47819`) - :func:`assert_frame_equal` now shows the first element where the DataFrames differ, analogously to ``pytest``'s output (:issue:`47910`) +- Added ``index`` parameter to :meth:`DataFrame.to_dict` (:issue:`46398`) - .. --------------------------------------------------------------------------- diff --git a/pandas/core/frame.py b/pandas/core/frame.py index c52a7b0daa30e..26a068fc94395 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -1864,6 +1864,7 @@ def to_dict( "dict", "list", "series", "split", "tight", "records", "index" ] = "dict", into: type[dict] = dict, + index: bool = True, ) -> dict | list[dict]: """ Convert the DataFrame to a dictionary. @@ -1900,6 +1901,13 @@ def to_dict( instance of the mapping type you want. If you want a collections.defaultdict, you must pass it initialized. + index : bool, default True + Whether to include the index item (and index_names item if `orient` + is 'tight') in the returned dictionary. Can only be ``False`` + when `orient` is 'split' or 'tight'. + + .. versionadded:: 1.6.0 + Returns ------- dict, list or collections.abc.Mapping @@ -2005,6 +2013,11 @@ def to_dict( elif orient.startswith("i"): orient = "index" + if not index and orient not in ["split", "tight"]: + raise ValueError( + "'index=False' is only valid when 'orient' is 'split' or 'tight'" + ) + if orient == "dict": return into_c((k, v.to_dict(into)) for k, v in self.items()) @@ -2015,8 +2028,8 @@ def to_dict( elif orient == "split": return into_c( - ( - ("index", self.index.tolist()), + ((("index", self.index.tolist()),) if index else ()) + + ( ("columns", self.columns.tolist()), ( "data", @@ -2030,8 +2043,8 @@ def to_dict( elif orient == "tight": return into_c( - ( - ("index", self.index.tolist()), + ((("index", self.index.tolist()),) if index else ()) + + ( ("columns", self.columns.tolist()), ( "data", @@ -2040,9 +2053,9 @@ def to_dict( for t in self.itertuples(index=False, name=None) ], ), - ("index_names", list(self.index.names)), - ("column_names", list(self.columns.names)), ) + + ((("index_names", list(self.index.names)),) if index else ()) + + (("column_names", list(self.columns.names)),) ) elif orient == "series": diff --git a/pandas/tests/frame/methods/test_to_dict.py b/pandas/tests/frame/methods/test_to_dict.py index 6d5c32cae7368..613f7147a4a7d 100644 --- a/pandas/tests/frame/methods/test_to_dict.py +++ b/pandas/tests/frame/methods/test_to_dict.py @@ -421,3 +421,31 @@ def test_to_dict_returns_native_types(self, orient, data, expected_types): for i, key, value in assertion_iterator: assert value == data[key][i] assert type(value) is expected_types[key][i] + + @pytest.mark.parametrize("orient", ["dict", "list", "series", "records", "index"]) + def test_to_dict_index_false_error(self, orient): + # GH#46398 + df = DataFrame({"col1": [1, 2], "col2": [3, 4]}, index=["row1", "row2"]) + msg = "'index=False' is only valid when 'orient' is 'split' or 'tight'" + with pytest.raises(ValueError, match=msg): + df.to_dict(orient=orient, index=False) + + @pytest.mark.parametrize( + "orient, expected", + [ + ("split", {"columns": ["col1", "col2"], "data": [[1, 3], [2, 4]]}), + ( + "tight", + { + "columns": ["col1", "col2"], + "data": [[1, 3], [2, 4]], + "column_names": [None], + }, + ), + ], + ) + def test_to_dict_index_false(self, orient, expected): + # GH#46398 + df = DataFrame({"col1": [1, 2], "col2": [3, 4]}, index=["row1", "row2"]) + result = df.to_dict(orient=orient, index=False) + tm.assert_dict_equal(result, expected)