Skip to content

Commit fc0a7a3

Browse files
attack68meeseeksmachine
authored andcommitted
Backport PR pandas-dev#41266: API: make hide_columns and hide_index have a consistent signature and function in Styler
1 parent 0c1e59c commit fc0a7a3

File tree

3 files changed

+235
-68
lines changed

3 files changed

+235
-68
lines changed

pandas/io/formats/style.py

+154-19
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@
3131
from pandas.util._decorators import doc
3232

3333
import pandas as pd
34-
from pandas import RangeIndex
34+
from pandas import (
35+
IndexSlice,
36+
RangeIndex,
37+
)
3538
from pandas.api.types import is_list_like
3639
from pandas.core import generic
3740
import pandas.core.common as com
@@ -682,7 +685,7 @@ def to_latex(
682685
self.data.columns = RangeIndex(stop=len(self.data.columns))
683686
numeric_cols = self.data._get_numeric_data().columns.to_list()
684687
self.data.columns = _original_columns
685-
column_format = "" if self.hidden_index else "l" * self.data.index.nlevels
688+
column_format = "" if self.hide_index_ else "l" * self.data.index.nlevels
686689
for ci, _ in enumerate(self.data.columns):
687690
if ci not in self.hidden_columns:
688691
column_format += (
@@ -926,7 +929,7 @@ def _copy(self, deepcopy: bool = False) -> Styler:
926929
)
927930

928931
styler.uuid = self.uuid
929-
styler.hidden_index = self.hidden_index
932+
styler.hide_index_ = self.hide_index_
930933

931934
if deepcopy:
932935
styler.ctx = copy.deepcopy(self.ctx)
@@ -965,7 +968,7 @@ def clear(self) -> None:
965968
self.cell_context.clear()
966969
self._todo.clear()
967970

968-
self.hidden_index = False
971+
self.hide_index_ = False
969972
self.hidden_columns = []
970973
# self.format and self.table_styles may be dependent on user
971974
# input in self.__init__()
@@ -1096,7 +1099,7 @@ def _applymap(
10961099
) -> Styler:
10971100
func = partial(func, **kwargs) # applymap doesn't take kwargs?
10981101
if subset is None:
1099-
subset = pd.IndexSlice[:]
1102+
subset = IndexSlice[:]
11001103
subset = non_reducing_slice(subset)
11011104
result = self.data.loc[subset].applymap(func)
11021105
self._update_ctx(result)
@@ -1511,37 +1514,169 @@ def set_na_rep(self, na_rep: str) -> StylerRenderer:
15111514
self.na_rep = na_rep
15121515
return self.format(na_rep=na_rep, precision=self.precision)
15131516

1514-
def hide_index(self) -> Styler:
1517+
def hide_index(self, subset: Subset | None = None) -> Styler:
15151518
"""
1516-
Hide any indices from rendering.
1519+
Hide the entire index, or specific keys in the index from rendering.
1520+
1521+
This method has dual functionality:
1522+
1523+
- if ``subset`` is ``None`` then the entire index will be hidden whilst
1524+
displaying all data-rows.
1525+
- if a ``subset`` is given then those specific rows will be hidden whilst the
1526+
index itself remains visible.
1527+
1528+
.. versionchanged:: 1.3.0
1529+
1530+
Parameters
1531+
----------
1532+
subset : label, array-like, IndexSlice, optional
1533+
A valid 1d input or single key along the index axis within
1534+
`DataFrame.loc[<subset>, :]`, to limit ``data`` to *before* applying
1535+
the function.
15171536
15181537
Returns
15191538
-------
15201539
self : Styler
1540+
1541+
See Also
1542+
--------
1543+
Styler.hide_columns: Hide the entire column headers row, or specific columns.
1544+
1545+
Examples
1546+
--------
1547+
Simple application hiding specific rows:
1548+
1549+
>>> df = pd.DataFrame([[1,2], [3,4], [5,6]], index=["a", "b", "c"])
1550+
>>> df.style.hide_index(["a", "b"])
1551+
0 1
1552+
c 5 6
1553+
1554+
Hide the index and retain the data values:
1555+
1556+
>>> midx = pd.MultiIndex.from_product([["x", "y"], ["a", "b", "c"]])
1557+
>>> df = pd.DataFrame(np.random.randn(6,6), index=midx, columns=midx)
1558+
>>> df.style.format("{:.1f}").hide_index()
1559+
x y
1560+
a b c a b c
1561+
0.1 0.0 0.4 1.3 0.6 -1.4
1562+
0.7 1.0 1.3 1.5 -0.0 -0.2
1563+
1.4 -0.8 1.6 -0.2 -0.4 -0.3
1564+
0.4 1.0 -0.2 -0.8 -1.2 1.1
1565+
-0.6 1.2 1.8 1.9 0.3 0.3
1566+
0.8 0.5 -0.3 1.2 2.2 -0.8
1567+
1568+
Hide specific rows but retain the index:
1569+
1570+
>>> df.style.format("{:.1f}").hide_index(subset=(slice(None), ["a", "c"]))
1571+
x y
1572+
a b c a b c
1573+
x b 0.7 1.0 1.3 1.5 -0.0 -0.2
1574+
y b -0.6 1.2 1.8 1.9 0.3 0.3
1575+
1576+
Hide specific rows and the index:
1577+
1578+
>>> df.style.format("{:.1f}").hide_index(subset=(slice(None), ["a", "c"]))
1579+
... .hide_index()
1580+
x y
1581+
a b c a b c
1582+
0.7 1.0 1.3 1.5 -0.0 -0.2
1583+
-0.6 1.2 1.8 1.9 0.3 0.3
15211584
"""
1522-
self.hidden_index = True
1585+
if subset is None:
1586+
self.hide_index_ = True
1587+
else:
1588+
subset_ = IndexSlice[subset, :] # new var so mypy reads not Optional
1589+
subset = non_reducing_slice(subset_)
1590+
hide = self.data.loc[subset]
1591+
hrows = self.index.get_indexer_for(hide.index)
1592+
# error: Incompatible types in assignment (expression has type
1593+
# "ndarray", variable has type "Sequence[int]")
1594+
self.hidden_rows = hrows # type: ignore[assignment]
15231595
return self
15241596

1525-
def hide_columns(self, subset: Subset) -> Styler:
1597+
def hide_columns(self, subset: Subset | None = None) -> Styler:
15261598
"""
1527-
Hide columns from rendering.
1599+
Hide the column headers or specific keys in the columns from rendering.
1600+
1601+
This method has dual functionality:
1602+
1603+
- if ``subset`` is ``None`` then the entire column headers row will be hidden
1604+
whilst the data-values remain visible.
1605+
- if a ``subset`` is given then those specific columns, including the
1606+
data-values will be hidden, whilst the column headers row remains visible.
1607+
1608+
.. versionchanged:: 1.3.0
15281609
15291610
Parameters
15301611
----------
1531-
subset : label, array-like, IndexSlice
1532-
A valid 1d input or single key along the appropriate axis within
1533-
`DataFrame.loc[]`, to limit ``data`` to *before* applying the function.
1612+
subset : label, array-like, IndexSlice, optional
1613+
A valid 1d input or single key along the columns axis within
1614+
`DataFrame.loc[:, <subset>]`, to limit ``data`` to *before* applying
1615+
the function.
15341616
15351617
Returns
15361618
-------
15371619
self : Styler
1620+
1621+
See Also
1622+
--------
1623+
Styler.hide_index: Hide the entire index, or specific keys in the index.
1624+
1625+
Examples
1626+
--------
1627+
Simple application hiding specific columns:
1628+
1629+
>>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=["a", "b", "c"])
1630+
>>> df.style.hide_columns(["a", "b"])
1631+
c
1632+
0 3
1633+
1 6
1634+
1635+
Hide column headers and retain the data values:
1636+
1637+
>>> midx = pd.MultiIndex.from_product([["x", "y"], ["a", "b", "c"]])
1638+
>>> df = pd.DataFrame(np.random.randn(6,6), index=midx, columns=midx)
1639+
>>> df.style.format("{:.1f}").hide_columns()
1640+
x d 0.1 0.0 0.4 1.3 0.6 -1.4
1641+
e 0.7 1.0 1.3 1.5 -0.0 -0.2
1642+
f 1.4 -0.8 1.6 -0.2 -0.4 -0.3
1643+
y d 0.4 1.0 -0.2 -0.8 -1.2 1.1
1644+
e -0.6 1.2 1.8 1.9 0.3 0.3
1645+
f 0.8 0.5 -0.3 1.2 2.2 -0.8
1646+
1647+
Hide specific columns but retain the column headers:
1648+
1649+
>>> df.style.format("{:.1f}").hide_columns(subset=(slice(None), ["a", "c"]))
1650+
x y
1651+
b b
1652+
x a 0.0 0.6
1653+
b 1.0 -0.0
1654+
c -0.8 -0.4
1655+
y a 1.0 -1.2
1656+
b 1.2 0.3
1657+
c 0.5 2.2
1658+
1659+
Hide specific columns and the column headers:
1660+
1661+
>>> df.style.format("{:.1f}").hide_columns(subset=(slice(None), ["a", "c"]))
1662+
... .hide_columns()
1663+
x a 0.0 0.6
1664+
b 1.0 -0.0
1665+
c -0.8 -0.4
1666+
y a 1.0 -1.2
1667+
b 1.2 0.3
1668+
c 0.5 2.2
15381669
"""
1539-
subset = non_reducing_slice(subset)
1540-
hidden_df = self.data.loc[subset]
1541-
hcols = self.columns.get_indexer_for(hidden_df.columns)
1542-
# error: Incompatible types in assignment (expression has type
1543-
# "ndarray", variable has type "Sequence[int]")
1544-
self.hidden_columns = hcols # type: ignore[assignment]
1670+
if subset is None:
1671+
self.hide_columns_ = True
1672+
else:
1673+
subset_ = IndexSlice[:, subset] # new var so mypy reads not Optional
1674+
subset = non_reducing_slice(subset_)
1675+
hide = self.data.loc[subset]
1676+
hcols = self.columns.get_indexer_for(hide.columns)
1677+
# error: Incompatible types in assignment (expression has type
1678+
# "ndarray", variable has type "Sequence[int]")
1679+
self.hidden_columns = hcols # type: ignore[assignment]
15451680
return self
15461681

15471682
# -----------------------------------------------------------------------

pandas/io/formats/style_render.py

+54-43
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ def __init__(
9797
self.cell_ids = cell_ids
9898

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

298300
head = []
299301
# 1) column headers
300-
for r in range(self.data.columns.nlevels):
301-
index_blanks = [
302-
_element("th", blank_class, blank_value, not self.hidden_index)
303-
] * (self.data.index.nlevels - 1)
304-
305-
name = self.data.columns.names[r]
306-
column_name = [
307-
_element(
308-
"th",
309-
f"{blank_class if name is None else index_name_class} level{r}",
310-
name if name is not None else blank_value,
311-
not self.hidden_index,
312-
)
313-
]
314-
315-
if clabels:
316-
column_headers = [
302+
if not self.hide_columns_:
303+
for r in range(self.data.columns.nlevels):
304+
index_blanks = [
305+
_element("th", blank_class, blank_value, not self.hide_index_)
306+
] * (self.data.index.nlevels - 1)
307+
308+
name = self.data.columns.names[r]
309+
column_name = [
317310
_element(
318311
"th",
319-
f"{col_heading_class} level{r} col{c}",
320-
value,
321-
_is_visible(c, r, col_lengths),
322-
attributes=(
323-
f'colspan="{col_lengths.get((r, c), 0)}"'
324-
if col_lengths.get((r, c), 0) > 1
325-
else ""
326-
),
312+
f"{blank_class if name is None else index_name_class} level{r}",
313+
name if name is not None else blank_value,
314+
not self.hide_index_,
327315
)
328-
for c, value in enumerate(clabels[r])
329316
]
330317

331-
if len(self.data.columns) > max_cols:
332-
# add an extra column with `...` value to indicate trimming
333-
column_headers.append(
318+
if clabels:
319+
column_headers = [
334320
_element(
335321
"th",
336-
f"{col_heading_class} level{r} {trimmed_col_class}",
337-
"...",
338-
True,
339-
attributes="",
322+
f"{col_heading_class} level{r} col{c}",
323+
value,
324+
_is_visible(c, r, col_lengths),
325+
attributes=(
326+
f'colspan="{col_lengths.get((r, c), 0)}"'
327+
if col_lengths.get((r, c), 0) > 1
328+
else ""
329+
),
340330
)
341-
)
342-
head.append(index_blanks + column_name + column_headers)
331+
for c, value in enumerate(clabels[r])
332+
]
333+
334+
if len(self.data.columns) > max_cols:
335+
# add an extra column with `...` value to indicate trimming
336+
column_headers.append(
337+
_element(
338+
"th",
339+
f"{col_heading_class} level{r} {trimmed_col_class}",
340+
"...",
341+
True,
342+
attributes="",
343+
)
344+
)
345+
head.append(index_blanks + column_name + column_headers)
343346

344347
# 2) index names
345348
if (
346349
self.data.index.names
347350
and com.any_not_none(*self.data.index.names)
348-
and not self.hidden_index
351+
and not self.hide_index_
349352
):
350353
index_names = [
351354
_element(
@@ -411,7 +414,9 @@ def _translate_body(
411414
The associated HTML elements needed for template rendering.
412415
"""
413416
# for sparsifying a MultiIndex
414-
idx_lengths = _get_level_lengths(self.index, sparsify_index, max_rows)
417+
idx_lengths = _get_level_lengths(
418+
self.index, sparsify_index, max_rows, self.hidden_rows
419+
)
415420

416421
rlabels = self.data.index.tolist()[:max_rows] # slice to allow trimming
417422
if self.data.index.nlevels == 1:
@@ -425,7 +430,7 @@ def _translate_body(
425430
"th",
426431
f"{row_heading_class} level{c} {trimmed_row_class}",
427432
"...",
428-
not self.hidden_index,
433+
not self.hide_index_,
429434
attributes="",
430435
)
431436
for c in range(self.data.index.nlevels)
@@ -462,7 +467,7 @@ def _translate_body(
462467
"th",
463468
f"{row_heading_class} level{c} row{r}",
464469
value,
465-
(_is_visible(r, c, idx_lengths) and not self.hidden_index),
470+
(_is_visible(r, c, idx_lengths) and not self.hide_index_),
466471
id=f"level{c}_row{r}",
467472
attributes=(
468473
f'rowspan="{idx_lengths.get((c, r), 0)}"'
@@ -496,7 +501,7 @@ def _translate_body(
496501
"td",
497502
f"{data_class} row{r} col{c}{cls}",
498503
value,
499-
(c not in self.hidden_columns),
504+
(c not in self.hidden_columns and r not in self.hidden_rows),
500505
attributes="",
501506
display_value=self._display_funcs[(r, c)](value),
502507
)
@@ -527,7 +532,7 @@ def _translate_latex(self, d: dict) -> None:
527532
d["head"] = [[col for col in row if col["is_visible"]] for row in d["head"]]
528533
body = []
529534
for r, row in enumerate(d["body"]):
530-
if self.hidden_index:
535+
if self.hide_index_:
531536
row_body_headers = []
532537
else:
533538
row_body_headers = [
@@ -842,7 +847,13 @@ def _get_level_lengths(
842847
last_label = j
843848
lengths[(i, last_label)] = 0
844849
elif j not in hidden_elements:
845-
lengths[(i, last_label)] += 1
850+
if lengths[(i, last_label)] == 0:
851+
# if the previous iteration was first-of-kind but hidden then offset
852+
last_label = j
853+
lengths[(i, last_label)] = 1
854+
else:
855+
# else add to previous iteration
856+
lengths[(i, last_label)] += 1
846857

847858
non_zero_lengths = {
848859
element: length for element, length in lengths.items() if length >= 1

0 commit comments

Comments
 (0)