Skip to content

Commit a85715d

Browse files
author
Justine Kosinski
committed
Fix Excel header NaN
1 parent 10102e6 commit a85715d

File tree

1 file changed

+27
-34
lines changed

1 file changed

+27
-34
lines changed

pandas/io/formats/excel.py

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ class CSSToExcelConverter:
116116
focusing on font styling, backgrounds, borders and alignment.
117117
118118
Operates by first computing CSS styles in a fairly generic
119-
way (see :meth:`compute_css`) then determining Excel style
120-
properties from CSS properties (see :meth:`build_xlstyle`).
119+
way (see :meth: `compute_css`) then determining Excel style
120+
properties from CSS properties (see :meth: `build_xlstyle`).
121121
122122
Parameters
123123
----------
@@ -587,14 +587,15 @@ def __init__(
587587

588588
def _format_value(self, val):
589589
if is_scalar(val) and missing.isna(val):
590-
val = self.na_rep
590+
return self.na_rep
591591
elif is_float(val):
592592
if missing.isposinf_scalar(val):
593-
val = self.inf_rep
593+
return self.inf_rep
594594
elif missing.isneginf_scalar(val):
595-
val = f"-{self.inf_rep}"
595+
return f"-{self.inf_rep}"
596596
elif self.float_format is not None:
597-
val = float(self.float_format % val)
597+
return float(self.float_format % val)
598+
598599
if getattr(val, "tzinfo", None) is not None:
599600
raise ValueError(
600601
"Excel does not support datetimes with "
@@ -616,15 +617,25 @@ def _format_header_mi(self) -> Iterable[ExcelCell]:
616617

617618
columns = self.columns
618619
merge_columns = self.merge_cells in {True, "columns"}
619-
level_strs = columns._format_multi(sparsify=merge_columns, include_names=False)
620+
NBSP = "\u00a0"
621+
622+
fixed_levels = []
623+
for lvl in range(columns.nlevels):
624+
vals = columns.get_level_values(lvl)
625+
fixed_levels.append(vals.fillna(NBSP))
626+
fixed_columns = MultiIndex.from_arrays(fixed_levels, names=columns.names)
627+
628+
level_strs = fixed_columns._format_multi(
629+
sparsify=merge_columns, include_names=False
630+
)
620631
level_lengths = get_level_lengths(level_strs)
621632
coloffset = 0
622633
lnum = 0
623634

624635
if self.index and isinstance(self.df.index, MultiIndex):
625636
coloffset = self.df.index.nlevels - 1
626637

627-
for lnum, name in enumerate(columns.names):
638+
for lnum, name in enumerate(fixed_columns.names):
628639
yield ExcelCell(
629640
row=lnum,
630641
col=coloffset,
@@ -633,7 +644,7 @@ def _format_header_mi(self) -> Iterable[ExcelCell]:
633644
)
634645

635646
for lnum, (spans, levels, level_codes) in enumerate(
636-
zip(level_lengths, columns.levels, columns.codes, strict=True)
647+
zip(level_lengths, fixed_columns.levels, fixed_columns.codes, strict=True)
637648
):
638649
values = levels.take(level_codes)
639650
for i, span_val in spans.items():
@@ -657,7 +668,6 @@ def _format_header_mi(self) -> Iterable[ExcelCell]:
657668
def _format_header_regular(self) -> Iterable[ExcelCell]:
658669
if self._has_aliases or self.header:
659670
coloffset = 0
660-
661671
if self.index:
662672
coloffset = 1
663673
if isinstance(self.df.index, MultiIndex):
@@ -673,7 +683,10 @@ def _format_header_regular(self) -> Iterable[ExcelCell]:
673683
)
674684
colnames = self.header
675685

676-
for colindex, colname in enumerate(colnames):
686+
NBSP = "\u00a0"
687+
output_colnames = colnames.fillna(NBSP)
688+
689+
for colindex, colname in enumerate(output_colnames):
677690
yield CssExcelCell(
678691
row=self.rowcounter,
679692
col=colindex + coloffset,
@@ -687,15 +700,14 @@ def _format_header_regular(self) -> Iterable[ExcelCell]:
687700

688701
def _format_header(self) -> Iterable[ExcelCell]:
689702
gen: Iterable[ExcelCell]
690-
691703
if isinstance(self.columns, MultiIndex):
692704
gen = self._format_header_mi()
693705
else:
694706
gen = self._format_header_regular()
695707

696708
gen2: Iterable[ExcelCell] = ()
697709

698-
if self.df.index.names:
710+
if self.df.index.names and self.header is not False:
699711
row = [x if x is not None else "" for x in self.df.index.names] + [
700712
""
701713
] * len(self.columns)
@@ -762,35 +774,25 @@ def _format_regular_rows(self) -> Iterable[ExcelCell]:
762774
def _format_hierarchical_rows(self) -> Iterable[ExcelCell]:
763775
if self._has_aliases or self.header:
764776
self.rowcounter += 1
765-
766777
gcolidx = 0
767-
768778
if self.index:
769779
index_labels = self.df.index.names
770-
# check for aliases
771780
if self.index_label and isinstance(
772781
self.index_label, (list, tuple, np.ndarray, Index)
773782
):
774783
index_labels = self.index_label
775-
776-
# MultiIndex columns require an extra row
777-
# with index names (blank if None) for
778-
# unambiguous round-trip, Issue #11328
779784
if isinstance(self.columns, MultiIndex):
780785
self.rowcounter += 1
781-
782-
# if index labels are not empty go ahead and dump
783786
if com.any_not_none(*index_labels) and self.header is not False:
784787
for cidx, name in enumerate(index_labels):
785788
yield ExcelCell(self.rowcounter - 1, cidx, name, None)
786789

787790
if self.merge_cells and self.merge_cells != "columns":
788-
# Format hierarchical rows as merged cells.
791+
# Cette partie (pour merge_cells=True) est correcte
789792
level_strs = self.df.index._format_multi(
790793
sparsify=True, include_names=False
791794
)
792795
level_lengths = get_level_lengths(level_strs)
793-
794796
for spans, levels, level_codes in zip(
795797
level_lengths,
796798
self.df.index.levels,
@@ -802,10 +804,8 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]:
802804
allow_fill=levels._can_hold_na,
803805
fill_value=levels._na_value,
804806
)
805-
# GH#60099
806-
if isinstance(values[0], Period):
807+
if values.size > 0 and isinstance(values[0], Period):
807808
values = values.to_timestamp()
808-
809809
for i, span_val in spans.items():
810810
mergestart, mergeend = None, None
811811
if span_val > 1:
@@ -824,15 +824,11 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]:
824824
mergeend=mergeend,
825825
)
826826
gcolidx += 1
827-
828827
else:
829-
# Format hierarchical rows with non-merged values.
830828
for indexcolvals in zip(*self.df.index, strict=True):
831829
for idx, indexcolval in enumerate(indexcolvals):
832-
# GH#60099
833830
if isinstance(indexcolval, Period):
834831
indexcolval = indexcolval.to_timestamp()
835-
836832
yield CssExcelCell(
837833
row=self.rowcounter + idx,
838834
col=gcolidx,
@@ -844,7 +840,6 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]:
844840
css_converter=self.style_converter,
845841
)
846842
gcolidx += 1
847-
848843
yield from self._generate_body(gcolidx)
849844

850845
@property
@@ -901,9 +896,7 @@ def write(
901896
write engine to use if writer is a path - you can also set this
902897
via the options ``io.excel.xlsx.writer``,
903898
or ``io.excel.xlsm.writer``.
904-
905899
{storage_options}
906-
907900
engine_kwargs: dict, optional
908901
Arbitrary keyword arguments passed to excel engine.
909902
"""

0 commit comments

Comments
 (0)