Skip to content

ENH: Styler.apply(map)_index made compatible with Styler.to_excel #41995

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 54 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
ece8855
add apply across index
attack68 Jun 9, 2021
a3a88e5
add applymap across index
attack68 Jun 9, 2021
066e4f3
improve docs
attack68 Jun 9, 2021
50dbcc7
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Jun 11, 2021
ef5839f
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Jun 12, 2021
5e4c1c0
add column header styling and amend tests
attack68 Jun 12, 2021
20ac7e0
doc sharing
attack68 Jun 13, 2021
26c5340
doc fix
attack68 Jun 13, 2021
2437b72
doc fix
attack68 Jun 13, 2021
312a6e6
collapse the cellstyle maps
attack68 Jun 13, 2021
553426f
add basic test
attack68 Jun 13, 2021
f01dfee
add basic test
attack68 Jun 13, 2021
f940165
parametrise tests
attack68 Jun 13, 2021
6f5b46c
test for raises ValueError
attack68 Jun 13, 2021
75cf6ca
test html working
attack68 Jun 13, 2021
d654139
test html working
attack68 Jun 13, 2021
728091a
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Jun 14, 2021
17787ef
whats new 1.4.0
attack68 Jun 14, 2021
e2240f9
make apply_header compatible with to_excel
attack68 Jun 14, 2021
6a198d2
whats new
attack68 Jun 14, 2021
248de0d
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Jun 20, 2021
8bc3e53
Merge branch 'styler_apply_index_specific' into styler_apply_index_sp…
attack68 Jun 20, 2021
321458e
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Jun 29, 2021
c104c3d
Merge branch 'styler_apply_index_specific' into styler_apply_index_sp…
attack68 Jun 29, 2021
13b5fe5
add tests for new cases
attack68 Jun 29, 2021
ed58798
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Jul 13, 2021
95a9a20
Merge branch 'styler_apply_index_specific' into styler_apply_index_sp…
attack68 Jul 13, 2021
8d8e88f
update tests
attack68 Jul 14, 2021
e17d766
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Jul 30, 2021
c56d736
Merge branch 'styler_apply_index_specific' into styler_apply_index_sp…
attack68 Jul 30, 2021
f70bf65
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Aug 6, 2021
228246b
Merge branch 'styler_apply_index_specific' into styler_apply_index_sp…
attack68 Aug 6, 2021
ec8c59e
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Aug 12, 2021
aa6591f
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Aug 17, 2021
27a5c7a
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Aug 20, 2021
da91d9b
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Aug 28, 2021
0832cbe
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Aug 31, 2021
c495aae
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Sep 7, 2021
96c6261
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Sep 20, 2021
889d794
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Sep 22, 2021
79eb10c
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Sep 24, 2021
d3fd856
excel styler tests
attack68 Sep 25, 2021
702a2e0
add tests
attack68 Sep 26, 2021
77704cb
add tests
attack68 Sep 26, 2021
cd9fe8b
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Oct 1, 2021
fe42e80
function
attack68 Oct 1, 2021
6391576
function
attack68 Oct 1, 2021
ab5e6a0
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Oct 2, 2021
ba4340c
new CssExcelCell class
attack68 Oct 2, 2021
20d0063
direct args
attack68 Oct 2, 2021
0913da9
comment on styler import jinja2 for to_excel
attack68 Oct 4, 2021
09600f8
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Oct 9, 2021
9f8b8fa
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Oct 11, 2021
04eebeb
Merge remote-tracking branch 'upstream/master' into styler_apply_inde…
attack68 Oct 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions doc/source/whatsnew/v1.4.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ Styler

:class:`.Styler` has been further developed in 1.4.0. The following enhancements have been made:

