Skip to content

Commit fab4256

Browse files
committed
ENH 14194: add style option for hiding index and columns
1 parent d50f981 commit fab4256

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
@@ -70,6 +70,9 @@ class Styler(object):
7070
a unique identifier to avoid CSS collisons; generated automatically
7171
caption: str, default None
7272
caption to attach to the table
73+
index_hidden: bool, default False
74+
hides the index in the html output
75+
.. versionadded:: 0.20.1
7376
7477
Attributes
7578
----------
@@ -118,7 +121,8 @@ class Styler(object):
118121
template = env.get_template("html.tpl")
119122

120123
def __init__(self, data, precision=None, table_styles=None, uuid=None,
121-
caption=None, table_attributes=None):
124+
caption=None, table_attributes=None, index_hidden=False,
125+
hidden_cols=None):
122126
self.ctx = defaultdict(list)
123127
self._todo = []
124128

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

145154
def default_display_func(x):
@@ -187,6 +196,8 @@ def _translate(self):
187196
caption = self.caption
188197
ctx = self.ctx
189198
precision = self.precision
199+
index_hidden = self.index_hidden
200+
hidden_cols = self.hidden_cols
190201
uuid = self.uuid or str(uuid1()).replace("-", "_")
191202
ROW_HEADING_CLASS = "row_heading"
192203
COL_HEADING_CLASS = "col_heading"
@@ -201,7 +212,7 @@ def format_attr(pair):
201212

202213
# for sparsifying a MultiIndex
203214
idx_lengths = _get_level_lengths(self.index)
204-
col_lengths = _get_level_lengths(self.columns)
215+
col_lengths = _get_level_lengths(self.columns, hidden_cols)
205216

206217
cell_context = dict()
207218

@@ -224,7 +235,7 @@ def format_attr(pair):
224235
row_es = [{"type": "th",
225236
"value": BLANK_VALUE,
226237
"display_value": BLANK_VALUE,
227-
"is_visible": True,
238+
"is_visible": (not index_hidden),
228239
"class": " ".join([BLANK_CLASS])}] * (n_rlvls - 1)
229240

230241
# ... except maybe the last for columns.names
@@ -236,7 +247,7 @@ def format_attr(pair):
236247
"value": name,
237248
"display_value": name,
238249
"class": " ".join(cs),
239-
"is_visible": True})
250+
"is_visible": (not index_hidden)})
240251

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

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

264275
for c, name in enumerate(self.data.index.names):
@@ -272,7 +283,7 @@ def format_attr(pair):
272283
[{"type": "th",
273284
"value": BLANK_VALUE,
274285
"class": " ".join([BLANK_CLASS])
275-
}] * len(clabels[0]))
286+
}] * (len(clabels[0]) - len(hidden_cols)))
276287

277288
head.append(index_header_row)
278289

@@ -282,7 +293,8 @@ def format_attr(pair):
282293
for c, value in enumerate(rlabels[r]):
283294
es = {
284295
"type": "th",
285-
"is_visible": _is_visible(r, c, idx_lengths),
296+
"is_visible": (_is_visible(r, c, idx_lengths) &
297+
(~index_hidden)),
286298
"value": value,
287299
"display_value": value,
288300
"class": " ".join([ROW_HEADING_CLASS, "level%s" % c,
@@ -305,7 +317,8 @@ def format_attr(pair):
305317
"value": value,
306318
"class": " ".join(cs),
307319
"id": "_".join(cs[1:]),
308-
"display_value": formatter(value)
320+
"display_value": formatter(value),
321+
"is_visible": (c not in hidden_cols)
309322
})
310323
props = []
311324
for x in ctx[r, c]:
@@ -461,7 +474,9 @@ def _update_ctx(self, attrs):
461474
def _copy(self, deepcopy=False):
462475
styler = Styler(self.data, precision=self.precision,
463476
caption=self.caption, uuid=self.uuid,
464-
table_styles=self.table_styles)
477+
table_styles=self.table_styles,
478+
index_hidden=self.index_hidden,
479+
hidden_cols=self.hidden_cols)
465480
if deepcopy:
466481
styler.ctx = copy.deepcopy(self.ctx)
467482
styler._todo = copy.deepcopy(self._todo)
@@ -717,7 +732,7 @@ def set_uuid(self, uuid):
717732

