Repository: pandas-dev/pandas
Pull request: ENH 14194: add style option for hiding index and columns
 - [ x] closes #14194 
 - [ x] tests added / passed
 - [ x] passes ``git diff upstream/master --name-only -- '*.py' | flake8 --diff``
 - [x] whatsnew entry


Problem statement:
ENH: DataFrame.style option for hiding the index
#### Code Sample, a copy-pastable example if possible

``` python
df = pd.DataFrame(np.random.randn(10, 2))
df.style.set_include_index(False)  # name TBD
```
#### Expected Output

Same output, but without the index

Would maybe want a way to hide the columns too.

---

Current workaround

``` python
df = pd.DataFrame(np.random.randn(10, 2))
df.style.set_table_styles([
    {'selector': '.row_heading, .blank', 'props': [('display', 'none;')]}
])
```

This doesn't work if there's a `df.index.name` and almost surely doesn't work with MultiIndexes.

Hints from issue discussion:

Commit message: fix merge

Patch to review:
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..1a5ac9bda0ec2 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 ``index`` parameter and corresponding method ``hide_index()`` to determine whether the index will be rendered in ouptut (:issue:`14194`)
+- :class:`pandas.io.formats.style.Styler` now has ``hidden_cols`` parameter and corresponding 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):
 


Code review comments:
diff_hunk: @@ -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 ``index`` parameter and corresponding method ``hide_index()`` to determine whether the index will be rendered in ouptut (:issue:`14194`)
+- :class:`pandas.io.formats.style.Styler` now has ``hidden_cols`` parameter and corresponding method ``hide_columns()`` to determine whether columns will be hidden in output (:issue:`14194`)
path: doc/source/whatsnew/v0.22.0.txt
text: @user1:
hide_cols -> hide_columns

@user1:
didn't we eliminate the paramaters as user facing and only use the methods? (which i think is better anyhow)

@author:
@user1 yes, thanks, i should have caught that. fixed!