Skip to content

API: make hide_columns and hide_index have a consistent signature and function in Styler #41266

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 33 commits into from
Jun 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b0bfab7
ENH: make hide_index and hide_columns consistent in functionality
attack68 May 2, 2021
c615b08
DOC: improve specificity
attack68 May 2, 2021
38b7407
TST: add tests for hide_index(subset=)
attack68 May 2, 2021
40504db
TST: add tests for hide_columns(subset=None)
attack68 May 2, 2021
5199248
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 May 2, 2021
fc423dc
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 May 5, 2021
67598a4
remove show option
attack68 May 5, 2021
f05757a
remove show option
attack68 May 5, 2021
d870dc6
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 May 6, 2021
1dfad74
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 May 12, 2021
57aa0fc
type subset
attack68 May 12, 2021
0f05bc1
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 May 18, 2021
515707d
docstring for subset
attack68 May 20, 2021
fa8ecee
simpler examples
attack68 May 20, 2021
0704210
nomenclature: hide_columns_
attack68 May 20, 2021
cb58422
nomenclature: hide_index_
attack68 May 20, 2021
4d9626d
nomenclature: comments
attack68 May 20, 2021
461ad30
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 May 20, 2021
54bb183
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 May 22, 2021
c6c1086
merge upstream master
attack68 May 22, 2021
16a4da3
fix spelling
attack68 May 23, 2021
f890888
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 May 24, 2021
ec5817c
adjust to_lext components after recent merge
attack68 May 24, 2021
6da4e1f
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 May 26, 2021
35c6b65
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 May 29, 2021
7d55fc4
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 Jun 1, 2021
d51a3d7
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 Jun 3, 2021
8095415
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 Jun 5, 2021
0921804
merge upstream master
attack68 Jun 5, 2021
5e0cc0f
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 Jun 5, 2021
a6f811b
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 Jun 9, 2021
dca039d
Merge remote-tracking branch 'upstream/master' into hiding_data_colum…
attack68 Jun 11, 2021
b309358
Merge branch 'rls1.3.0' into hiding_data_columns_and_index
attack68 Jun 15, 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
173 changes: 154 additions & 19 deletions pandas/io/formats/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
from pandas.util._decorators import doc

