Skip to content

Commit 6f3d1d4

Browse files
committed
ENH 14194: add style option for hiding index and columns
1 parent d50b162 commit 6f3d1d4

File tree

2 files changed

+194
-17
lines changed

2 files changed

+194
-17
lines changed

pandas/io/formats/style.py

+84-17
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ class Styler(object):
6969
a unique identifier to avoid CSS collisons; generated automatically
7070
caption: str, default None
7171
caption to attach to the table
72+
index_hidden: bool, default False
73+
hides the index in the html output
74+
.. versionadded:: 0.20.1
7275
7376
Attributes
7477
----------
@@ -117,7 +120,8 @@ class Styler(object):
117120
template = env.get_template("html.tpl")
118121

119122
def __init__(self, data, precision=None, table_styles=None, uuid=None,
120-
caption=None, table_attributes=None):
123+
caption=None, table_attributes=None, index_hidden=False,
124+
hidden_cols=None):
121125
self.ctx = defaultdict(list)
122126
self._todo = []
123127

@@ -139,6 +143,11 @@ def __init__(self, data, precision=None, table_styles=None, uuid=None,
139143
precision = get_option('display.precision')
140144
self.precision = precision
141145
self.table_attributes = table_attributes
146+
self.index_hidden = index_hidden
147+
if hidden_cols is None:
148+
hidden_cols = []
149+
self.hidden_cols = hidden_cols
150+
142151
# display_funcs maps (row, col) -> formatting function
143152

144153
def default_display_func(x):
@@ -186,6 +195,8 @@ def _translate(self):
186195
caption = self.caption
187196
ctx = self.ctx
188197
precision = self.precision
198+
index_hidden = self.index_hidden
199+
hidden_cols = self.hidden_cols
189200
uuid = self.uuid or str(uuid1()).replace("-", "_")
190201
ROW_HEADING_CLASS = "row_heading"
191202
COL_HEADING_CLASS = "col_heading"
@@ -200,7 +211,7 @@ def format_attr(pair):
200211

201212
# for sparsifying a MultiIndex
202213
idx_lengths = _get_level_lengths(self.index)
203-
col_lengths = _get_level_lengths(self.columns)
214+
col_lengths = _get_level_lengths(self.columns, hidden_cols)
204215

205216
cell_context = dict()
206217

@@ -223,7 +234,7 @@ def format_attr(pair):
223234
row_es = [{"type": "th",
224235
"value": BLANK_VALUE,
225236
"display_value": BLANK_VALUE,
226-
"is_visible": True,
237+
"is_visible": (not index_hidden),
227238
"class": " ".join([BLANK_CLASS])}] * (n_rlvls - 1)
228239

229240
# ... except maybe the last for columns.names
@@ -235,7 +246,7 @@ def format_attr(pair):
235246
"value": name,
236247
"display_value": name,
237248
"class": " ".join(cs),
238-
"is_visible": True})
249+
"is_visible": (not index_hidden)})
239250

240251
for c, value in enumerate(clabels[r]):
241252
cs = [COL_HEADING_CLASS, "level%s" % r, "col%s" % c]
@@ -256,8 +267,8 @@ def format_attr(pair):
256267
row_es.append(es)
257268
head.append(row_es)
258269

259-
if self.data.index.names and not all(x is None
260-
for x in self.data.index.names):
270+
if self.data.index.names and not index_hidden\
271+
and not all(x is None for x in self.data.index.names):
261272
index_header_row = []
262273

263274
for c, name in enumerate(self.data.index.names):
@@ -271,7 +282,7 @@ def format_attr(pair):
271282
[{"type": "th",
272283
"value": BLANK_VALUE,
273284
"class": " ".join([BLANK_CLASS])
274-
}] * len(clabels[0]))
285+
}] * (len(clabels[0]) - len(hidden_cols)))
275286

276287
head.append(index_header_row)
277288

