diff --git a/doc/source/style.ipynb b/doc/source/style.ipynb index a78595beabf1d..20f7c2a93b9e6 100644 --- a/doc/source/style.ipynb +++ b/doc/source/style.ipynb @@ -674,13 +674,14 @@ "- precision\n", "- captions\n", "- table-wide styles\n", + "- hiding the index or columns\n", "\n", "Each of these can be specified in two ways:\n", "\n", "- A keyword argument to `Styler.__init__`\n", - "- A call to one of the `.set_` methods, e.g. `.set_caption`\n", + "- A call to one of the `.set_` or `.hide_` methods, e.g. `.set_caption` or `.hide_columns`\n", "\n", - "The best method to use depends on the context. Use the `Styler` constructor when building many styled DataFrames that should all share the same properties. For interactive use, the`.set_` methods are more convenient." + "The best method to use depends on the context. Use the `Styler` constructor when building many styled DataFrames that should all share the same properties. For interactive use, the`.set_` and `.hide_` methods are more convenient." ] }, { @@ -814,6 +815,38 @@ "We hope to collect some useful ones either in pandas, or preferable in a new package that [builds on top](#Extensibility) the tools here." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Hiding the Index or Columns" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The index can be hidden from rendering by calling `Styler.hide_index`. Columns can be hidden from rendering by calling `Styler.hide_columns` and passing in the name of a column, or a slice of columns." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df.style.hide_index()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df.style.hide_columns(['C','D'])" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -875,7 +908,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "from IPython.html import widgets\n", @@ -911,7 +946,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "np.random.seed(25)\n", @@ -1010,7 +1047,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "%mkdir templates" @@ -1027,7 +1066,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "%%file templates/myhtml.tpl\n", @@ -1078,7 +1119,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "MyStyler(df)" @@ -1094,7 +1137,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "HTML(MyStyler(df).render(table_title=\"Extending Example\"))" @@ -1110,7 +1155,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "EasyStyler = Styler.from_custom_template(\"templates\", \"myhtml.tpl\")\n", @@ -1127,7 +1174,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "with open(\"template_structure.html\") as f:\n", @@ -1147,6 +1196,7 @@ "cell_type": "code", "execution_count": null, "metadata": { + "collapsed": true, "nbsphinx": "hidden" }, "outputs": [], @@ -1163,7 +1213,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python [default]", "language": "python", "name": "python3" }, @@ -1177,7 +1227,14 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.1" + "version": "3.5.3" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": {}, + "version_major": 1, + "version_minor": 0 + } } }, "nbformat": 4, diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index 8afdd1b2e22b3..a583dde29c9ed 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -24,7 +24,8 @@ Other Enhancements - Better support for :func:`Dataframe.style.to_excel` output with the ``xlsxwriter`` engine. (:issue:`16149`) - :func:`pandas.tseries.frequencies.to_offset` now accepts leading '+' signs e.g. '+1h'. (:issue:`18171`) -- +- :class:`pandas.io.formats.style.Styler` now has method ``hide_index()`` to determine whether the index will be rendered in ouptut (:issue:`14194`) +- :class:`pandas.io.formats.style.Styler` now has method ``hide_columns()`` to determine whether columns will be hidden in output (:issue:`14194`) .. _whatsnew_0220.api_breaking: diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 776669d6d28db..4fab8c5c3bde5 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -133,6 +133,9 @@ def __init__(self, data, precision=None, table_styles=None, uuid=None, precision = get_option('display.precision') self.precision = precision self.table_attributes = table_attributes + self.hidden_index = False + self.hidden_columns = [] + # display_funcs maps (row, col) -> formatting function def default_display_func(x): @@ -180,6 +183,8 @@ def _translate(self): caption = self.caption ctx = self.ctx precision = self.precision + hidden_index = self.hidden_index + hidden_columns = self.hidden_columns uuid = self.uuid or str(uuid1()).replace("-", "_") ROW_HEADING_CLASS = "row_heading" COL_HEADING_CLASS = "col_heading" @@ -194,7 +199,7 @@ def format_attr(pair): # for sparsifying a MultiIndex idx_lengths = _get_level_lengths(self.index) - col_lengths = _get_level_lengths(self.columns) + col_lengths = _get_level_lengths(self.columns, hidden_columns) cell_context = dict() @@ -217,7 +222,7 @@ def format_attr(pair): row_es = [{"type": "th", "value": BLANK_VALUE, "display_value": BLANK_VALUE, - "is_visible": True, + "is_visible": not hidden_index, "class": " ".join([BLANK_CLASS])}] * (n_rlvls - 1) # ... except maybe the last for columns.names @@ -229,7 +234,7 @@ def format_attr(pair): "value": name, "display_value": name, "class": " ".join(cs), - "is_visible": True}) + "is_visible": not hidden_index}) if clabels: for c, value in enumerate(clabels[r]): @@ -252,7 +257,8 @@ def format_attr(pair): row_es.append(es) head.append(row_es) - if self.data.index.names and _any_not_none(*self.data.index.names): + if (self.data.index.names and _any_not_none(*self.data.index.names) and + not hidden_index): index_header_row = [] for c, name in enumerate(self.data.index.names): @@ -266,7 +272,7 @@ def format_attr(pair): [{"type": "th", "value": BLANK_VALUE, "class": " ".join([BLANK_CLASS]) - }] * len(clabels[0])) + }] * (len(clabels[0]) - len(hidden_columns))) head.append(index_header_row) @@ -278,7 +284,8 @@ def format_attr(pair): "row{row}".format(row=r)] es = { "type": "th", - "is_visible": _is_visible(r, c, idx_lengths), + "is_visible": (_is_visible(r, c, idx_lengths) and + not hidden_index), "value": value, "display_value": value, "id": "_".join(rid[1:]), @@ -302,7 +309,8 @@ def format_attr(pair): "value": value, "class": " ".join(cs), "id": "_".join(cs[1:]), - "display_value": formatter(value) + "display_value": formatter(value), + "is_visible": (c not in hidden_columns) }) props = [] for x in ctx[r, c]: @@ -742,7 +750,7 @@ def set_uuid(self, uuid): def set_caption(self, caption): """ - Se the caption on a Styler + Set the caption on a Styler Parameters ---------- @@ -784,6 +792,40 @@ def set_table_styles(self, table_styles): self.table_styles = table_styles return self + def hide_index(self): + """ + Hide any indices from rendering. + + .. versionadded:: 0.22.0 + + Returns + ------- + self : Styler + """ + self.hidden_index = True + return self + + def hide_columns(self, subset): + """ + Hide columns from rendering. + + .. versionadded:: 0.22.0 + + Parameters + ---------- + subset: IndexSlice + An argument to ``DataFrame.loc`` that identifies which columns + are hidden. + + Returns + ------- + self : Styler + """ + subset = _non_reducing_slice(subset) + hidden_df = self.data.loc[subset] + self.hidden_columns = self.columns.get_indexer_for(hidden_df.columns) + return self + # ----------------------------------------------------------------------- # A collection of "builtin" styles # ----------------------------------------------------------------------- @@ -1158,31 +1200,48 @@ def _is_visible(idx_row, idx_col, lengths): return (idx_col, idx_row) in lengths -def _get_level_lengths(index): +def _get_level_lengths(index, hidden_elements=None): """ Given an index, find the level lenght for each element. + Optional argument is a list of index positions which + should not be visible. Result is a dictionary of (level, inital_position): span """ sentinel = sentinel_factory() levels = index.format(sparsify=sentinel, adjoin=False, names=False) - if index.nlevels == 1: - return {(0, i): 1 for i, value in enumerate(levels)} + if hidden_elements is None: + hidden_elements = [] lengths = {} + if index.nlevels == 1: + for i, value in enumerate(levels): + if(i not in hidden_elements): + lengths[(0, i)] = 1 + return lengths for i, lvl in enumerate(levels): for j, row in enumerate(lvl): if not get_option('display.multi_sparse'): lengths[(i, j)] = 1 - elif row != sentinel: + elif (row != sentinel) and (j not in hidden_elements): last_label = j lengths[(i, last_label)] = 1 - else: + elif (row != sentinel): + # even if its hidden, keep track of it in case + # length >1 and later elemens are visible + last_label = j + lengths[(i, last_label)] = 0 + elif(j not in hidden_elements): lengths[(i, last_label)] += 1 - return lengths + non_zero_lengths = {} + for element, length in lengths.items(): + if(length >= 1): + non_zero_lengths[element] = length + + return non_zero_lengths def _maybe_wrap_formatter(formatter): diff --git a/pandas/tests/io/formats/test_style.py b/pandas/tests/io/formats/test_style.py index 811381e4cbd2a..62f1f0c39ce8b 100644 --- a/pandas/tests/io/formats/test_style.py +++ b/pandas/tests/io/formats/test_style.py @@ -891,6 +891,120 @@ def test_mi_sparse_column_names(self): ] assert head == expected + def test_hide_single_index(self): + # GH 14194 + # single unnamed index + ctx = self.df.style._translate() + assert ctx['body'][0][0]['is_visible'] + assert ctx['head'][0][0]['is_visible'] + ctx2 = self.df.style.hide_index()._translate() + assert not ctx2['body'][0][0]['is_visible'] + assert not ctx2['head'][0][0]['is_visible'] + + # single named index + ctx3 = self.df.set_index('A').style._translate() + assert ctx3['body'][0][0]['is_visible'] + assert len(ctx3['head']) == 2 # 2 header levels + assert ctx3['head'][0][0]['is_visible'] + + ctx4 = self.df.set_index('A').style.hide_index()._translate() + assert not ctx4['body'][0][0]['is_visible'] + assert len(ctx4['head']) == 1 # only 1 header levels + assert not ctx4['head'][0][0]['is_visible'] + + def test_hide_multiindex(self): + # GH 14194 + df = pd.DataFrame({'A': [1, 2]}, index=pd.MultiIndex.from_arrays( + [['a', 'a'], [0, 1]], + names=['idx_level_0', 'idx_level_1']) + ) + ctx1 = df.style._translate() + # tests for 'a' and '0' + assert ctx1['body'][0][0]['is_visible'] + assert ctx1['body'][0][1]['is_visible'] + # check for blank header rows + assert ctx1['head'][0][0]['is_visible'] + assert ctx1['head'][0][1]['is_visible'] + + ctx2 = df.style.hide_index()._translate() + # tests for 'a' and '0' + assert not ctx2['body'][0][0]['is_visible'] + assert not ctx2['body'][0][1]['is_visible'] + # check for blank header rows + assert not ctx2['head'][0][0]['is_visible'] + assert not ctx2['head'][0][1]['is_visible'] + + def test_hide_columns_single_level(self): + # GH 14194 + # test hiding single column + ctx = self.df.style._translate() + assert ctx['head'][0][1]['is_visible'] + assert ctx['head'][0][1]['display_value'] == 'A' + assert ctx['head'][0][2]['is_visible'] + assert ctx['head'][0][2]['display_value'] == 'B' + assert ctx['body'][0][1]['is_visible'] # col A, row 1 + assert ctx['body'][1][2]['is_visible'] # col B, row 1 + + ctx = self.df.style.hide_columns('A')._translate() + assert not ctx['head'][0][1]['is_visible'] + assert not ctx['body'][0][1]['is_visible'] # col A, row 1 + assert ctx['body'][1][2]['is_visible'] # col B, row 1 + + # test hiding mulitiple columns + ctx = self.df.style.hide_columns(['A', 'B'])._translate() + assert not ctx['head'][0][1]['is_visible'] + assert not ctx['head'][0][2]['is_visible'] + assert not ctx['body'][0][1]['is_visible'] # col A, row 1 + assert not ctx['body'][1][2]['is_visible'] # col B, row 1 + + def test_hide_columns_mult_levels(self): + # GH 14194 + # setup dataframe with multiple column levels and indices + i1 = pd.MultiIndex.from_arrays([['a', 'a'], [0, 1]], + names=['idx_level_0', + 'idx_level_1']) + i2 = pd.MultiIndex.from_arrays([['b', 'b'], [0, 1]], + names=['col_level_0', + 'col_level_1']) + df = pd.DataFrame([[1, 2], [3, 4]], index=i1, columns=i2) + ctx = df.style._translate() + # column headers + assert ctx['head'][0][2]['is_visible'] + assert ctx['head'][1][2]['is_visible'] + assert ctx['head'][1][3]['display_value'] == 1 + # indices + assert ctx['body'][0][0]['is_visible'] + # data + assert ctx['body'][1][2]['is_visible'] + assert ctx['body'][1][2]['display_value'] == 3 + assert ctx['body'][1][3]['is_visible'] + assert ctx['body'][1][3]['display_value'] == 4 + + # hide top column level, which hides both columns + ctx = df.style.hide_columns('b')._translate() + assert not ctx['head'][0][2]['is_visible'] # b + assert not ctx['head'][1][2]['is_visible'] # 0 + assert not ctx['body'][1][2]['is_visible'] # 3 + assert ctx['body'][0][0]['is_visible'] # index + + # hide first column only + ctx = df.style.hide_columns([('b', 0)])._translate() + assert ctx['head'][0][2]['is_visible'] # b + assert not ctx['head'][1][2]['is_visible'] # 0 + assert not ctx['body'][1][2]['is_visible'] # 3 + assert ctx['body'][1][3]['is_visible'] + assert ctx['body'][1][3]['display_value'] == 4 + + # hide second column and index + ctx = df.style.hide_columns([('b', 1)]).hide_index()._translate() + assert not ctx['body'][0][0]['is_visible'] # index + assert ctx['head'][0][2]['is_visible'] # b + assert ctx['head'][1][2]['is_visible'] # 0 + assert not ctx['head'][1][3]['is_visible'] # 1 + assert not ctx['body'][1][3]['is_visible'] # 4 + assert ctx['body'][1][2]['is_visible'] + assert ctx['body'][1][2]['display_value'] == 3 + class TestStylerMatplotlibDep(object):