From 9a09f29407717b7d447ff066ffe2942793d1996a Mon Sep 17 00:00:00 2001 From: Justine Kosinski Date: Sat, 4 Oct 2025 10:49:55 +0200 Subject: [PATCH 1/3] fix: fix with wrong formatting --- pandas/io/formats/excel.py | 90 ++++++++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 29 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index f2cf1a4838c36..e88ac4cf5ec00 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -2,7 +2,7 @@ Utilities for conversion to writer-agnostic Excel representation. """ -from __future__ import annotations +from _future_ import annotations from collections.abc import ( Callable, @@ -11,6 +11,7 @@ Mapping, Sequence, ) +import pandas as pd import functools import itertools import re @@ -63,10 +64,10 @@ class ExcelCell: - __fields__ = ("row", "col", "val", "style", "mergestart", "mergeend") - __slots__ = __fields__ + _fields_ = ("row", "col", "val", "style", "mergestart", "mergeend") + _slots_ = _fields_ - def __init__( + def _init_( self, row: int, col: int, @@ -84,7 +85,7 @@ def __init__( class CssExcelCell(ExcelCell): - def __init__( + def _init_( self, row: int, col: int, @@ -105,7 +106,7 @@ def __init__( unique_declarations = frozenset(declaration_dict.items()) style = css_converter(unique_declarations) - super().__init__(row=row, col=col, val=val, style=style, **kwargs) + super()._init_(row=row, col=col, val=val, style=style, **kwargs) class CSSToExcelConverter: @@ -116,14 +117,14 @@ class CSSToExcelConverter: focusing on font styling, backgrounds, borders and alignment. Operates by first computing CSS styles in a fairly generic - way (see :meth:`compute_css`) then determining Excel style - properties from CSS properties (see :meth:`build_xlstyle`). + way (see :meth:⁠ compute_css ⁠) then determining Excel style + properties from CSS properties (see :meth:⁠ build_xlstyle ⁠). Parameters ---------- inherited : str, optional CSS declarations understood to be the containing scope for the - CSS processed by :meth:`__call__`. + CSS processed by :meth:⁠ __call__ ⁠. """ NAMED_COLORS = CSS4_COLORS @@ -183,25 +184,25 @@ class CSSToExcelConverter: ] } - # NB: Most of the methods here could be classmethods, as only __init__ - # and __call__ make use of instance attributes. We leave them as + # NB: Most of the methods here could be classmethods, as only _init_ + # and _call_ make use of instance attributes. We leave them as # instancemethods so that users can easily experiment with extensions # without monkey-patching. inherited: dict[str, str] | None - def __init__(self, inherited: str | None = None) -> None: + def _init_(self, inherited: str | None = None) -> None: if inherited is not None: self.inherited = self.compute_css(inherited) else: self.inherited = None - # We should avoid cache on the __call__ method. - # Otherwise once the method __call__ has been called + # We should avoid cache on the _call_ method. + # Otherwise once the method _call_ has been called # garbage collection no longer deletes the instance. self._call_cached = functools.cache(self._call_uncached) compute_css = CSSResolver() - def __call__( + def _call_( self, declarations: str | frozenset[tuple[str, str]] ) -> dict[str, dict[str, str]]: """ @@ -517,19 +518,19 @@ class ExcelFormatter: output row names (index) index_label : str or sequence, default None Column label for index column(s) if desired. If None is given, and - `header` and `index` are True, then the index names are used. A + ⁠ header ⁠ and ⁠ index ⁠ are True, then the index names are used. A sequence should be given if the DataFrame uses MultiIndex. merge_cells : bool or 'columns', default False Format MultiIndex column headers and Hierarchical Rows as merged cells if True. Merge MultiIndex column headers only if 'columns'. .. versionchanged:: 3.0.0 Added the 'columns' option. - inf_rep : str, default `'inf'` + inf_rep : str, default ⁠ 'inf' ⁠ representation for np.inf values (which aren't representable in Excel) - A `'-'` sign will be added in front of -inf. + A ⁠ '-' ⁠ sign will be added in front of -inf. style_converter : callable, optional This translates Styler styles (CSS) into ExcelWriter styles. - Defaults to ``CSSToExcelConverter()``. + Defaults to `⁠ CSSToExcelConverter() ⁠`. It should have signature css_declarations string -> excel style. This is only called for body cells. """ @@ -537,7 +538,7 @@ class ExcelFormatter: max_rows = 2**20 max_cols = 2**14 - def __init__( + def _init_( self, df, na_rep: str = "", @@ -594,7 +595,11 @@ def _format_value(self, val): elif missing.isneginf_scalar(val): val = f"-{self.inf_rep}" elif self.float_format is not None: - val = float(self.float_format % val) + val = self.float_format % val + else: + # respecter l'affichage par défaut de pandas (console) + val = repr(val) + if getattr(val, "tzinfo", None) is not None: raise ValueError( "Excel does not support datetimes with " @@ -616,7 +621,20 @@ def _format_header_mi(self) -> Iterable[ExcelCell]: columns = self.columns merge_columns = self.merge_cells in {True, "columns"} - level_strs = columns._format_multi(sparsify=merge_columns, include_names=False) + + # Replace NaN column header values with a non-breaking space so + # Excel output matches console display (see user's _fix_headers). + NBSP = "\u00A0" + if isinstance(columns, MultiIndex): + fixed_levels = [] + for lvl in range(columns.nlevels): + vals = columns.get_level_values(lvl) + fixed_levels.append([NBSP if pd.isna(v) else str(v) for v in vals]) + fixed_columns = MultiIndex.from_arrays(fixed_levels, names=columns.names) + else: + fixed_columns = Index([NBSP if pd.isna(v) else str(v) for v in columns], name=columns.name) + + level_strs = fixed_columns._format_multi(sparsify=merge_columns, include_names=False) level_lengths = get_level_lengths(level_strs) coloffset = 0 lnum = 0 @@ -625,17 +643,24 @@ def _format_header_mi(self) -> Iterable[ExcelCell]: coloffset = self.df.index.nlevels - 1 for lnum, name in enumerate(columns.names): + val = NBSP if pd.isna(name) else str(name) yield ExcelCell( row=lnum, col=coloffset, - val=name, + val=val, style=None, ) - for lnum, (spans, levels, level_codes) in enumerate( - zip(level_lengths, columns.levels, columns.codes) + + + # Iterate the fixed_columns levels/codes so values already have + # NaNs replaced by NBSP (and are strings). + for lnum, (spans, level, codes) in enumerate( + zip(level_lengths, fixed_columns.levels, fixed_columns.codes) ): - values = levels.take(level_codes) + # level.take(codes) on fixed_columns.levels yields string values + values = level.take(codes).to_numpy() + for i, span_val in spans.items(): mergestart, mergeend = None, None if merge_columns and span_val > 1: @@ -652,6 +677,7 @@ def _format_header_mi(self) -> Iterable[ExcelCell]: mergestart=mergestart, mergeend=mergeend, ) + self.rowcounter = lnum def _format_header_regular(self) -> Iterable[ExcelCell]: @@ -673,6 +699,12 @@ def _format_header_regular(self) -> Iterable[ExcelCell]: ) colnames = self.header + # Normalize NaN column labels to a non-breaking space so Excel + # header output matches console display (same behavior as + # applied to MultiIndex headers in _format_header_mi). + NBSP = "\u00A0" + colnames = [NBSP if pd.isna(v) else str(v) for v in colnames] + for colindex, colname in enumerate(colnames): yield CssExcelCell( row=self.rowcounter, @@ -896,8 +928,8 @@ def write( is to be frozen engine : string, default None write engine to use if writer is a path - you can also set this - via the options ``io.excel.xlsx.writer``, - or ``io.excel.xlsm.writer``. + via the options `⁠ io.excel.xlsx.writer ⁠`, + or `⁠ io.excel.xlsm.writer ⁠`. {storage_options} @@ -939,4 +971,4 @@ def write( finally: # make sure to close opened file handles if need_save: - writer.close() + writer.close() \ No newline at end of file From 44e2b69c5e7579f45ba92499fa0d8ad431635d8a Mon Sep 17 00:00:00 2001 From: Justine Kosinski Date: Sat, 4 Oct 2025 11:42:24 +0200 Subject: [PATCH 2/3] style: right formatting --- pandas/io/formats/excel.py | 47 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index e88ac4cf5ec00..9a1727c58b0af 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -2,7 +2,7 @@ Utilities for conversion to writer-agnostic Excel representation. """ -from _future_ import annotations +from __future__ import annotations from collections.abc import ( Callable, @@ -11,7 +11,6 @@ Mapping, Sequence, ) -import pandas as pd import functools import itertools import re @@ -64,10 +63,10 @@ class ExcelCell: - _fields_ = ("row", "col", "val", "style", "mergestart", "mergeend") - _slots_ = _fields_ + __fields__ = ("row", "col", "val", "style", "mergestart", "mergeend") + __slots__ = __fields__ - def _init_( + def __init__( self, row: int, col: int, @@ -85,7 +84,7 @@ def _init_( class CssExcelCell(ExcelCell): - def _init_( + def __init__( self, row: int, col: int, @@ -106,7 +105,7 @@ def _init_( unique_declarations = frozenset(declaration_dict.items()) style = css_converter(unique_declarations) - super()._init_(row=row, col=col, val=val, style=style, **kwargs) + super().__init__(row=row, col=col, val=val, style=style, **kwargs) class CSSToExcelConverter: @@ -117,14 +116,14 @@ class CSSToExcelConverter: focusing on font styling, backgrounds, borders and alignment. Operates by first computing CSS styles in a fairly generic - way (see :meth:⁠ compute_css ⁠) then determining Excel style - properties from CSS properties (see :meth:⁠ build_xlstyle ⁠). + way (see :meth: `compute_css`) then determining Excel style + properties from CSS properties (see :meth: `build_xlstyle`). Parameters ---------- inherited : str, optional CSS declarations understood to be the containing scope for the - CSS processed by :meth:⁠ __call__ ⁠. + CSS processed by :meth:`__call__`. """ NAMED_COLORS = CSS4_COLORS @@ -184,25 +183,25 @@ class CSSToExcelConverter: ] } - # NB: Most of the methods here could be classmethods, as only _init_ - # and _call_ make use of instance attributes. We leave them as + # NB: Most of the methods here could be classmethods, as only __init__ + # and __call__ make use of instance attributes. We leave them as # instancemethods so that users can easily experiment with extensions # without monkey-patching. inherited: dict[str, str] | None - def _init_(self, inherited: str | None = None) -> None: + def __init__(self, inherited: str | None = None) -> None: if inherited is not None: self.inherited = self.compute_css(inherited) else: self.inherited = None - # We should avoid cache on the _call_ method. - # Otherwise once the method _call_ has been called + # We should avoid cache on the __call__ method. + # Otherwise once the method __call__ has been called # garbage collection no longer deletes the instance. self._call_cached = functools.cache(self._call_uncached) compute_css = CSSResolver() - def _call_( + def __call__( self, declarations: str | frozenset[tuple[str, str]] ) -> dict[str, dict[str, str]]: """ @@ -518,19 +517,19 @@ class ExcelFormatter: output row names (index) index_label : str or sequence, default None Column label for index column(s) if desired. If None is given, and - ⁠ header ⁠ and ⁠ index ⁠ are True, then the index names are used. A + `header` and `index` are True, then the index names are used. A sequence should be given if the DataFrame uses MultiIndex. merge_cells : bool or 'columns', default False Format MultiIndex column headers and Hierarchical Rows as merged cells if True. Merge MultiIndex column headers only if 'columns'. .. versionchanged:: 3.0.0 Added the 'columns' option. - inf_rep : str, default ⁠ 'inf' ⁠ + inf_rep : str, default `'inf'` representation for np.inf values (which aren't representable in Excel) - A ⁠ '-' ⁠ sign will be added in front of -inf. + A `'-'` sign will be added in front of -inf. style_converter : callable, optional This translates Styler styles (CSS) into ExcelWriter styles. - Defaults to `⁠ CSSToExcelConverter() ⁠`. + Defaults to ``CSSToExcelConverter()``. It should have signature css_declarations string -> excel style. This is only called for body cells. """ @@ -538,7 +537,7 @@ class ExcelFormatter: max_rows = 2**20 max_cols = 2**14 - def _init_( + def __init__( self, df, na_rep: str = "", @@ -928,8 +927,8 @@ def write( is to be frozen engine : string, default None write engine to use if writer is a path - you can also set this - via the options `⁠ io.excel.xlsx.writer ⁠`, - or `⁠ io.excel.xlsm.writer ⁠`. + via the options ``io.excel.xlsx.writer``, + or ``io.excel.xlsm.writer``. {storage_options} @@ -971,4 +970,4 @@ def write( finally: # make sure to close opened file handles if need_save: - writer.close() \ No newline at end of file + writer.close() From a5784f5aa6fce6e35e8e6afeb2d3775283230eac Mon Sep 17 00:00:00 2001 From: Justine Kosinski Date: Sat, 4 Oct 2025 12:09:05 +0200 Subject: [PATCH 3/3] refactor: put right import for isna method --- pandas/io/formats/excel.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 9a1727c58b0af..96f55ea6451e8 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -628,10 +628,10 @@ def _format_header_mi(self) -> Iterable[ExcelCell]: fixed_levels = [] for lvl in range(columns.nlevels): vals = columns.get_level_values(lvl) - fixed_levels.append([NBSP if pd.isna(v) else str(v) for v in vals]) + fixed_levels.append([NBSP if missing.isna(v) else str(v) for v in vals]) fixed_columns = MultiIndex.from_arrays(fixed_levels, names=columns.names) else: - fixed_columns = Index([NBSP if pd.isna(v) else str(v) for v in columns], name=columns.name) + fixed_columns = Index([NBSP if missing.isna(v) else str(v) for v in columns], name=columns.name) level_strs = fixed_columns._format_multi(sparsify=merge_columns, include_names=False) level_lengths = get_level_lengths(level_strs) @@ -642,7 +642,7 @@ def _format_header_mi(self) -> Iterable[ExcelCell]: coloffset = self.df.index.nlevels - 1 for lnum, name in enumerate(columns.names): - val = NBSP if pd.isna(name) else str(name) + val = NBSP if missing.isna(name) else str(name) yield ExcelCell( row=lnum, col=coloffset, @@ -702,7 +702,7 @@ def _format_header_regular(self) -> Iterable[ExcelCell]: # header output matches console display (same behavior as # applied to MultiIndex headers in _format_header_mi). NBSP = "\u00A0" - colnames = [NBSP if pd.isna(v) else str(v) for v in colnames] + colnames = [NBSP if missing.isna(v) else str(v) for v in colnames] for colindex, colname in enumerate(colnames): yield CssExcelCell(