From 8d5d4bb85d2c3e2bd5f371025a039ee2a784a142 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 15 Nov 2020 21:15:38 +0700 Subject: [PATCH 01/19] CLN: use enumerate --- pandas/io/formats/excel.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index c6179f5c034c7..a2fec09a95fd1 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -547,8 +547,7 @@ def _format_header_mi(self): if self.merge_cells: # Format multi-index as a merged cells. - for lnum in range(len(level_lengths)): - name = columns.names[lnum] + for lnum, name in enumerate(columns.names): yield ExcelCell(lnum, coloffset, name, self.header_style) for lnum, (spans, levels, level_codes) in enumerate( From b066f5f2668095eb5721575bca7bdbe39d0e8de9 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 15 Nov 2020 21:17:26 +0700 Subject: [PATCH 02/19] CLN: use kwargs for calling ExcelCell --- pandas/io/formats/excel.py | 52 ++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index a2fec09a95fd1..5e678626905f7 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -548,7 +548,12 @@ def _format_header_mi(self): if self.merge_cells: # Format multi-index as a merged cells. for lnum, name in enumerate(columns.names): - yield ExcelCell(lnum, coloffset, name, self.header_style) + yield ExcelCell( + row=lnum, + col=coloffset, + val=name, + style=self.header_style, + ) for lnum, (spans, levels, level_codes) in enumerate( zip(level_lengths, columns.levels, columns.codes) @@ -557,16 +562,19 @@ def _format_header_mi(self): for i in spans: if spans[i] > 1: yield ExcelCell( - lnum, - coloffset + i + 1, - values[i], - self.header_style, - lnum, - coloffset + i + spans[i], + row=lnum, + col=coloffset + i + 1, + val=values[i], + style=self.header_style, + mergestart=lnum, + mergeend=coloffset + i + spans[i], ) else: yield ExcelCell( - lnum, coloffset + i + 1, values[i], self.header_style + row=lnum, + col=coloffset + i + 1, + val=values[i], + style=self.header_style, ) else: # Format in legacy format with dots to indicate levels. @@ -723,19 +731,19 @@ def _format_hierarchical_rows(self): for i in spans: if spans[i] > 1: yield ExcelCell( - self.rowcounter + i, - gcolidx, - values[i], - self.header_style, - self.rowcounter + i + spans[i] - 1, - gcolidx, + row=self.rowcounter + i, + col=gcolidx, + val=values[i], + style=self.header_style, + mergestart=self.rowcounter + i + spans[i] - 1, + mergeend=gcolidx, ) else: yield ExcelCell( - self.rowcounter + i, - gcolidx, - values[i], - self.header_style, + row=self.rowcounter + i, + col=gcolidx, + val=values[i], + style=self.header_style, ) gcolidx += 1 @@ -744,10 +752,10 @@ def _format_hierarchical_rows(self): for indexcolvals in zip(*self.df.index): for idx, indexcolval in enumerate(indexcolvals): yield ExcelCell( - self.rowcounter + idx, - gcolidx, - indexcolval, - self.header_style, + row=self.rowcounter + idx, + col=gcolidx, + val=indexcolval, + style=self.header_style, ) gcolidx += 1 From 3484f5347d806ac4604c0f0df57bac5a88398d42 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 15 Nov 2020 22:04:26 +0700 Subject: [PATCH 03/19] CLN: de-duplicate by updating kwargs --- pandas/io/formats/excel.py | 53 ++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 5e678626905f7..6eb8e6b0c8456 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -560,22 +560,21 @@ def _format_header_mi(self): ): values = levels.take(level_codes) for i in spans: + kwargs = dict( + row=lnum, + col=coloffset + i + 1, + val=values[i], + style=self.header_style, + ) + if spans[i] > 1: - yield ExcelCell( - row=lnum, - col=coloffset + i + 1, - val=values[i], - style=self.header_style, - mergestart=lnum, - mergeend=coloffset + i + spans[i], - ) - else: - yield ExcelCell( - row=lnum, - col=coloffset + i + 1, - val=values[i], - style=self.header_style, + kwargs.update( + { + "mergestart": lnum, + "mergeend": coloffset + i + spans[i], + } ) + yield ExcelCell(**kwargs) else: # Format in legacy format with dots to indicate levels. for i, values in enumerate(zip(*level_strs)): @@ -729,22 +728,20 @@ def _format_hierarchical_rows(self): ) for i in spans: + kwargs = dict( + row=self.rowcounter + i, + col=gcolidx, + val=values[i], + style=self.header_style, + ) if spans[i] > 1: - yield ExcelCell( - row=self.rowcounter + i, - col=gcolidx, - val=values[i], - style=self.header_style, - mergestart=self.rowcounter + i + spans[i] - 1, - mergeend=gcolidx, - ) - else: - yield ExcelCell( - row=self.rowcounter + i, - col=gcolidx, - val=values[i], - style=self.header_style, + kwargs.update( + { + "mergestart": self.rowcounter + i + spans[i] - 1, + "mergeend": gcolidx, + } ) + yield ExcelCell(**kwargs) gcolidx += 1 else: From 5c2f16099f5ba14bba8c36acdf4316d642190346 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 15 Nov 2020 21:37:23 +0700 Subject: [PATCH 04/19] REF: extract property _has_aliases --- pandas/io/formats/excel.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 6eb8e6b0c8456..cc1bd2e25ded0 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -530,8 +530,7 @@ def _format_header_mi(self): "index ('index'=False) is not yet implemented." ) - has_aliases = isinstance(self.header, (tuple, list, np.ndarray, ABCIndex)) - if not (has_aliases or self.header): + if not (self._has_aliases or self.header): return columns = self.columns @@ -584,8 +583,7 @@ def _format_header_mi(self): self.rowcounter = lnum def _format_header_regular(self): - has_aliases = isinstance(self.header, (tuple, list, np.ndarray, ABCIndex)) - if has_aliases or self.header: + if self._has_aliases or self.header: coloffset = 0 if self.index: @@ -594,7 +592,7 @@ def _format_header_regular(self): coloffset = len(self.df.index[0]) colnames = self.columns - if has_aliases: + if self._has_aliases: # pandas\io\formats\excel.py:593: error: Argument 1 to "len" # has incompatible type "Union[Sequence[Optional[Hashable]], # bool]"; expected "Sized" [arg-type] @@ -644,8 +642,7 @@ def _format_body(self): return self._format_regular_rows() def _format_regular_rows(self): - has_aliases = isinstance(self.header, (tuple, list, np.ndarray, ABCIndex)) - if has_aliases or self.header: + if self._has_aliases or self.header: self.rowcounter += 1 # output index and index_label? @@ -683,8 +680,7 @@ def _format_regular_rows(self): yield from self._generate_body(coloffset) def _format_hierarchical_rows(self): - has_aliases = isinstance(self.header, (tuple, list, np.ndarray, ABCIndex)) - if has_aliases or self.header: + if self._has_aliases or self.header: self.rowcounter += 1 gcolidx = 0 @@ -758,6 +754,10 @@ def _format_hierarchical_rows(self): yield from self._generate_body(gcolidx) + @property + def _has_aliases(self) -> bool: + return bool(isinstance(self.header, (tuple, list, np.ndarray, ABCIndex))) + def _generate_body(self, coloffset: int): if self.styler is None: styles = None From ee884ca513fb1c657119675546843c94579343db Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 15 Nov 2020 21:49:17 +0700 Subject: [PATCH 05/19] TYP: fix typing error, remove ignore --- pandas/io/formats/excel.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index cc1bd2e25ded0..5dbc736cb25c3 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -5,7 +5,7 @@ from functools import reduce import itertools import re -from typing import Callable, Dict, Mapping, Optional, Sequence, Union +from typing import Callable, Dict, Mapping, Optional, Sequence, Sized, Union import warnings import numpy as np @@ -593,16 +593,10 @@ def _format_header_regular(self): colnames = self.columns if self._has_aliases: - # pandas\io\formats\excel.py:593: error: Argument 1 to "len" - # has incompatible type "Union[Sequence[Optional[Hashable]], - # bool]"; expected "Sized" [arg-type] - if len(self.header) != len(self.columns): # type: ignore[arg-type] - # pandas\io\formats\excel.py:602: error: Argument 1 to - # "len" has incompatible type - # "Union[Sequence[Optional[Hashable]], bool]"; expected - # "Sized" [arg-type] + assert isinstance(self.header, Sized) + if len(self.header) != len(self.columns): raise ValueError( - f"Writing {len(self.columns)} cols " # type: ignore[arg-type] + f"Writing {len(self.columns)} cols " f"but got {len(self.header)} aliases" ) else: From dab9e15debabb6b2d6b24d6136e7dfc7537c128d Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 15 Nov 2020 22:07:35 +0700 Subject: [PATCH 06/19] TYP: type cell iterators --- pandas/io/formats/excel.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 5dbc736cb25c3..62288cc5936d6 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -5,7 +5,7 @@ from functools import reduce import itertools import re -from typing import Callable, Dict, Mapping, Optional, Sequence, Sized, Union +from typing import Callable, Dict, Iterator, Mapping, Optional, Sequence, Sized, Union import warnings import numpy as np @@ -522,7 +522,7 @@ def _format_value(self, val): ) return val - def _format_header_mi(self): + def _format_header_mi(self) -> Iterator[ExcelCell]: if self.columns.nlevels > 1: if not self.index: raise NotImplementedError( @@ -582,7 +582,7 @@ def _format_header_mi(self): self.rowcounter = lnum - def _format_header_regular(self): + def _format_header_regular(self) -> Iterator[ExcelCell]: if self._has_aliases or self.header: coloffset = 0 @@ -607,7 +607,7 @@ def _format_header_regular(self): self.rowcounter, colindex + coloffset, colname, self.header_style ) - def _format_header(self): + def _format_header(self) -> Iterator[ExcelCell]: if isinstance(self.columns, MultiIndex): gen = self._format_header_mi() else: @@ -629,13 +629,13 @@ def _format_header(self): self.rowcounter += 1 return itertools.chain(gen, gen2) - def _format_body(self): + def _format_body(self) -> Iterator[ExcelCell]: if isinstance(self.df.index, MultiIndex): return self._format_hierarchical_rows() else: return self._format_regular_rows() - def _format_regular_rows(self): + def _format_regular_rows(self) -> Iterator[ExcelCell]: if self._has_aliases or self.header: self.rowcounter += 1 @@ -673,7 +673,7 @@ def _format_regular_rows(self): yield from self._generate_body(coloffset) - def _format_hierarchical_rows(self): + def _format_hierarchical_rows(self) -> Iterator[ExcelCell]: if self._has_aliases or self.header: self.rowcounter += 1 @@ -752,7 +752,7 @@ def _format_hierarchical_rows(self): def _has_aliases(self) -> bool: return bool(isinstance(self.header, (tuple, list, np.ndarray, ABCIndex))) - def _generate_body(self, coloffset: int): + def _generate_body(self, coloffset: int) -> Iterator[ExcelCell]: if self.styler is None: styles = None else: @@ -769,7 +769,7 @@ def _generate_body(self, coloffset: int): xlstyle = self.style_converter(";".join(styles[i, colidx])) yield ExcelCell(self.rowcounter + i, colidx + coloffset, val, xlstyle) - def get_formatted_cells(self): + def get_formatted_cells(self) -> Iterator[ExcelCell]: for cell in itertools.chain(self._format_header(), self._format_body()): cell.val = self._format_value(cell.val) yield cell From 1ff8cb8a88774a41f0bb383aa980cfa0696155c2 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 15 Nov 2020 22:25:48 +0700 Subject: [PATCH 07/19] DOC: add docstring for _has_aliases --- pandas/io/formats/excel.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 62288cc5936d6..61f63e8e1ec38 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -750,6 +750,7 @@ def _format_hierarchical_rows(self) -> Iterator[ExcelCell]: @property def _has_aliases(self) -> bool: + """Whether the aliases for column names are present.""" return bool(isinstance(self.header, (tuple, list, np.ndarray, ABCIndex))) def _generate_body(self, coloffset: int) -> Iterator[ExcelCell]: From ee09d4d121e506e909b3771e1beb8d043b2628a7 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 15 Nov 2020 23:07:20 +0700 Subject: [PATCH 08/19] CLN: remove unnecessary bool conversion --- pandas/io/formats/excel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 61f63e8e1ec38..73a25de353c32 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -751,7 +751,7 @@ def _format_hierarchical_rows(self) -> Iterator[ExcelCell]: @property def _has_aliases(self) -> bool: """Whether the aliases for column names are present.""" - return bool(isinstance(self.header, (tuple, list, np.ndarray, ABCIndex))) + return isinstance(self.header, (tuple, list, np.ndarray, ABCIndex)) def _generate_body(self, coloffset: int) -> Iterator[ExcelCell]: if self.styler is None: From c5e1d9ce39a45f7b80093a3df22388019c12215c Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 15 Nov 2020 23:16:28 +0700 Subject: [PATCH 09/19] REF: update handling merge* kwargs vs spans[i] --- pandas/io/formats/excel.py | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 73a25de353c32..f1beda14665fc 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -29,7 +29,13 @@ class ExcelCell: __slots__ = __fields__ def __init__( - self, row: int, col: int, val, style=None, mergestart=None, mergeend=None + self, + row: int, + col: int, + val, + style=None, + mergestart: Optional[int] = None, + mergeend: Optional[int] = None, ): self.row = row self.col = col @@ -559,21 +565,14 @@ def _format_header_mi(self) -> Iterator[ExcelCell]: ): values = levels.take(level_codes) for i in spans: - kwargs = dict( + yield ExcelCell( row=lnum, col=coloffset + i + 1, val=values[i], style=self.header_style, + mergestart=lnum if spans[i] > 1 else None, + mergeend=coloffset + i + spans[i] if spans[i] > 1 else None, ) - - if spans[i] > 1: - kwargs.update( - { - "mergestart": lnum, - "mergeend": coloffset + i + spans[i], - } - ) - yield ExcelCell(**kwargs) else: # Format in legacy format with dots to indicate levels. for i, values in enumerate(zip(*level_strs)): @@ -718,20 +717,18 @@ def _format_hierarchical_rows(self) -> Iterator[ExcelCell]: ) for i in spans: - kwargs = dict( + yield ExcelCell( row=self.rowcounter + i, col=gcolidx, val=values[i], style=self.header_style, + mergestart=( + self.rowcounter + i + spans[i] - 1 + if spans[i] > 1 + else None + ), + mergeend=gcolidx if spans[i] > 1 else None, ) - if spans[i] > 1: - kwargs.update( - { - "mergestart": self.rowcounter + i + spans[i] - 1, - "mergeend": gcolidx, - } - ) - yield ExcelCell(**kwargs) gcolidx += 1 else: From 7d27a71e8554321f0bb14e2f260a5aa286646b39 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Sun, 15 Nov 2020 23:39:59 +0700 Subject: [PATCH 10/19] REF: avoid explicit header check for 4 isinstances --- pandas/io/formats/excel.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index f1beda14665fc..002afd569bd47 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -5,7 +5,7 @@ from functools import reduce import itertools import re -from typing import Callable, Dict, Iterator, Mapping, Optional, Sequence, Sized, Union +from typing import Callable, Dict, Iterator, Mapping, Optional, Sequence, Union, cast import warnings import numpy as np @@ -14,7 +14,6 @@ from pandas.core.dtypes import missing from pandas.core.dtypes.common import is_float, is_scalar -from pandas.core.dtypes.generic import ABCIndex from pandas import DataFrame, Index, MultiIndex, PeriodIndex import pandas.core.common as com @@ -592,14 +591,14 @@ def _format_header_regular(self) -> Iterator[ExcelCell]: colnames = self.columns if self._has_aliases: - assert isinstance(self.header, Sized) - if len(self.header) != len(self.columns): + header = cast(Sequence, self.header) + if len(header) != len(self.columns): raise ValueError( f"Writing {len(self.columns)} cols " - f"but got {len(self.header)} aliases" + f"but got {len(header)} aliases" ) else: - colnames = self.header + colnames = header for colindex, colname in enumerate(colnames): yield ExcelCell( @@ -748,7 +747,7 @@ def _format_hierarchical_rows(self) -> Iterator[ExcelCell]: @property def _has_aliases(self) -> bool: """Whether the aliases for column names are present.""" - return isinstance(self.header, (tuple, list, np.ndarray, ABCIndex)) + return not isinstance(self.header, bool) def _generate_body(self, coloffset: int) -> Iterator[ExcelCell]: if self.styler is None: From 2f02811048623b502c2369052bb3cffe160a787e Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Mon, 16 Nov 2020 00:19:37 +0700 Subject: [PATCH 11/19] TYP: override self.header when casting --- 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 002afd569bd47..e27c6e67e6ca9 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -591,14 +591,14 @@ def _format_header_regular(self) -> Iterator[ExcelCell]: colnames = self.columns if self._has_aliases: - header = cast(Sequence, self.header) - if len(header) != len(self.columns): + self.header = cast(Sequence, self.header) + if len(self.header) != len(self.columns): raise ValueError( f"Writing {len(self.columns)} cols " - f"but got {len(header)} aliases" + f"but got {len(self.header)} aliases" ) else: - colnames = header + colnames = self.header for colindex, colname in enumerate(colnames): yield ExcelCell( From 9fcfb32f10e2dfa9fd9b8708a06521eb00b3fd76 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 17 Nov 2020 17:50:57 +0700 Subject: [PATCH 12/19] TYP: replace Iterator with Iterable --- pandas/io/formats/excel.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 4a3e39c342e5b..8015512a93d10 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -5,7 +5,7 @@ from functools import reduce import itertools import re -from typing import Callable, Dict, Iterator, Mapping, Optional, Sequence, Union, cast +from typing import Callable, Dict, Iterable, Mapping, Optional, Sequence, Union, cast import warnings import numpy as np @@ -527,7 +527,7 @@ def _format_value(self, val): ) return val - def _format_header_mi(self) -> Iterator[ExcelCell]: + def _format_header_mi(self) -> Iterable[ExcelCell]: if self.columns.nlevels > 1: if not self.index: raise NotImplementedError( @@ -580,7 +580,7 @@ def _format_header_mi(self) -> Iterator[ExcelCell]: self.rowcounter = lnum - def _format_header_regular(self) -> Iterator[ExcelCell]: + def _format_header_regular(self) -> Iterable[ExcelCell]: if self._has_aliases or self.header: coloffset = 0 @@ -605,7 +605,7 @@ def _format_header_regular(self) -> Iterator[ExcelCell]: self.rowcounter, colindex + coloffset, colname, self.header_style ) - def _format_header(self) -> Iterator[ExcelCell]: + def _format_header(self) -> Iterable[ExcelCell]: if isinstance(self.columns, MultiIndex): gen = self._format_header_mi() else: @@ -627,13 +627,13 @@ def _format_header(self) -> Iterator[ExcelCell]: self.rowcounter += 1 return itertools.chain(gen, gen2) - def _format_body(self) -> Iterator[ExcelCell]: + def _format_body(self) -> Iterable[ExcelCell]: if isinstance(self.df.index, MultiIndex): return self._format_hierarchical_rows() else: return self._format_regular_rows() - def _format_regular_rows(self) -> Iterator[ExcelCell]: + def _format_regular_rows(self) -> Iterable[ExcelCell]: if self._has_aliases or self.header: self.rowcounter += 1 @@ -671,7 +671,7 @@ def _format_regular_rows(self) -> Iterator[ExcelCell]: yield from self._generate_body(coloffset) - def _format_hierarchical_rows(self) -> Iterator[ExcelCell]: + def _format_hierarchical_rows(self) -> Iterable[ExcelCell]: if self._has_aliases or self.header: self.rowcounter += 1 @@ -749,7 +749,7 @@ def _has_aliases(self) -> bool: """Whether the aliases for column names are present.""" return not isinstance(self.header, bool) - def _generate_body(self, coloffset: int) -> Iterator[ExcelCell]: + def _generate_body(self, coloffset: int) -> Iterable[ExcelCell]: if self.styler is None: styles = None else: @@ -766,7 +766,7 @@ def _generate_body(self, coloffset: int) -> Iterator[ExcelCell]: xlstyle = self.style_converter(";".join(styles[i, colidx])) yield ExcelCell(self.rowcounter + i, colidx + coloffset, val, xlstyle) - def get_formatted_cells(self) -> Iterator[ExcelCell]: + def get_formatted_cells(self) -> Iterable[ExcelCell]: for cell in itertools.chain(self._format_header(), self._format_body()): cell.val = self._format_value(cell.val) yield cell From aa48fb6405d74562eb8df199e020ef85d3ddc6de Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 17 Nov 2020 17:51:29 +0700 Subject: [PATCH 13/19] REF: de-duplicate using local var is_multilevel --- pandas/io/formats/excel.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 8015512a93d10..c18a6e3f534b8 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -563,14 +563,15 @@ def _format_header_mi(self) -> Iterable[ExcelCell]: zip(level_lengths, columns.levels, columns.codes) ): values = levels.take(level_codes) - for i in spans: + for i, span_val in spans.items(): + is_multilevel = span_val > 1 yield ExcelCell( row=lnum, col=coloffset + i + 1, val=values[i], style=self.header_style, - mergestart=lnum if spans[i] > 1 else None, - mergeend=coloffset + i + spans[i] if spans[i] > 1 else None, + mergestart=lnum if is_multilevel else None, + mergeend=coloffset + i + span_val if is_multilevel else None, ) else: # Format in legacy format with dots to indicate levels. @@ -715,7 +716,8 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]: fill_value=levels._na_value, ) - for i in spans: + for i, span_val in spans.items(): + is_multilevel = span_val > 1 yield ExcelCell( row=self.rowcounter + i, col=gcolidx, @@ -723,10 +725,10 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]: style=self.header_style, mergestart=( self.rowcounter + i + spans[i] - 1 - if spans[i] > 1 + if is_multilevel else None ), - mergeend=gcolidx if spans[i] > 1 else None, + mergeend=gcolidx if is_multilevel else None, ) gcolidx += 1 From f4abe27ae6775b4b3dfec05b40cda9320e3b94ea Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 17 Nov 2020 18:16:31 +0700 Subject: [PATCH 14/19] REF: update _has_aliases using is_list_like --- pandas/io/formats/excel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index c18a6e3f534b8..f7c6af613eee5 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -10,6 +10,7 @@ import numpy as np +from pandas._libs.lib import is_list_like from pandas._typing import Label, StorageOptions from pandas.core.dtypes import missing @@ -749,7 +750,7 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]: @property def _has_aliases(self) -> bool: """Whether the aliases for column names are present.""" - return not isinstance(self.header, bool) + return is_list_like(self.header) def _generate_body(self, coloffset: int) -> Iterable[ExcelCell]: if self.styler is None: From f46839791ac865c97f51e7ad86676a0ad7f75206 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 17 Nov 2020 18:17:56 +0700 Subject: [PATCH 15/19] DOC: header accepts sequence of strings --- pandas/io/formats/excel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index f7c6af613eee5..1e7cc70511138 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -429,7 +429,7 @@ class ExcelFormatter: Format string for floating point numbers cols : sequence, optional Columns to write - header : boolean or list of string, default True + header : boolean or sequence of strings, default True Write out column names. If a list of string is given it is assumed to be aliases for the column names index : boolean, default True From 4cf8f837e9c8f0cdb2bbc65a201732b63857bacf Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 17 Nov 2020 23:16:00 +0700 Subject: [PATCH 16/19] CLN: rename is_multlevel -> spans_multiple_cells --- pandas/io/formats/excel.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 1e7cc70511138..0de5f8d4c1600 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -565,14 +565,16 @@ def _format_header_mi(self) -> Iterable[ExcelCell]: ): values = levels.take(level_codes) for i, span_val in spans.items(): - is_multilevel = span_val > 1 + spans_multiple_cells = span_val > 1 yield ExcelCell( row=lnum, col=coloffset + i + 1, val=values[i], style=self.header_style, - mergestart=lnum if is_multilevel else None, - mergeend=coloffset + i + span_val if is_multilevel else None, + mergestart=lnum if spans_multiple_cells else None, + mergeend=( + coloffset + i + span_val if spans_multiple_cells else None + ), ) else: # Format in legacy format with dots to indicate levels. @@ -718,7 +720,7 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]: ) for i, span_val in spans.items(): - is_multilevel = span_val > 1 + spans_multiple_cells = span_val > 1 yield ExcelCell( row=self.rowcounter + i, col=gcolidx, @@ -726,10 +728,10 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]: style=self.header_style, mergestart=( self.rowcounter + i + spans[i] - 1 - if is_multilevel + if spans_multiple_cells else None ), - mergeend=gcolidx if is_multilevel else None, + mergeend=gcolidx if spans_multiple_cells else None, ) gcolidx += 1 From 8a9a40afc447b798282ed37ad45c3df0098f55c3 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov <41443370+ivanovmg@users.noreply.github.com> Date: Tue, 24 Nov 2020 18:08:05 +0700 Subject: [PATCH 17/19] REF: implement suggestion Co-authored-by: Simon Hawkins --- pandas/io/formats/excel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 0de5f8d4c1600..2ce1e6a27ecf6 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -727,7 +727,7 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]: val=values[i], style=self.header_style, mergestart=( - self.rowcounter + i + spans[i] - 1 + self.rowcounter + i + spanval - 1 if spans_multiple_cells else None ), From 2ee661c834628041fd6ea8f4ae39009be169b70e Mon Sep 17 00:00:00 2001 From: Maxim Ivanov <41443370+ivanovmg@users.noreply.github.com> Date: Tue, 24 Nov 2020 18:08:32 +0700 Subject: [PATCH 18/19] REF: implement suggestion Co-authored-by: Simon Hawkins --- pandas/io/formats/excel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 2ce1e6a27ecf6..b955b99df9949 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -429,7 +429,7 @@ class ExcelFormatter: Format string for floating point numbers cols : sequence, optional Columns to write - header : boolean or sequence of strings, default True + header : boolean or sequence of str, default True Write out column names. If a list of string is given it is assumed to be aliases for the column names index : boolean, default True From c88aff935d957765f2affb0b43c43ad64e9f53fb Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 24 Nov 2020 19:04:19 +0700 Subject: [PATCH 19/19] FIX: fix typo --- pandas/io/formats/excel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index b955b99df9949..25885552239d6 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -727,7 +727,7 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]: val=values[i], style=self.header_style, mergestart=( - self.rowcounter + i + spanval - 1 + self.rowcounter + i + span_val - 1 if spans_multiple_cells else None ),