Skip to content

Commit ee0902a

Browse files
authored
BUG: Convert output type in Excel for MultiIndex with period levels (#60182)
1 parent eaa8b47 commit ee0902a

File tree

4 files changed

+73
-0
lines changed

4 files changed

+73
-0
lines changed

doc/source/whatsnew/v3.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ I/O
690690
- Bug in :meth:`DataFrame.from_records` where ``columns`` parameter with numpy structured array was not reordering and filtering out the columns (:issue:`59717`)
691691
- Bug in :meth:`DataFrame.to_dict` raises unnecessary ``UserWarning`` when columns are not unique and ``orient='tight'``. (:issue:`58281`)
692692
- Bug in :meth:`DataFrame.to_excel` when writing empty :class:`DataFrame` with :class:`MultiIndex` on both axes (:issue:`57696`)
693+
- Bug in :meth:`DataFrame.to_excel` where the :class:`MultiIndex` index with a period level was not a date (:issue:`60099`)
693694
- Bug in :meth:`DataFrame.to_stata` when writing :class:`DataFrame` and ``byteorder=`big```. (:issue:`58969`)
694695
- Bug in :meth:`DataFrame.to_stata` when writing more than 32,000 value labels. (:issue:`60107`)
695696
- Bug in :meth:`DataFrame.to_string` that raised ``StopIteration`` with nested DataFrames. (:issue:`16098`)

pandas/io/formats/excel.py

+8
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
DataFrame,
3838
Index,
3939
MultiIndex,
40+
Period,
4041
PeriodIndex,
4142
)
4243
import pandas.core.common as com
@@ -803,6 +804,9 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]:
803804
allow_fill=levels._can_hold_na,
804805
fill_value=levels._na_value,
805806
)
807+
# GH#60099
808+
if isinstance(values[0], Period):
809+
values = values.to_timestamp()
806810

807811
for i, span_val in spans.items():
808812
mergestart, mergeend = None, None
@@ -827,6 +831,10 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]:
827831
# Format hierarchical rows with non-merged values.
828832
for indexcolvals in zip(*self.df.index):
829833
for idx, indexcolval in enumerate(indexcolvals):
834+
# GH#60099
835+
if isinstance(indexcolval, Period):
836+
indexcolval = indexcolval.to_timestamp()
837+
830838
yield CssExcelCell(
831839
row=self.rowcounter + idx,
832840
col=gcolidx,

pandas/tests/io/excel/test_style.py

+26
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
from pandas import (
1111
DataFrame,
12+
MultiIndex,
13+
Timestamp,
14+
period_range,
1215
read_excel,
1316
)
1417
import pandas._testing as tm
@@ -333,3 +336,26 @@ def test_styler_to_s3(s3_public_bucket, s3so):
333336
f"s3://{mock_bucket_name}/{target_file}", index_col=0, storage_options=s3so
334337
)
335338
tm.assert_frame_equal(result, df)
339+
340+
341+
@pytest.mark.parametrize("merge_cells", [True, False, "columns"])
342+
def test_format_hierarchical_rows_periodindex(merge_cells):
343+
# GH#60099
344+
df = DataFrame(
345+
{"A": [1, 2]},
346+
index=MultiIndex.from_arrays(
347+
[
348+
period_range(start="2006-10-06", end="2006-10-07", freq="D"),
349+
["X", "Y"],
350+
],
351+
names=["date", "category"],
352+
),
353+
)
354+
formatter = ExcelFormatter(df, merge_cells=merge_cells)
355+
formatted_cells = formatter._format_hierarchical_rows()
356+
357+
for cell in formatted_cells:
358+
if cell.row != 0 and cell.col == 0:
359+
assert isinstance(
360+
cell.val, Timestamp
361+
), "Period should be converted to Timestamp"

pandas/tests/io/excel/test_writers.py

+38
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
MultiIndex,
2424
date_range,
2525
option_context,
26+
period_range,
2627
)
2728
import pandas._testing as tm
2829

@@ -335,6 +336,43 @@ def test_multiindex_interval_datetimes(self, tmp_excel):
335336
)
336337
tm.assert_frame_equal(result, expected)
337338

339+
@pytest.mark.parametrize("merge_cells", [True, False, "columns"])
340+
def test_excel_round_trip_with_periodindex(self, tmp_excel, merge_cells):
341+
# GH#60099
342+
df = DataFrame(
343+
{"A": [1, 2]},
344+
index=MultiIndex.from_arrays(
345+
[
346+
period_range(start="2006-10-06", end="2006-10-07", freq="D"),
347+
["X", "Y"],
348+
],
349+
names=["date", "category"],
350+
),
351+
)
352+
df.to_excel(tmp_excel, merge_cells=merge_cells)
353+
result = pd.read_excel(tmp_excel, index_col=[0, 1])
354+
expected = DataFrame(
355+
{"A": [1, 2]},
356+
MultiIndex.from_arrays(
357+
[
358+
[
359+
pd.to_datetime("2006-10-06 00:00:00"),
360+
pd.to_datetime("2006-10-07 00:00:00"),
361+
],
362+
["X", "Y"],
363+
],
364+
names=["date", "category"],
365+
),
366+
)
367+
time_format = (
368+
"datetime64[s]" if tmp_excel.endswith(".ods") else "datetime64[us]"
369+
)
370+
expected.index = expected.index.set_levels(
371+
expected.index.levels[0].astype(time_format), level=0
372+
)
373+
374+
tm.assert_frame_equal(result, expected)
375+
338376

339377
@pytest.mark.parametrize(
340378
"engine,ext",

0 commit comments

Comments
 (0)