import pandas as pd
from pandas import RangeIndex
from pandas import (
IndexSlice,
RangeIndex,
)
from pandas.api.types import is_list_like
from pandas.core import generic
import pandas.core.common as com
Expand Down Expand Up @@ -682,7 +685,7 @@ def to_latex(
self.data.columns = RangeIndex(stop=len(self.data.columns))
numeric_cols = self.data._get_numeric_data().columns.to_list()
self.data.columns = _original_columns
column_format = "" if self.hidden_index else "l" * self.data.index.nlevels
column_format = "" if self.hide_index_ else "l" * self.data.index.nlevels
for ci, _ in enumerate(self.data.columns):
if ci not in self.hidden_columns:
column_format += (
Expand Down Expand Up @@ -926,7 +929,7 @@ def _copy(self, deepcopy: bool = False) -> Styler:
)

styler.uuid = self.uuid
styler.hidden_index = self.hidden_index
styler.hide_index_ = self.hide_index_

if deepcopy:
styler.ctx = copy.deepcopy(self.ctx)
Expand Down Expand Up @@ -965,7 +968,7 @@ def clear(self) -> None:
self.cell_context.clear()
self._todo.clear()

self.hidden_index = False
self.hide_index_ = False
self.hidden_columns = []
# self.format and self.table_styles may be dependent on user
# input in self.__init__()
Expand Down Expand Up @@ -1096,7 +1099,7 @@ def _applymap(
) -> Styler:
func = partial(func, **kwargs) # applymap doesn't take kwargs?
if subset is None:
subset = pd.IndexSlice[:]
subset = IndexSlice[:]
subset = non_reducing_slice(subset)
result = self.data.loc[subset].applymap(func)
self._update_ctx(result)
Expand Down Expand Up @@ -1509,37 +1512,169 @@ def set_na_rep(self, na_rep: str) -> StylerRenderer:
self.na_rep = na_rep
return self.format(na_rep=na_rep, precision=self.precision)

def hide_index(self) -> Styler:
def hide_index(self, subset: Subset | None = None) -> Styler:
"""
Hide any indices from rendering.
Hide the entire index, or specific keys in the index from rendering.

This method has dual functionality:

- if ``subset`` is ``None`` then the entire index will be hidden whilst
displaying all data-rows.
- if a ``subset`` is given then those specific rows will be hidden whilst the
index itself remains visible.

.. versionchanged:: 1.3.0

Parameters
----------
subset : label, array-like, IndexSlice, optional
A valid 1d input or single key along the index axis within
`DataFrame.loc[<subset>, :]`, to limit ``data`` to *before* applying
the function.

Returns
-------
self : Styler

See Also
--------
Styler.hide_columns: Hide the entire column headers row, or specific columns.

Examples
--------
Simple application hiding specific rows:

>>> df = pd.DataFrame([[1,2], [3,4], [5,6]], index=["a", "b", "c"])
>>> df.style.hide_index(["a", "b"])
0 1
c 5 6

Hide the index and retain the data values:

>>> midx = pd.MultiIndex.from_product([["x", "y"], ["a", "b", "c"]])
>>> df = pd.DataFrame(np.random.randn(6,6), index=midx, columns=midx)
>>> df.style.format("{:.1f}").hide_index()
x y
a b c a b c
0.1 0.0 0.4 1.3 0.6 -1.4
0.7 1.0 1.3 1.5 -0.0 -0.2
1.4 -0.8 1.6 -0.2 -0.4 -0.3
0.4 1.0 -0.2 -0.8 -1.2 1.1
-0.6 1.2 1.8 1.9 0.3 0.3
0.8 0.5 -0.3 1.2 2.2 -0.8

Hide specific rows but retain the index:

>>> df.style.format("{:.1f}").hide_index(subset=(slice(None), ["a", "c"]))
x y
a b c a b c
x b 0.7 1.0 1.3 1.5 -0.0 -0.2
y b -0.6 1.2 1.8 1.9 0.3 0.3

Hide specific rows and the index:

>>> df.style.format("{:.1f}").hide_index(subset=(slice(None), ["a", "c"]))
... .hide_index()
x y
a b c a b c
0.7 1.0 1.3 1.5 -0.0 -0.2
-0.6 1.2 1.8 1.9 0.3 0.3
"""
self.hidden_index = True
if subset is None:
self.hide_index_ = True
else:
subset_ = IndexSlice[subset, :] # new var so mypy reads not Optional
subset = non_reducing_slice(subset_)
hide = self.data.loc[subset]
hrows = self.index.get_indexer_for(hide.index)
# error: Incompatible types in assignment (expression has type
# "ndarray", variable has type "Sequence[int]")
self.hidden_rows = hrows # type: ignore[assignment]
return self

def hide_columns(self, subset: Subset) -> Styler:
def hide_columns(self, subset: Subset | None = None) -> Styler:
"""
Hide columns from rendering.
Hide the column headers or specific keys in the columns from rendering.

This method has dual functionality:

- if ``subset`` is ``None`` then the entire column headers row will be hidden
whilst the data-values remain visible.
- if a ``subset`` is given then those specific columns, including the
data-values will be hidden, whilst the column headers row remains visible.

.. versionchanged:: 1.3.0

Parameters
----------
subset : label, array-like, IndexSlice
A valid 1d input or single key along the appropriate axis within
`DataFrame.loc[]`, to limit ``data`` to *before* applying the function.
subset : label, array-like, IndexSlice, optional
A valid 1d input or single key along the columns axis within
`DataFrame.loc[:, <subset>]`, to limit ``data`` to *before* applying
the function.

Returns
-------
self : Styler

See Also
--------
Styler.hide_index: Hide the entire index, or specific keys in the index.

Examples
--------
Simple application hiding specific columns:

>>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=["a", "b", "c"])
>>> df.style.hide_columns(["a", "b"])
c
0 3
1 6

Hide column headers and retain the data values:

>>> midx = pd.MultiIndex.from_product([["x", "y"], ["a", "b", "c"]])
Copy link
Contributor

Choose a reason for hiding this comment

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

can you show an example first that has a single level index.

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

>>> df = pd.DataFrame(np.random.randn(6,6), index=midx, columns=midx)
>>> df.style.format("{:.1f}").hide_columns()
x d 0.1 0.0 0.4 1.3 0.6 -1.4
e 0.7 1.0 1.3 1.5 -0.0 -0.2
f 1.4 -0.8 1.6 -0.2 -0.4 -0.3
y d 0.4 1.0 -0.2 -0.8 -1.2 1.1
e -0.6 1.2 1.8 1.9 0.3 0.3
f 0.8 0.5 -0.3 1.2 2.2 -0.8

Hide specific columns but retain the column headers:

>>> df.style.format("{:.1f}").hide_columns(subset=(slice(None), ["a", "c"]))
x y
b b
x a 0.0 0.6
b 1.0 -0.0
c -0.8 -0.4
y a 1.0 -1.2
b 1.2 0.3
c 0.5 2.2

Hide specific columns and the column headers:

>>> df.style.format("{:.1f}").hide_columns(subset=(slice(None), ["a", "c"]))
... .hide_columns()
x a 0.0 0.6
b 1.0 -0.0
c -0.8 -0.4
y a 1.0 -1.2
b 1.2 0.3
c 0.5 2.2
"""
subset = non_reducing_slice(subset)
hidden_df = self.data.loc[subset]
hcols = self.columns.get_indexer_for(hidden_df.columns)
# error: Incompatible types in assignment (expression has type
# "ndarray", variable has type "Sequence[int]")
self.hidden_columns = hcols # type: ignore[assignment]
if subset is None:
self.hide_columns_ = True
else:
subset_ = IndexSlice[:, subset] # new var so mypy reads not Optional
subset = non_reducing_slice(subset_)
hide = self.data.loc[subset]
hcols = self.columns.get_indexer_for(hide.columns)
# error: Incompatible types in assignment (expression has type
# "ndarray", variable has type "Sequence[int]")
self.hidden_columns = hcols # type: ignore[assignment]
return self

# -----------------------------------------------------------------------
Expand Down
97 changes: 54 additions & 43 deletions pandas/io/formats/style_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ def __init__(
self.cell_ids = cell_ids

# add rendering variables
self.hidden_index: bool = False
self.hide_index_: bool = False # bools for hiding col/row headers
self.hide_columns_: bool = False
self.hidden_rows: Sequence[int] = [] # sequence for specific hidden rows/cols
self.hidden_columns: Sequence[int] = []
self.ctx: DefaultDict[tuple[int, int], CSSList] = defaultdict(list)
self.cell_context: DefaultDict[tuple[int, int], str] = defaultdict(str)
Expand Down Expand Up @@ -297,55 +299,56 @@ def _translate_header(

head = []
# 1) column headers
for r in range(self.data.columns.nlevels):
index_blanks = [
_element("th", blank_class, blank_value, not self.hidden_index)
] * (self.data.index.nlevels - 1)

name = self.data.columns.names[r]
column_name = [
_element(
"th",
f"{blank_class if name is None else index_name_class} level{r}",
name if name is not None else blank_value,
not self.hidden_index,
)
]

if clabels:
column_headers = [
if not self.hide_columns_:
for r in range(self.data.columns.nlevels):
index_blanks = [
_element("th", blank_class, blank_value, not self.hide_index_)
] * (self.data.index.nlevels - 1)

name = self.data.columns.names[r]
column_name = [
_element(
"th",
f"{col_heading_class} level{r} col{c}",
value,
_is_visible(c, r, col_lengths),
attributes=(
f'colspan="{col_lengths.get((r, c), 0)}"'
if col_lengths.get((r, c), 0) > 1
else ""
),
f"{blank_class if name is None else index_name_class} level{r}",
name if name is not None else blank_value,
not self.hide_index_,
)
for c, value in enumerate(clabels[r])
]

if len(self.data.columns) > max_cols:
# add an extra column with `...` value to indicate trimming
column_headers.append(
if clabels:
column_headers = [
_element(
"th",
f"{col_heading_class} level{r} {trimmed_col_class}",
"...",
True,
attributes="",
f"{col_heading_class} level{r} col{c}",
value,
_is_visible(c, r, col_lengths),
attributes=(
f'colspan="{col_lengths.get((r, c), 0)}"'
if col_lengths.get((r, c), 0) > 1
else ""
),
)
)
head.append(index_blanks + column_name + column_headers)
for c, value in enumerate(clabels[r])
]

if len(self.data.columns) > max_cols:
# add an extra column with `...` value to indicate trimming
column_headers.append(
_element(
"th",
f"{col_heading_class} level{r} {trimmed_col_class}",
"...",
True,
attributes="",
)
)
head.append(index_blanks + column_name + column_headers)

# 2) index names
if (
self.data.index.names
and com.any_not_none(*self.data.index.names)
and not self.hidden_index
and not self.hide_index_
):
index_names = [
_element(
Expand Down Expand Up @@ -411,7 +414,9 @@ def _translate_body(
The associated HTML elements needed for template rendering.
"""
# for sparsifying a MultiIndex
idx_lengths = _get_level_lengths(self.index, sparsify_index, max_rows)
idx_lengths = _get_level_lengths(
self.index, sparsify_index, max_rows, self.hidden_rows
)

rlabels = self.data.index.tolist()[:max_rows] # slice to allow trimming
if self.data.index.nlevels == 1:
Expand All @@ -425,7 +430,7 @@ def _translate_body(
"th",
f"{row_heading_class} level{c} {trimmed_row_class}",
"...",
not self.hidden_index,
not self.hide_index_,
attributes="",
)
for c in range(self.data.index.nlevels)
Expand Down Expand Up @@ -462,7 +467,7 @@ def _translate_body(
"th",
f"{row_heading_class} level{c} row{r}",
value,
(_is_visible(r, c, idx_lengths) and not self.hidden_index),
(_is_visible(r, c, idx_lengths) and not self.hide_index_),
id=f"level{c}_row{r}",
attributes=(
f'rowspan="{idx_lengths.get((c, r), 0)}"'
Expand Down Expand Up @@ -496,7 +501,7 @@ def _translate_body(
"td",
f"{data_class} row{r} col{c}{cls}",
value,
(c not in self.hidden_columns),
(c not in self.hidden_columns and r not in self.hidden_rows),
attributes="",
display_value=self._display_funcs[(r, c)](value),
)
Expand Down Expand Up @@ -527,7 +532,7 @@ def _translate_latex(self, d: dict) -> None:
d["head"] = [[col for col in row if col["is_visible"]] for row in d["head"]]
body = []
for r, row in enumerate(d["body"]):
if self.hidden_index:
if self.hide_index_:
row_body_headers = []
else:
row_body_headers = [
Expand Down Expand Up @@ -842,7 +847,13 @@ def _get_level_lengths(
last_label = j
lengths[(i, last_label)] = 0
elif j not in hidden_elements:
lengths[(i, last_label)] += 1
if lengths[(i, last_label)] == 0:
# if the previous iteration was first-of-kind but hidden then offset
last_label = j
lengths[(i, last_label)] = 1
else:
# else add to previous iteration
lengths[(i, last_label)] += 1

non_zero_lengths = {
element: length for element, length in lengths.items() if length >= 1
Expand Down
Loading