@@ -281,7 +292,8 @@ def format_attr(pair):
281292
for c, value in enumerate(rlabels[r]):
282293
es = {
283294
"type": "th",
284-
"is_visible": _is_visible(r, c, idx_lengths),
295+
"is_visible": (_is_visible(r, c, idx_lengths) &
296+
(~index_hidden)),
285297
"value": value,
286298
"display_value": value,
287299
"class": " ".join([ROW_HEADING_CLASS, "level%s" % c,
@@ -304,7 +316,8 @@ def format_attr(pair):
304316
"value": value,
305317
"class": " ".join(cs),
306318
"id": "_".join(cs[1:]),
307-
"display_value": formatter(value)
319+
"display_value": formatter(value),
320+
"is_visible": (c not in hidden_cols)
308321
})
309322
props = []
310323
for x in ctx[r, c]:
@@ -460,7 +473,9 @@ def _update_ctx(self, attrs):
460473
def _copy(self, deepcopy=False):
461474
styler = Styler(self.data, precision=self.precision,
462475
caption=self.caption, uuid=self.uuid,
463-
table_styles=self.table_styles)
476+
table_styles=self.table_styles,
477+
index_hidden=self.index_hidden,
478+
hidden_cols=self.hidden_cols)
464479
if deepcopy:
465480
styler.ctx = copy.deepcopy(self.ctx)
466481
styler._todo = copy.deepcopy(self._todo)
@@ -716,7 +731,7 @@ def set_uuid(self, uuid):
716731