- Styling and formatting of indexes has been added, with :meth:`.Styler.apply_index`, :meth:`.Styler.applymap_index` and :meth:`.Styler.format_index`. These mirror the signature of the methods already used to style and format data values, and work with both HTML and LaTeX format (:issue:`41893`, :issue:`43101`).
- :meth:`.Styler.bar` introduces additional arguments to control alignment, display and colors (:issue:`26070`, :issue:`36419`, :issue:`43662`), and it also validates the input arguments ``width`` and ``height`` (:issue:`42511`).
- Styling and formatting of indexes has been added, with :meth:`.Styler.apply_index`, :meth:`.Styler.applymap_index` and :meth:`.Styler.format_index`. These mirror the signature of the methods already used to style and format data values, and work with both HTML, LaTeX and Excel format (:issue:`41893`, :issue:`43101`, :issue:`41993`, :issue:`41995`).
- :meth:`.Styler.bar` introduces additional arguments to control alignment and display (:issue:`26070`, :issue:`36419`), and it also validates the input arguments ``width`` and ``height`` (:issue:`42511`).
- :meth:`.Styler.to_latex` introduces keyword argument ``environment``, which also allows a specific "longtable" entry through a separate jinja2 template (:issue:`41866`).
- :meth:`.Styler.to_html` introduces keyword arguments ``sparse_index``, ``sparse_columns``, ``bold_headers``, ``caption``, ``max_rows`` and ``max_columns`` (:issue:`41946`, :issue:`43149`, :issue:`42972`).
- Keyword arguments ``level`` and ``names`` added to :meth:`.Styler.hide_index` and :meth:`.Styler.hide_columns` for additional control of visibility of MultiIndexes and index names (:issue:`25475`, :issue:`43404`, :issue:`43346`)
Expand Down
122 changes: 89 additions & 33 deletions pandas/io/formats/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import itertools
import re
from typing import (
Any,
Callable,
Hashable,
Iterable,
Expand Down Expand Up @@ -70,6 +71,26 @@ def __init__(
self.mergeend = mergeend


class CssExcelCell(ExcelCell):
def __init__(
self,
row: int,
col: int,
val,
style: dict | None,
css_styles: dict[tuple[int, int], list[tuple[str, Any]]] | None,
css_row: int,
css_col: int,
css_converter: Callable | None,
**kwargs,
):
if css_styles and css_converter:
css = ";".join(a + ":" + str(v) for (a, v) in css_styles[css_row, css_col])
style = css_converter(css)

return super().__init__(row=row, col=col, val=val, style=style, **kwargs)


class CSSToExcelConverter:
"""
A callable for converting CSS declarations to ExcelWriter styles
Expand Down Expand Up @@ -472,12 +493,14 @@ def __init__(
self.na_rep = na_rep
if not isinstance(df, DataFrame):
self.styler = df
self.styler._compute() # calculate applied styles
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this need to be called? e.g. shouldn't this be in render?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok i see your expl. i guess this is fine.

df = df.data
if style_converter is None:
style_converter = CSSToExcelConverter()
self.style_converter = style_converter
self.style_converter: Callable | None = style_converter
else:
self.styler = None
self.style_converter = None
self.df = df
if cols is not None:

Expand Down Expand Up @@ -567,22 +590,35 @@ def _format_header_mi(self) -> Iterable[ExcelCell]:
):
values = levels.take(level_codes)
for i, span_val in spans.items():
spans_multiple_cells = span_val > 1
yield ExcelCell(
mergestart, mergeend = None, None
if span_val > 1:
mergestart, mergeend = lnum, coloffset + i + span_val
yield CssExcelCell(
row=lnum,
col=coloffset + i + 1,
val=values[i],
style=self.header_style,
mergestart=lnum if spans_multiple_cells else None,
mergeend=(
coloffset + i + span_val if spans_multiple_cells else None
),
css_styles=getattr(self.styler, "ctx_columns", None),
css_row=lnum,
css_col=i,
css_converter=self.style_converter,
mergestart=mergestart,
mergeend=mergeend,
)
else:
# Format in legacy format with dots to indicate levels.
for i, values in enumerate(zip(*level_strs)):
v = ".".join(map(pprint_thing, values))
yield ExcelCell(lnum, coloffset + i + 1, v, self.header_style)
yield CssExcelCell(
row=lnum,
col=coloffset + i + 1,
val=v,
style=self.header_style,
css_styles=getattr(self.styler, "ctx_columns", None),
css_row=lnum,
css_col=i,
css_converter=self.style_converter,
)

self.rowcounter = lnum

Expand All @@ -607,8 +643,15 @@ def _format_header_regular(self) -> Iterable[ExcelCell]:
colnames = self.header

for colindex, colname in enumerate(colnames):
yield ExcelCell(
self.rowcounter, colindex + coloffset, colname, self.header_style
yield CssExcelCell(
row=self.rowcounter,
col=colindex + coloffset,
val=colname,
style=self.header_style,
css_styles=getattr(self.styler, "ctx_columns", None),
css_row=0,
css_col=colindex,
css_converter=self.style_converter,
)

def _format_header(self) -> Iterable[ExcelCell]:
Expand Down Expand Up @@ -668,8 +711,16 @@ def _format_regular_rows(self) -> Iterable[ExcelCell]:
index_values = self.df.index.to_timestamp()

for idx, idxval in enumerate(index_values):
yield ExcelCell(self.rowcounter + idx, 0, idxval, self.header_style)

yield CssExcelCell(
row=self.rowcounter + idx,
col=0,
val=idxval,
style=self.header_style,
css_styles=getattr(self.styler, "ctx_index", None),
css_row=idx,
css_col=0,
css_converter=self.style_converter,
)
coloffset = 1
else:
coloffset = 0
Expand Down Expand Up @@ -721,30 +772,37 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]:
)

for i, span_val in spans.items():
spans_multiple_cells = span_val > 1
yield ExcelCell(
mergestart, mergeend = None, None
if span_val > 1:
mergestart = self.rowcounter + i + span_val - 1
mergeend = gcolidx
yield CssExcelCell(
row=self.rowcounter + i,
col=gcolidx,
val=values[i],
style=self.header_style,
mergestart=(
self.rowcounter + i + span_val - 1
if spans_multiple_cells
else None
),
mergeend=gcolidx if spans_multiple_cells else None,
css_styles=getattr(self.styler, "ctx_index", None),
css_row=i,
css_col=gcolidx,
css_converter=self.style_converter,
mergestart=mergestart,
mergeend=mergeend,
)
gcolidx += 1

else:
# Format hierarchical rows with non-merged values.
for indexcolvals in zip(*self.df.index):
for idx, indexcolval in enumerate(indexcolvals):
yield ExcelCell(
yield CssExcelCell(
row=self.rowcounter + idx,
col=gcolidx,
val=indexcolval,
style=self.header_style,
css_styles=getattr(self.styler, "ctx_index", None),
css_row=idx,
css_col=gcolidx,
css_converter=self.style_converter,
)
gcolidx += 1

Expand All @@ -756,22 +814,20 @@ def _has_aliases(self) -> bool:
return is_list_like(self.header)

def _generate_body(self, coloffset: int) -> Iterable[ExcelCell]:
if self.styler is None:
styles = None
else:
styles = self.styler._compute().ctx
if not styles:
styles = None
xlstyle = None

# Write the body of the frame data series by series.
for colidx in range(len(self.columns)):
series = self.df.iloc[:, colidx]
for i, val in enumerate(series):
if styles is not None:
css = ";".join([a + ":" + str(v) for (a, v) in styles[i, colidx]])
xlstyle = self.style_converter(css)
yield ExcelCell(self.rowcounter + i, colidx + coloffset, val, xlstyle)
yield CssExcelCell(
row=self.rowcounter + i,
col=colidx + coloffset,
val=val,
style=None,
css_styles=getattr(self.styler, "ctx", None),
css_row=i,
css_col=colidx,
css_converter=self.style_converter,
)

def get_formatted_cells(self) -> Iterable[ExcelCell]:
for cell in itertools.chain(self._format_header(), self._format_body()):
Expand Down
Loading