Skip to content

ENH: max_rows max_cols kw for Styler.to_html + styler.options #42972

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 25 commits into from
Sep 9, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7e30dfe
max rows
attack68 Aug 10, 2021
4f78bc4
addt to docs
attack68 Aug 10, 2021
3ccdfd7
add test
attack68 Aug 10, 2021
7bd12e0
add test
attack68 Aug 10, 2021
adbefdd
add test
attack68 Aug 10, 2021
e6eaa69
doc edit
attack68 Aug 10, 2021
5f1406e
change cols -->> columns
attack68 Aug 11, 2021
b25a191
change cols -->> columns
attack68 Aug 11, 2021
d842d25
Merge remote-tracking branch 'upstream/master' into styler_max_rows_cols
attack68 Aug 17, 2021
7427bec
Merge remote-tracking branch 'upstream/master' into styler_max_rows_cols
attack68 Aug 22, 2021
c7e7e55
add to docs
attack68 Aug 23, 2021
936ca49
Merge remote-tracking branch 'upstream/master' into styler_max_rows_cols
attack68 Aug 29, 2021
9a889cb
whats new
attack68 Aug 29, 2021
8a55102
Merge remote-tracking branch 'upstream/master' into styler_max_rows_cols
attack68 Aug 31, 2021
0c06716
typing fix
attack68 Aug 31, 2021
6e5e0e7
Merge remote-tracking branch 'upstream/master' into styler_max_rows_cols
attack68 Sep 4, 2021
8bf52b2
Merge remote-tracking branch 'upstream/master' into styler_max_rows_cols
attack68 Sep 7, 2021
5ff4b47
doc fix
attack68 Sep 7, 2021
038ba17
tests expand
attack68 Sep 7, 2021
b8638fe
fix tests due to conflict in substring names
attack68 Sep 7, 2021
c16fffa
Merge remote-tracking branch 'upstream/master' into styler_max_rows_cols
attack68 Sep 8, 2021
7fffc91
Merge remote-tracking branch 'upstream/master' into styler_max_rows_cols
attack68 Sep 8, 2021
c526343
fix tests
attack68 Sep 8, 2021
3a6dd58
fix tests
attack68 Sep 8, 2021
5d6c6d8
fix tests
attack68 Sep 8, 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: 4 additions & 0 deletions doc/source/user_guide/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,10 @@ styler.sparse.columns True "Sparsify" MultiIndex displ
in Styler output.
styler.render.max_elements 262144 Maximum number of datapoints that Styler will render
trimming either rows, columns or both to fit.
styler.render.max_rows None Maximum number of rows that Styler will render. By default
this is dynamic based on ``max_elements``.
styler.render.max_columns None Maximum number of columns that Styler will render. By default
this is dynamic based on ``max_elements``.
======================================= ============ ==================================


Expand Down
3 changes: 2 additions & 1 deletion doc/source/whatsnew/v1.4.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ Styler
- Styling of indexing has been added, with :meth:`.Styler.apply_index` and :meth:`.Styler.applymap_index`. These mirror the signature of the methods already used to style data values, and work with both HTML and LaTeX format (:issue:`41893`).
- :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`` and ``sparse_columns`` (:issue:`41946`)
- :meth:`.Styler.to_html` introduces keyword arguments ``sparse_index``, ``sparse_columns``, ``max_rows`` and ``max_columns`` (:issue:`41946` :issue:`42972`)
- Keyword argument ``level`` is added to :meth:`.Styler.hide_index` and :meth:`.Styler.hide_columns` for optionally controlling hidden levels in a MultiIndex (:issue:`25475`)
- Global options have been extended to configure default ``Styler`` properties including maximum display options (:issue:`41395`)

There are also bug fixes and deprecations listed below.