717732
def set_caption(self, caption):
718733
"""
719-
Se the caption on a Styler
734+
Set the caption on a Styler
720735
721736
.. versionadded:: 0.17.1
722737
@@ -762,6 +777,41 @@ def set_table_styles(self, table_styles):
762777
self.table_styles = table_styles
763778
return self
764779

780+
def hide_index(self):
781+
"""
782+
Hide any indices from rendering.
783+
784+
.. versionadded:: 0.20.1
785+
786+
Returns
787+
-------
788+
self : Styler
789+
"""
790+
self.index_hidden = True
791+
return self
792+
793+
def hide_columns(self, subset):
794+
"""
795+
Hide columns from rendering.
796+
797+
.. versionadded:: 0.20.1
798+
799+
Parameters
800+
----------
801+
802+
subset: IndexSlice
803+
An argument to ``DataFrame.loc`` that identifies which columns
804+
are hidden.
805+
806+
Returns
807+
-------
808+
self : Styler
809+
"""
810+
subset = _non_reducing_slice(subset)
811+
hidden_df = self.data.loc[subset]
812+
self.hidden_cols = self.columns.get_indexer_for(hidden_df.columns)
813+
return self
814+
765815
# -----------------------------------------------------------------------
766816
# A collection of "builtin" styles
767817
# -----------------------------------------------------------------------
@@ -1009,31 +1059,48 @@ def _is_visible(idx_row, idx_col, lengths):
10091059
return (idx_col, idx_row) in lengths
10101060

10111061

1012-
def _get_level_lengths(index):
1062+
def _get_level_lengths(index, hidden_elements=None):
10131063
"""
10141064
Given an index, find the level lenght for each element.
1065+
Optional argument is a list of index positions which
1066+
should not be visible.
10151067
10161068
Result is a dictionary of (level, inital_position): span
10171069
"""
10181070
sentinel = com.sentinel_factory()
10191071
levels = index.format(sparsify=sentinel, adjoin=False, names=False)
10201072

1021-
if index.nlevels == 1:
1022-
return {(0, i): 1 for i, value in enumerate(levels)}
1073+
if hidden_elements is None:
1074+
hidden_elements = []
10231075

10241076
lengths = {}
1077+
if index.nlevels == 1:
1078+
for i, value in enumerate(levels):
1079+
if(i not in hidden_elements):
1080+
lengths[(0, i)] = 1
1081+
return lengths
10251082

10261083
for i, lvl in enumerate(levels):
10271084
for j, row in enumerate(lvl):
10281085
if not get_option('display.multi_sparse'):
10291086
lengths[(i, j)] = 1
1030-
elif row != sentinel:
1087+
elif (row != sentinel) and (j not in hidden_elements):
10311088
last_label = j
10321089
lengths[(i, last_label)] = 1
1033-
else:
1090+
elif (row != sentinel):
1091+
# even if its hidden, keep track of it in case
1092+
# length >1 and later elemens are visible
1093+
last_label = j
1094+
lengths[(i, last_label)] = 0
1095+
elif(j not in hidden_elements):
10341096
lengths[(i, last_label)] += 1
10351097

1036-
return lengths
1098+
non_zero_lengths = {}
1099+
for element, length in lengths.items():
1100+
if(length >= 1):
1101+
non_zero_lengths[element] = length
1102+
1103+
return non_zero_lengths
10371104

10381105

10391106
def _maybe_wrap_formatter(formatter):

pandas/tests/io/formats/test_style.py

+110
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,116 @@ def test_mi_sparse_column_names(self):
702702
]
703703
self.assertEqual(head, expected)
704704

705+
def test_hide_single_index(self):
706+
# single unnamed index
707+
ctx = self.df.style._translate()
708+
self.assertTrue(ctx['body'][0][0]['is_visible'])
709+
self.assertTrue(ctx['head'][0][0]['is_visible'])
710+
ctx2 = self.df.style.hide_index()._translate()
711+
self.assertFalse(ctx2['body'][0][0]['is_visible'])
712+
self.assertFalse(ctx2['head'][0][0]['is_visible'])
713+
714+
# single named index
715+
ctx3 = self.df.set_index('A').style._translate()
716+
self.assertTrue(ctx3['body'][0][0]['is_visible'])
717+
self.assertEqual(len(ctx3['head']), 2) # 2 header levels
718+
self.assertTrue(ctx3['head'][0][0]['is_visible'])
719+
720+
ctx4 = self.df.set_index('A').style.hide_index()._translate()
721+
self.assertFalse(ctx4['body'][0][0]['is_visible'])
722+
self.assertEqual(len(ctx4['head']), 1) # only 1 header levels
723+
self.assertFalse(ctx4['head'][0][0]['is_visible'])
724+
725+
def test_hide_multiindex(self):
726+
df = pd.DataFrame({'A': [1, 2]}, index=pd.MultiIndex.from_arrays(
727+
[['a', 'a'], [0, 1]],
728+
names=['idx_level_0', 'idx_level_1'])
729+
)
730+
ctx1 = df.style._translate()
731+
# tests for 'a' and '0'
732+
self.assertTrue(ctx1['body'][0][0]['is_visible'])
733+
self.assertTrue(ctx1['body'][0][1]['is_visible'])
734+
# check for blank header rows
735+
self.assertTrue(ctx1['head'][0][0]['is_visible'])
736+
self.assertTrue(ctx1['head'][0][1]['is_visible'])
737+
738+
ctx2 = df.style.hide_index()._translate()
739+
# tests for 'a' and '0'
740+
self.assertFalse(ctx2['body'][0][0]['is_visible'])
741+
self.assertFalse(ctx2['body'][0][1]['is_visible'])
742+
# check for blank header rows
743+
self.assertFalse(ctx2['head'][0][0]['is_visible'])
744+
self.assertFalse(ctx2['head'][0][1]['is_visible'])
745+
746+
def test_hide_columns_single_level(self):
747+
# test hiding single column
748+
ctx = self.df.style._translate()
749+
self.assertTrue(ctx['head'][0][1]['is_visible'])
750+
self.assertEqual(ctx['head'][0][1]['display_value'], 'A')
751+
self.assertTrue(ctx['head'][0][2]['is_visible'])
752+
self.assertEqual(ctx['head'][0][2]['display_value'], 'B')
753+
self.assertTrue(ctx['body'][0][1]['is_visible']) # col A, row 1
754+
self.assertTrue(ctx['body'][1][2]['is_visible']) # col B, row 1
755+
756+
ctx = self.df.style.hide_columns('A')._translate()
757+
self.assertFalse(ctx['head'][0][1]['is_visible'])
758+
self.assertFalse(ctx['body'][0][1]['is_visible']) # col A, row 1
759+
self.assertTrue(ctx['body'][1][2]['is_visible']) # col B, row 1
760+
761+
# test hiding mulitiple columns
762+
ctx = self.df.style.hide_columns(['A', 'B'])._translate()
763+
self.assertFalse(ctx['head'][0][1]['is_visible'])
764+
self.assertFalse(ctx['head'][0][2]['is_visible'])
765+
self.assertFalse(ctx['body'][0][1]['is_visible']) # col A, row 1
766+
self.assertFalse(ctx['body'][1][2]['is_visible']) # col B, row 1
767+
768+
def test_hide_columns_mult_levels(self):
769+
# setup dataframe with multiple column levels and indices
770+
i1 = pd.MultiIndex.from_arrays([['a', 'a'], [0, 1]],
771+
names=['idx_level_0',
772+
'idx_level_1'])
773+
i2 = pd.MultiIndex.from_arrays([['b', 'b'], [0, 1]],
774+
names=['col_level_0',
775+
'col_level_1'])
776+
df = pd.DataFrame([[1, 2], [3, 4]], index=i1, columns=i2)
777+
ctx = df.style._translate()
778+
# column headers
779+
self.assertTrue(ctx['head'][0][2]['is_visible'])
780+
self.assertTrue(ctx['head'][1][2]['is_visible'])
781+
self.assertEqual(ctx['head'][1][3]['display_value'], 1)
782+
# indices
783+
self.assertTrue(ctx['body'][0][0]['is_visible'])
784+
# data
785+
self.assertTrue(ctx['body'][1][2]['is_visible'])
786+
self.assertEqual(ctx['body'][1][2]['display_value'], 3)
787+
self.assertTrue(ctx['body'][1][3]['is_visible'])
788+
self.assertEqual(ctx['body'][1][3]['display_value'], 4)
789+
790+
# hide top column level, which hides both columns
791+
ctx = df.style.hide_columns('b')._translate()
792+
self.assertFalse(ctx['head'][0][2]['is_visible']) # b
793+
self.assertFalse(ctx['head'][1][2]['is_visible']) # 0
794+
self.assertFalse(ctx['body'][1][2]['is_visible']) # 3
795+
self.assertTrue(ctx['body'][0][0]['is_visible']) # index
796+
797+
# hide first column only
798+
ctx = df.style.hide_columns([('b', 0)])._translate()
799+
self.assertTrue(ctx['head'][0][2]['is_visible']) # b
800+
self.assertFalse(ctx['head'][1][2]['is_visible']) # 0
801+
self.assertFalse(ctx['body'][1][2]['is_visible']) # 3
802+
self.assertTrue(ctx['body'][1][3]['is_visible'])
803+
self.assertEqual(ctx['body'][1][3]['display_value'], 4)
804+
805+
# hide second column and index
806+
ctx = df.style.hide_columns([('b', 1)]).hide_index()._translate()
807+
self.assertFalse(ctx['body'][0][0]['is_visible']) # index
808+
self.assertTrue(ctx['head'][0][2]['is_visible']) # b
809+
self.assertTrue(ctx['head'][1][2]['is_visible']) # 0
810+
self.assertFalse(ctx['head'][1][3]['is_visible']) # 1
811+
self.assertFalse(ctx['body'][1][3]['is_visible']) # 4
812+
self.assertTrue(ctx['body'][1][2]['is_visible'])
813+
self.assertEqual(ctx['body'][1][2]['display_value'], 3)
814+
705815

706816
@tm.mplskip
707817
class TestStylerMatplotlibDep(TestCase):

0 commit comments

Comments
 (0)