718733
def set_caption(self, caption):
719734
"""
720-
Se the caption on a Styler
735+
Set the caption on a Styler
721736
722737
.. versionadded:: 0.17.1
723738
@@ -763,6 +778,41 @@ def set_table_styles(self, table_styles):
763778
self.table_styles = table_styles
764779
return self
765780

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

11491199

1150-
def _get_level_lengths(index):
1200+
def _get_level_lengths(index, hidden_elements=None):
11511201
"""
11521202
Given an index, find the level lenght for each element.
1203+
Optional argument is a list of index positions which
1204+
should not be visible.
11531205
11541206
Result is a dictionary of (level, inital_position): span
11551207
"""
11561208
sentinel = com.sentinel_factory()
11571209
levels = index.format(sparsify=sentinel, adjoin=False, names=False)
11581210

1159-
if index.nlevels == 1:
1160-
return {(0, i): 1 for i, value in enumerate(levels)}
1211+
if hidden_elements is None:
1212+
hidden_elements = []
11611213

11621214
lengths = {}
1215+
if index.nlevels == 1:
1216+
for i, value in enumerate(levels):
1217+
if(i not in hidden_elements):
1218+
lengths[(0, i)] = 1
1219+
return lengths
11631220

11641221
for i, lvl in enumerate(levels):
11651222
for j, row in enumerate(lvl):
11661223
if not get_option('display.multi_sparse'):
11671224
lengths[(i, j)] = 1
1168-
elif row != sentinel:
1225+
elif (row != sentinel) and (j not in hidden_elements):
11691226
last_label = j
11701227
lengths[(i, last_label)] = 1
1171-
else:
1228+
elif (row != sentinel):
1229+
# even if its hidden, keep track of it in case
1230+
# length >1 and later elemens are visible
1231+
last_label = j
1232+
lengths[(i, last_label)] = 0
1233+
elif(j not in hidden_elements):
11721234
lengths[(i, last_label)] += 1
11731235

1174-
return lengths
1236+
non_zero_lengths = {}
1237+
for element, length in lengths.items():
1238+
if(length >= 1):
1239+
non_zero_lengths[element] = length
1240+
1241+
return non_zero_lengths
11751242

11761243

11771244
def _maybe_wrap_formatter(formatter):

pandas/tests/io/formats/test_style.py

+110
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,116 @@ def test_mi_sparse_column_names(self):
811811
]
812812
assert head == expected
813813

814+
def test_hide_single_index(self):
815+
# single unnamed index
816+
ctx = self.df.style._translate()
817+
self.assertTrue(ctx['body'][0][0]['is_visible'])
818+
self.assertTrue(ctx['head'][0][0]['is_visible'])
819+
ctx2 = self.df.style.hide_index()._translate()
820+
self.assertFalse(ctx2['body'][0][0]['is_visible'])
821+
self.assertFalse(ctx2['head'][0][0]['is_visible'])
822+
823+
# single named index
824+
ctx3 = self.df.set_index('A').style._translate()
825+
self.assertTrue(ctx3['body'][0][0]['is_visible'])
826+
self.assertEqual(len(ctx3['head']), 2) # 2 header levels
827+
self.assertTrue(ctx3['head'][0][0]['is_visible'])
828+
829+
ctx4 = self.df.set_index('A').style.hide_index()._translate()
830+
self.assertFalse(ctx4['body'][0][0]['is_visible'])
831+
self.assertEqual(len(ctx4['head']), 1) # only 1 header levels
832+
self.assertFalse(ctx4['head'][0][0]['is_visible'])
833+
834+
def test_hide_multiindex(self):
835+
df = pd.DataFrame({'A': [1, 2]}, index=pd.MultiIndex.from_arrays(
836+
[['a', 'a'], [0, 1]],
837+
names=['idx_level_0', 'idx_level_1'])
838+
)
839+
ctx1 = df.style._translate()
840+
# tests for 'a' and '0'
841+
self.assertTrue(ctx1['body'][0][0]['is_visible'])
842+
self.assertTrue(ctx1['body'][0][1]['is_visible'])
843+
# check for blank header rows
844+
self.assertTrue(ctx1['head'][0][0]['is_visible'])
845+
self.assertTrue(ctx1['head'][0][1]['is_visible'])
846+
847+
ctx2 = df.style.hide_index()._translate()
848+
# tests for 'a' and '0'
849+
self.assertFalse(ctx2['body'][0][0]['is_visible'])
850+
self.assertFalse(ctx2['body'][0][1]['is_visible'])
851+
# check for blank header rows
852+
self.assertFalse(ctx2['head'][0][0]['is_visible'])
853+
self.assertFalse(ctx2['head'][0][1]['is_visible'])
854+
855+
def test_hide_columns_single_level(self):
856+
# test hiding single column
857+
ctx = self.df.style._translate()
858+
self.assertTrue(ctx['head'][0][1]['is_visible'])
859+
self.assertEqual(ctx['head'][0][1]['display_value'], 'A')
860+
self.assertTrue(ctx['head'][0][2]['is_visible'])
861+
self.assertEqual(ctx['head'][0][2]['display_value'], 'B')
862+
self.assertTrue(ctx['body'][0][1]['is_visible']) # col A, row 1
863+
self.assertTrue(ctx['body'][1][2]['is_visible']) # col B, row 1
864+
865+
ctx = self.df.style.hide_columns('A')._translate()
866+
self.assertFalse(ctx['head'][0][1]['is_visible'])
867+
self.assertFalse(ctx['body'][0][1]['is_visible']) # col A, row 1
868+
self.assertTrue(ctx['body'][1][2]['is_visible']) # col B, row 1
869+
870+
# test hiding mulitiple columns
871+
ctx = self.df.style.hide_columns(['A', 'B'])._translate()
872+
self.assertFalse(ctx['head'][0][1]['is_visible'])
873+
self.assertFalse(ctx['head'][0][2]['is_visible'])
874+
self.assertFalse(ctx['body'][0][1]['is_visible']) # col A, row 1
875+
self.assertFalse(ctx['body'][1][2]['is_visible']) # col B, row 1
876+
877+
def test_hide_columns_mult_levels(self):
878+
# setup dataframe with multiple column levels and indices
879+
i1 = pd.MultiIndex.from_arrays([['a', 'a'], [0, 1]],
880+
names=['idx_level_0',
881+
'idx_level_1'])
882+
i2 = pd.MultiIndex.from_arrays([['b', 'b'], [0, 1]],
883+
names=['col_level_0',
884+
'col_level_1'])
885+
df = pd.DataFrame([[1, 2], [3, 4]], index=i1, columns=i2)
886+
ctx = df.style._translate()
887+
# column headers
888+
self.assertTrue(ctx['head'][0][2]['is_visible'])
889+
self.assertTrue(ctx['head'][1][2]['is_visible'])
890+
self.assertEqual(ctx['head'][1][3]['display_value'], 1)
891+
# indices
892+
self.assertTrue(ctx['body'][0][0]['is_visible'])
893+
# data
894+
self.assertTrue(ctx['body'][1][2]['is_visible'])
895+
self.assertEqual(ctx['body'][1][2]['display_value'], 3)
896+
self.assertTrue(ctx['body'][1][3]['is_visible'])
897+
self.assertEqual(ctx['body'][1][3]['display_value'], 4)
898+
899+
# hide top column level, which hides both columns
900+
ctx = df.style.hide_columns('b')._translate()
901+
self.assertFalse(ctx['head'][0][2]['is_visible']) # b
902+
self.assertFalse(ctx['head'][1][2]['is_visible']) # 0
903+
self.assertFalse(ctx['body'][1][2]['is_visible']) # 3
904+
self.assertTrue(ctx['body'][0][0]['is_visible']) # index
905+
906+
# hide first column only
907+
ctx = df.style.hide_columns([('b', 0)])._translate()
908+
self.assertTrue(ctx['head'][0][2]['is_visible']) # b
909+
self.assertFalse(ctx['head'][1][2]['is_visible']) # 0
910+
self.assertFalse(ctx['body'][1][2]['is_visible']) # 3
911+
self.assertTrue(ctx['body'][1][3]['is_visible'])
912+
self.assertEqual(ctx['body'][1][3]['display_value'], 4)
913+
914+
# hide second column and index
915+
ctx = df.style.hide_columns([('b', 1)]).hide_index()._translate()
916+
self.assertFalse(ctx['body'][0][0]['is_visible']) # index
917+
self.assertTrue(ctx['head'][0][2]['is_visible']) # b
918+
self.assertTrue(ctx['head'][1][2]['is_visible']) # 0
919+
self.assertFalse(ctx['head'][1][3]['is_visible']) # 1
920+
self.assertFalse(ctx['body'][1][3]['is_visible']) # 4
921+
self.assertTrue(ctx['body'][1][2]['is_visible'])
922+
self.assertEqual(ctx['body'][1][2]['display_value'], 3)
923+
814924

815925
class TestStylerMatplotlibDep(object):
816926

0 commit comments

Comments
 (0)