Expand Down
26 changes: 26 additions & 0 deletions pandas/core/config_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,18 @@ def register_converter_cb(key):
trimming will occur over columns, rows or both if needed.
"""

styler_max_rows = """
: int, optional
The maximum number of rows that will be rendered. May still be reduced to
satsify ``max_elements``, which takes precedence.
"""

styler_max_columns = """
: int, optional
The maximum number of columns that will be rendered. May still be reduced to
satsify ``max_elements``, which takes precedence.
"""

with cf.config_prefix("styler"):
cf.register_option("sparse.index", True, styler_sparse_index_doc, validator=bool)

Expand All @@ -775,3 +787,17 @@ def register_converter_cb(key):
styler_max_elements,
validator=is_nonnegative_int,
)

cf.register_option(
"render.max_rows",
None,
styler_max_rows,
validator=is_nonnegative_int,
)

cf.register_option(
"render.max_columns",
None,
styler_max_columns,
validator=is_nonnegative_int,
)
9 changes: 9 additions & 0 deletions pandas/io/formats/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,8 @@ def to_html(
table_attributes: str | None = None,
sparse_index: bool | None = None,
sparse_columns: bool | None = None,
max_rows: int | None = None,
max_columns: int | None = None,
encoding: str | None = None,
doctype_html: bool = False,
exclude_styles: bool = False,
Expand Down Expand Up @@ -875,6 +877,11 @@ def to_html(
will display each explicit level element in a hierarchical key for each
column. Defaults to ``pandas.options.styler.sparse.columns`` value.

.. versionadded:: 1.4.0
max_rows, max_columns : int, optional
The maximum rows and/or columns that will be rendered. Defaults to
``pandas.options.styler.render.max_rows/max_columns``.

.. versionadded:: 1.4.0
encoding : str, optional
Character encoding setting for file output, and HTML meta tags,
Expand Down Expand Up @@ -917,6 +924,8 @@ def to_html(
html = obj._render_html(
sparse_index=sparse_index,
sparse_columns=sparse_columns,
max_rows=max_rows,
max_cols=max_columns,
exclude_styles=exclude_styles,
encoding=encoding if encoding else "utf-8",
doctype_html=doctype_html,
Expand Down
50 changes: 45 additions & 5 deletions pandas/io/formats/style_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,21 @@ def __init__(
tuple[int, int], Callable[[Any], str]
] = defaultdict(lambda: partial(_default_formatter, precision=def_precision))

def _render_html(self, sparse_index: bool, sparse_columns: bool, **kwargs) -> str:
def _render_html(
self,
sparse_index: bool,
sparse_columns: bool,
max_rows: int | None = None,
max_cols: int | None = None,
**kwargs,
) -> str:
"""
Renders the ``Styler`` including all applied styles to HTML.
Generates a dict with necessary kwargs passed to jinja2 template.
"""
self._compute()
# TODO: namespace all the pandas keys
d = self._translate(sparse_index, sparse_columns)
d = self._translate(sparse_index, sparse_columns, max_rows, max_cols)
d.update(kwargs)
return self.template_html.render(
**d,
Expand Down Expand Up @@ -161,7 +168,14 @@ def _compute(self):
r = func(self)(*args, **kwargs)
return r

def _translate(self, sparse_index: bool, sparse_cols: bool, blank: str = " "):
def _translate(
self,
sparse_index: bool,
sparse_cols: bool,
max_rows: int | None = None,
max_cols: int | None = None,
blank: str = " ",
):
"""
Process Styler data and settings into a dict for template rendering.

