diff --git a/doc/source/whatsnew/v2.3.0.rst b/doc/source/whatsnew/v2.3.0.rst index 922cc0ead7fb0..d57d86f4a1476 100644 --- a/doc/source/whatsnew/v2.3.0.rst +++ b/doc/source/whatsnew/v2.3.0.rst @@ -133,7 +133,7 @@ MultiIndex I/O ^^^ -- +- :meth:`DataFrame.to_excel` was storing decimals as strings instead of numbers (:issue:`49598`) - Period diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index ef52107c283e9..ced2ad91dba1e 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -8,6 +8,7 @@ Sequence, ) import datetime +from decimal import Decimal from functools import partial import os from textwrap import fill @@ -43,6 +44,7 @@ from pandas.core.dtypes.common import ( is_bool, + is_decimal, is_file_like, is_float, is_integer, @@ -1348,6 +1350,8 @@ def _value_with_fmt( val = float(val) elif is_bool(val): val = bool(val) + elif is_decimal(val): + val = Decimal(val) elif isinstance(val, datetime.datetime): fmt = self._datetime_format elif isinstance(val, datetime.date): diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index 44266ae9a62a5..81aa0be24bffc 100644 --- a/pandas/tests/io/excel/test_writers.py +++ b/pandas/tests/io/excel/test_writers.py @@ -3,6 +3,7 @@ datetime, timedelta, ) +from decimal import Decimal from functools import partial from io import BytesIO import os @@ -977,6 +978,36 @@ def test_to_excel_float_format(self, tmp_excel): ) tm.assert_frame_equal(result, expected) + def test_to_excel_datatypes_preserved(self, tmp_excel): + # Test that when writing and reading Excel with dtype=object, + # datatypes are preserved, except Decimals which should be + # stored as floats + + # see gh-49598 + df = DataFrame( + [ + [1.23, "1.23", Decimal("1.23")], + [4.56, "4.56", Decimal("4.56")], + ], + index=["A", "B"], + columns=["X", "Y", "Z"], + ) + df.to_excel(tmp_excel) + + with ExcelFile(tmp_excel) as reader: + result = pd.read_excel(reader, index_col=0, dtype=object) + + expected = DataFrame( + [ + [1.23, "1.23", 1.23], + [4.56, "4.56", 4.56], + ], + index=["A", "B"], + columns=["X", "Y", "Z"], + dtype=object, + ) + tm.assert_frame_equal(result, expected) + def test_to_excel_output_encoding(self, tmp_excel): # Avoid mixed inferred_type. df = DataFrame(