Expand All @@ -176,6 +190,10 @@ def _translate(self, sparse_index: bool, sparse_cols: bool, blank: str = " 
sparse_cols : bool
Whether to sparsify the columns or print all hierarchical column elements.
Upstream defaults are typically to `pandas.options.styler.sparse.columns`.
blank : str
Entry to top-left blank cells.
max_rows, max_cols : int, optional
Specific max rows and cols. max_elements always take precedence in render.

Returns
-------
Expand All @@ -201,8 +219,14 @@ def _translate(self, sparse_index: bool, sparse_cols: bool, blank: str = " 
}

max_elements = get_option("styler.render.max_elements")
max_rows = max_rows if max_rows else get_option("styler.render.max_rows")
max_cols = max_cols if max_cols else get_option("styler.render.max_columns")
max_rows, max_cols = _get_trimming_maximums(
len(self.data.index), len(self.data.columns), max_elements
len(self.data.index),
len(self.data.columns),
max_elements,
max_rows,
max_cols,
)

self.cellstyle_map_columns: DefaultDict[
Expand Down Expand Up @@ -813,7 +837,14 @@ def _element(
}


def _get_trimming_maximums(rn, cn, max_elements, scaling_factor=0.8):
def _get_trimming_maximums(
rn,
cn,
max_elements,
max_rows=None,
max_cols=None,
scaling_factor=0.8,
) -> tuple[int, int]:
"""
Recursively reduce the number of rows and columns to satisfy max elements.

Expand All @@ -823,6 +854,10 @@ def _get_trimming_maximums(rn, cn, max_elements, scaling_factor=0.8):
The number of input rows / columns
max_elements : int
The number of allowable elements
max_rows, max_cols : int, optional
Directly specify an initial maximum rows or columns before compression.
scaling_factor : float
Factor at which to reduce the number of rows / columns to fit.

Returns
-------
Expand All @@ -836,6 +871,11 @@ def scale_down(rn, cn):
else:
return int(rn * scaling_factor), cn

if max_rows:
Copy link
Contributor

Choose a reason for hiding this comment

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

this exercised enough in tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added specific cases

rn = max_rows if rn > max_rows else rn
if max_cols:
cn = max_cols if cn > max_cols else cn

while rn * cn > max_elements:
rn, cn = scale_down(rn, cn)

Expand Down
13 changes: 13 additions & 0 deletions pandas/tests/io/formats/style/test_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,3 +430,16 @@ def test_applymap_header_cell_ids(styler, index, columns):
'<th id="T__level0_col0" class="col_heading level0 col0" >A</th>' in result
) is columns
assert ("#T__level0_col0 {\n attr: val;\n}" in result) is columns


@pytest.mark.parametrize("rows", [True, False])
@pytest.mark.parametrize("cols", [True, False])
def test_maximums(styler_mi, rows, cols):
result = styler_mi.to_html(
max_rows=2 if rows else None,
max_columns=2 if cols else None,
)

assert ">5</td>" in result # [[0,1], [4,5]] always visible
assert (">8</td>" in result) is not rows # first trimmed vertical element
assert (">2</td>" in result) is not cols # first trimmed horizontal element
35 changes: 28 additions & 7 deletions pandas/tests/io/formats/style/test_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,21 +154,42 @@ def test_trimming_maximum():
rn, cn = _get_trimming_maximums(1000, 3, 750, scaling_factor=0.5)
assert (rn, cn) == (250, 3)

rn, cn = _get_trimming_maximums(100, 100, 200, 4, 6, scaling_factor=0.5)
assert (rn, cn) == (4, 6)

def test_render_trimming():

@pytest.mark.parametrize(
"option, val",
[
("styler.render.max_elements", 6),
("styler.render.max_rows", 3),
],
)
def test_render_trimming_rows(option, val):
# test auto and specific trimming of rows
df = DataFrame(np.arange(120).reshape(60, 2))
with pd.option_context("styler.render.max_elements", 6):
with pd.option_context(option, val):
ctx = df.style._translate(True, True)
assert len(ctx["head"][0]) == 3 # index + 2 data cols
assert len(ctx["body"]) == 4 # 3 data rows + trimming row
assert len(ctx["body"][0]) == 3 # index + 2 data cols

df = DataFrame(np.arange(120).reshape(12, 10))
with pd.option_context("styler.render.max_elements", 6):

@pytest.mark.parametrize(
"option, val",
[
("styler.render.max_elements", 6),
("styler.render.max_columns", 2),
],
)
def test_render_trimming_cols(option, val):
# test auto and specific trimming of cols
df = DataFrame(np.arange(30).reshape(3, 10))
with pd.option_context(option, val):
ctx = df.style._translate(True, True)
assert len(ctx["head"][0]) == 4 # index + 2 data cols + trimming row
assert len(ctx["body"]) == 4 # 3 data rows + trimming row
assert len(ctx["body"][0]) == 4 # index + 2 data cols + trimming row
assert len(ctx["head"][0]) == 4 # index + 2 data cols + trimming col
assert len(ctx["body"]) == 3 # 3 data rows
assert len(ctx["body"][0]) == 4 # index + 2 data cols + trimming col


def test_render_trimming_mi():
Expand Down