From dda6ee91d1b1eb007225587e2e507b2a499c22d6 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Thu, 2 Mar 2017 12:43:46 +0100 Subject: [PATCH 01/11] ENH: _to_str_columns accepts now optional header Hereby also to_latex and to_string accept optional headers --- pandas/formats/format.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/pandas/formats/format.py b/pandas/formats/format.py index 9dde3b0001c31..8d857694e6329 100644 --- a/pandas/formats/format.py +++ b/pandas/formats/format.py @@ -489,30 +489,48 @@ def _to_str_columns(self): str_index = self._get_formatted_index(frame) str_columns = self._get_formatted_column_labels(frame) - if self.header: + has_aliases = isinstance(self.header, (tuple, list, np.ndarray, Index)) + if not (has_aliases or self.header): stringified = [] for i, c in enumerate(frame): - cheader = str_columns[i] - max_colwidth = max(self.col_space or 0, *(self.adj.len(x) - for x in cheader)) fmt_values = self._format_col(i) fmt_values = _make_fixed_width(fmt_values, self.justify, - minimum=max_colwidth, + minimum=(self.col_space or 0), + adj=self.adj) + stringified.append(fmt_values) + elif has_aliases: + if len(self.header) != len(self.columns): + raise ValueError(('Writing %d cols but got %d aliases' + % (len(self.columns), len(self.header)))) + stringified = [] + for i, c in enumerate(frame): + cheader = [self.header[i]] + header_colwidth = max(self.col_space or 0, + *(self.adj.len(x) for x in cheader)) + fmt_values = self._format_col(i) + fmt_values = _make_fixed_width(fmt_values, self.justify, + minimum=header_colwidth, adj=self.adj) max_len = max(np.max([self.adj.len(x) for x in fmt_values]), - max_colwidth) + header_colwidth) cheader = self.adj.justify(cheader, max_len, mode=self.justify) stringified.append(cheader + fmt_values) else: stringified = [] for i, c in enumerate(frame): + cheader = str_columns[i] + header_colwidth = max(self.col_space or 0, + *(self.adj.len(x) for x in cheader)) fmt_values = self._format_col(i) fmt_values = _make_fixed_width(fmt_values, self.justify, - minimum=(self.col_space or 0), + minimum=header_colwidth, adj=self.adj) - stringified.append(fmt_values) + max_len = max(np.max([self.adj.len(x) for x in fmt_values]), + header_colwidth) + cheader = self.adj.justify(cheader, max_len, mode=self.justify) + stringified.append(cheader + fmt_values) strcols = stringified if self.index: From a9bec302bd0b52aa2a93f3c77c0150582b24a283 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Thu, 2 Mar 2017 13:46:28 +0100 Subject: [PATCH 02/11] Added tests for to_string and to_latex --- pandas/tests/formats/test_format.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pandas/tests/formats/test_format.py b/pandas/tests/formats/test_format.py index ddf9d35841ce7..7bae22d185404 100644 --- a/pandas/tests/formats/test_format.py +++ b/pandas/tests/formats/test_format.py @@ -1125,6 +1125,17 @@ def test_to_string_no_header(self): self.assertEqual(df_s, expected) + def test_to_latex_specified_header(self): + df = DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]}) + + df_s = df.to_string(header=['X', 'Y']) + expected = ' X Y\n0 1 4\n1 2 5\n2 3 6' + + self.assertEqual(df_s, expected) + + with tm.assertRaises(ValueError): + df.to_string(header=['X']) + def test_to_string_no_index(self): df = DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]}) From 384fbd7e6b6d5018bf9e591aef675962c7cd3ce3 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Thu, 2 Mar 2017 13:47:08 +0100 Subject: [PATCH 03/11] Added tests for to_latex --- pandas/tests/formats/test_to_latex.py | 45 +++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/pandas/tests/formats/test_to_latex.py b/pandas/tests/formats/test_to_latex.py index 17e1e18f03dd6..29ead83f3bcd9 100644 --- a/pandas/tests/formats/test_to_latex.py +++ b/pandas/tests/formats/test_to_latex.py @@ -428,6 +428,51 @@ def test_to_latex_no_header(self): assert withoutindex_result == withoutindex_expected + def test_to_latex_specified_header(self): + # GH 7124 + df = DataFrame({'a': [1, 2], 'b': ['b1', 'b2']}) + withindex_result = df.to_latex(header=['AA', 'BB']) + withindex_expected = r"""\begin{tabular}{lrl} +\toprule +{} & AA & BB \\ +\midrule +0 & 1 & b1 \\ +1 & 2 & b2 \\ +\bottomrule +\end{tabular} +""" + + assert withindex_result == withindex_expected + + withoutindex_result = df.to_latex(header=['AA', 'BB'], index=False) + withoutindex_expected = r"""\begin{tabular}{rl} +\toprule +AA & BB \\ +\midrule + 1 & b1 \\ + 2 & b2 \\ +\bottomrule +\end{tabular} +""" + + assert withoutindex_result == withoutindex_expected + + withoutescape_result = df.to_latex(header=['$A$', '$B$'], escape=False) + withoutescape_expected = r"""\begin{tabular}{lrl} +\toprule +{} & $A$ & $B$ \\ +\midrule +0 & 1 & b1 \\ +1 & 2 & b2 \\ +\bottomrule +\end{tabular} +""" + + assert withoutescape_result == withoutescape_expected + + with tm.assertRaises(ValueError): + df.to_latex(header=['A']) + def test_to_latex_decimal(self, frame): # GH 12031 frame.to_latex() From 64c45c570e21a6e87d4bb02972175230afb2f3f5 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Thu, 2 Mar 2017 15:00:19 +0100 Subject: [PATCH 04/11] renamed test_to_string_specified_header --- pandas/tests/formats/test_format.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/formats/test_format.py b/pandas/tests/formats/test_format.py index 7bae22d185404..b1f163ccf9429 100644 --- a/pandas/tests/formats/test_format.py +++ b/pandas/tests/formats/test_format.py @@ -1125,7 +1125,7 @@ def test_to_string_no_header(self): self.assertEqual(df_s, expected) - def test_to_latex_specified_header(self): + def test_to_string_specified_header(self): df = DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]}) df_s = df.to_string(header=['X', 'Y']) From 78ec8aa9ec5026491c43ec41004c8658dabed56e Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Thu, 2 Mar 2017 13:46:28 +0100 Subject: [PATCH 05/11] Added tests for to_string and to_latex --- pandas/tests/formats/test_format.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/formats/test_format.py b/pandas/tests/formats/test_format.py index b1f163ccf9429..7bae22d185404 100644 --- a/pandas/tests/formats/test_format.py +++ b/pandas/tests/formats/test_format.py @@ -1125,7 +1125,7 @@ def test_to_string_no_header(self): self.assertEqual(df_s, expected) - def test_to_string_specified_header(self): + def test_to_latex_specified_header(self): df = DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]}) df_s = df.to_string(header=['X', 'Y']) From 87a38384578101c83d45c90ebb4c3370d082cbf1 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Thu, 2 Mar 2017 15:00:19 +0100 Subject: [PATCH 06/11] renamed test_to_string_specified_header --- pandas/tests/formats/test_format.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/formats/test_format.py b/pandas/tests/formats/test_format.py index 7bae22d185404..b1f163ccf9429 100644 --- a/pandas/tests/formats/test_format.py +++ b/pandas/tests/formats/test_format.py @@ -1125,7 +1125,7 @@ def test_to_string_no_header(self): self.assertEqual(df_s, expected) - def test_to_latex_specified_header(self): + def test_to_string_specified_header(self): df = DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]}) df_s = df.to_string(header=['X', 'Y']) From b010d16dd209e48780c367cb82f7909ebf66b850 Mon Sep 17 00:00:00 2001 From: Ben Thayer Date: Thu, 2 Mar 2017 08:16:48 -0500 Subject: [PATCH 07/11] ENH: Added FrozenList difference setop closes #15475 Author: Ben Thayer Author: bthayer2365 Closes #15506 from bthayer2365/frozen-index and squashes the following commits: 428a1b3 [Ben Thayer] Added __iadd__ test, fixed whatsnew 84ba405 [Ben Thayer] Merge branch 'master' of github.com:pandas-dev/pandas into frozen-index 8dbde1e [Ben Thayer] Rebased to upstream/master 6f6c140 [Ben Thayer] Added docstrings, depricated __iadd__, changed __add__ to use self.union() 66b3b91 [Ben Thayer] Fixed issue number 3d6cee5 [Ben Thayer] Depricated __add__ in favor of union ccd75c7 [Ben Thayer] Changed __sub__ to difference cd7de26 [Ben Thayer] Added versionadded tag in docs and renamed test_inplace to test_inplace_add for consistency 0ea8d21 [Ben Thayer] Added __isub__ and groupby example to docs 79dd958 [Ben Thayer] Updated whatsnew to reflect changes 0fc7e19 [Ben Thayer] Removed whitespace 73564ab [Ben Thayer] Added FrozenList subtraction fee7a7d [bthayer2365] Merge branch 'master' into frozen-index 6a2b48d [Ben Thayer] Added docstrings, depricated __iadd__, changed __add__ to use self.union() 2ab85cb [Ben Thayer] Fixed issue number cb95089 [Ben Thayer] Depricated __add__ in favor of union 2e43849 [Ben Thayer] Changed __sub__ to difference fdcfbbb [Ben Thayer] Added versionadded tag in docs and renamed test_inplace to test_inplace_add for consistency 2fad2f7 [Ben Thayer] Added __isub__ and groupby example to docs cd73faa [Ben Thayer] Updated whatsnew to reflect changes f6381a8 [Ben Thayer] Removed whitespace ada7cda [Ben Thayer] Added FrozenList subtraction --- doc/source/groupby.rst | 10 +++++++++ doc/source/whatsnew/v0.20.0.txt | 6 ++++++ pandas/indexes/frozen.py | 24 ++++++++++++++++++--- pandas/tests/indexes/test_frozen.py | 33 +++++++++++++++++++++-------- 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/doc/source/groupby.rst b/doc/source/groupby.rst index 8484ccd69a983..2d406de7c0c9b 100644 --- a/doc/source/groupby.rst +++ b/doc/source/groupby.rst @@ -126,6 +126,16 @@ We could naturally group by either the ``A`` or ``B`` columns or both: grouped = df.groupby('A') grouped = df.groupby(['A', 'B']) +.. versionadded:: 0.20 + +If we also have a MultiIndex on columns ``A`` and ``B``, we can group by all +but the specified columns. + +.. ipython:: python + + df2 = df.set_index(['A', 'B']) + grouped = df2.groupby(level=df2.index.names.difference(['B']) + These will split the DataFrame on its index (rows). We could also split by the columns: diff --git a/doc/source/whatsnew/v0.20.0.txt b/doc/source/whatsnew/v0.20.0.txt index 725dc7fc52ed0..0dcff24de7757 100644 --- a/doc/source/whatsnew/v0.20.0.txt +++ b/doc/source/whatsnew/v0.20.0.txt @@ -30,6 +30,8 @@ New features - Integration with the ``feather-format``, including a new top-level ``pd.read_feather()`` and ``DataFrame.to_feather()`` method, see :ref:`here `. - ``Series.str.replace()`` now accepts a callable, as replacement, which is passed to ``re.sub`` (:issue:`15055`) - ``Series.str.replace()`` now accepts a compiled regular expression as a pattern (:issue:`15446`) +- ``.str.replace`` now accepts a callable, as replacement, which is passed to ``re.sub`` (:issue:`15055`) +- ``FrozenList`` has gained the ``.difference()`` setop method (:issue:`15475`) @@ -601,12 +603,16 @@ Deprecations - ``Series.sortlevel`` and ``DataFrame.sortlevel`` have been deprecated in favor of ``Series.sort_index`` and ``DataFrame.sort_index`` (:issue:`15099`) - importing ``concat`` from ``pandas.tools.merge`` has been deprecated in favor of imports from the ``pandas`` namespace. This should only affect explict imports (:issue:`15358`) - ``Series/DataFrame/Panel.consolidate()`` been deprecated as a public method. (:issue:`15483`) +<<<<<<< HEAD - The following top-level pandas functions have been deprecated and will be removed in a future version (:issue:`13790`) * ``pd.pnow()``, replaced by ``Period.now()`` * ``pd.Term``, is removed, as it is not applicable to user code. Instead use in-line string expressions in the where clause when searching in HDFStore * ``pd.Expr``, is removed, as it is not applicable to user code. * ``pd.match()``, is removed. * ``pd.groupby()``, replaced by using the ``.groupby()`` method directly on a ``Series/DataFrame`` +======= +- ``FrozenList`` addition (new object and inplace) have been deprecated in favor of the ``.union()`` method. (:issue: `15475`) +>>>>>>> 478003b... ENH: Added FrozenList difference setop .. _whatsnew_0200.prior_deprecations: diff --git a/pandas/indexes/frozen.py b/pandas/indexes/frozen.py index e043ba64bbad7..47e2557333ec7 100644 --- a/pandas/indexes/frozen.py +++ b/pandas/indexes/frozen.py @@ -13,6 +13,8 @@ from pandas.types.cast import _coerce_indexer_dtype from pandas.formats.printing import pprint_thing +import warnings + class FrozenList(PandasObject, list): @@ -25,11 +27,14 @@ class FrozenList(PandasObject, list): # typechecks def __add__(self, other): + warnings.warn("__add__ is deprecated, use union(...)", FutureWarning) + return self.union(other) + + def __iadd__(self, other): + warnings.warn("__iadd__ is deprecated, use union(...)", FutureWarning) if isinstance(other, tuple): other = list(other) - return self.__class__(super(FrozenList, self).__add__(other)) - - __iadd__ = __add__ + return super(FrozenList, self).__iadd__(other) # Python 2 compat def __getslice__(self, i, j): @@ -80,6 +85,19 @@ def __repr__(self): __setitem__ = __setslice__ = __delitem__ = __delslice__ = _disabled pop = append = extend = remove = sort = insert = _disabled + def union(self, other): + """Returns a FrozenList with other concatenated to the end of self""" + if isinstance(other, tuple): + other = list(other) + return self.__class__(super(FrozenList, self).__add__(other)) + + def difference(self, other): + """Returns a FrozenList with the same elements as self, but with elements + that are also in other removed.""" + other = set(other) + temp = [x for x in self if x not in other] + return self.__class__(temp) + class FrozenNDArray(PandasObject, np.ndarray): diff --git a/pandas/tests/indexes/test_frozen.py b/pandas/tests/indexes/test_frozen.py index a82409fbf9513..a5fbf066adc83 100644 --- a/pandas/tests/indexes/test_frozen.py +++ b/pandas/tests/indexes/test_frozen.py @@ -15,20 +15,35 @@ def setUp(self): self.klass = FrozenList def test_add(self): - result = self.container + (1, 2, 3) + q = FrozenList([1]) + with tm.assert_produces_warning(FutureWarning, + check_stacklevel=False): + q = q + [2, 3] + expected = FrozenList([1, 2, 3]) + self.check_result(q, expected) + + def test_iadd(self): + q = FrozenList([1]) + with tm.assert_produces_warning(FutureWarning, + check_stacklevel=False): + q += [2, 3] + expected = FrozenList([1, 2, 3]) + self.check_result(q, expected) + + def test_union(self): + result = self.container.union((1, 2, 3)) expected = FrozenList(self.lst + [1, 2, 3]) self.check_result(result, expected) - result = (1, 2, 3) + self.container - expected = FrozenList([1, 2, 3] + self.lst) + def test_difference(self): + result = self.container.difference([2]) + expected = FrozenList([1, 3, 4, 5]) self.check_result(result, expected) - def test_inplace(self): - q = r = self.container - q += [5] - self.check_result(q, self.lst + [5]) - # other shouldn't be mutated - self.check_result(r, self.lst) + def test_difference_dupe(self): + result = FrozenList([1, 2, 3, 2]).difference([2]) + expected = FrozenList([1, 3]) + self.check_result(result, expected) class TestFrozenNDArray(CheckImmutable, CheckStringMixin, tm.TestCase): From 82a9e3e04a82dbc935c0532663001ef7ae8eb765 Mon Sep 17 00:00:00 2001 From: Jeff Reback Date: Thu, 2 Mar 2017 09:23:58 -0500 Subject: [PATCH 08/11] TST: remove deprecated usages of FrozenList.__add__ from test code xref #15506 --- pandas/core/panel.py | 6 +++--- pandas/core/reshape.py | 6 +++--- pandas/core/strings.py | 2 +- pandas/tests/groupby/test_value_counts.py | 2 +- pandas/tools/concat.py | 2 +- test_fast.sh | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pandas/core/panel.py b/pandas/core/panel.py index 4a6c6cf291316..c5ea513223dce 100644 --- a/pandas/core/panel.py +++ b/pandas/core/panel.py @@ -940,9 +940,9 @@ def construct_index_parts(idx, major=True): minor_labels, minor_levels, minor_names = construct_index_parts( self.minor_axis, major=False) - levels = major_levels + minor_levels - labels = major_labels + minor_labels - names = major_names + minor_names + levels = list(major_levels) + list(minor_levels) + labels = list(major_labels) + list(minor_labels) + names = list(major_names) + list(minor_names) index = MultiIndex(levels=levels, labels=labels, names=names, verify_integrity=False) diff --git a/pandas/core/reshape.py b/pandas/core/reshape.py index 7bcd9f2d30b79..4dad410d52739 100644 --- a/pandas/core/reshape.py +++ b/pandas/core/reshape.py @@ -254,8 +254,8 @@ def get_new_columns(self): width = len(self.value_columns) propagator = np.repeat(np.arange(width), stride) if isinstance(self.value_columns, MultiIndex): - new_levels = self.value_columns.levels + (self.removed_level,) - new_names = self.value_columns.names + (self.removed_name,) + new_levels = self.value_columns.levels.union((self.removed_level,)) + new_names = self.value_columns.names.union((self.removed_name,)) new_labels = [lab.take(propagator) for lab in self.value_columns.labels] @@ -844,7 +844,7 @@ def melt(frame, id_vars=None, value_vars=None, var_name=None, for col in id_vars: mdata[col] = np.tile(frame.pop(col).values, K) - mcolumns = id_vars + var_name + [value_name] + mcolumns = list(id_vars) + list(var_name) + list([value_name]) mdata[value_name] = frame.values.ravel('F') for i, col in enumerate(var_name): diff --git a/pandas/core/strings.py b/pandas/core/strings.py index 46ba48b4cd846..686613e1fca0b 100644 --- a/pandas/core/strings.py +++ b/pandas/core/strings.py @@ -822,7 +822,7 @@ def str_extractall(arr, pat, flags=0): if 0 < len(index_list): from pandas import MultiIndex index = MultiIndex.from_tuples( - index_list, names=arr.index.names + ["match"]) + index_list, names=arr.index.names.union(["match"])) else: index = None result = arr._constructor_expanddim(match_list, index=index, diff --git a/pandas/tests/groupby/test_value_counts.py b/pandas/tests/groupby/test_value_counts.py index 801d0da070112..ff01df2693c7c 100644 --- a/pandas/tests/groupby/test_value_counts.py +++ b/pandas/tests/groupby/test_value_counts.py @@ -28,7 +28,7 @@ def check_value_counts(df, keys, bins): gr = df.groupby(keys, sort=isort) right = gr['3rd'].apply(Series.value_counts, **kwargs) - right.index.names = right.index.names[:-1] + ['3rd'] + right.index.names = right.index.names[:-1].union(['3rd']) # have to sort on index because of unstable sort on values left, right = map(rebuild_index, (left, right)) # xref GH9212 diff --git a/pandas/tools/concat.py b/pandas/tools/concat.py index 6405106118472..ae9d7af9d98ff 100644 --- a/pandas/tools/concat.py +++ b/pandas/tools/concat.py @@ -574,7 +574,7 @@ def _make_concat_multiindex(indexes, keys, levels=None, names=None): " not have the same number of levels") # also copies - names = names + _get_consensus_names(indexes) + names = list(names) + list(_get_consensus_names(indexes)) return MultiIndex(levels=levels, labels=label_list, names=names, verify_integrity=False) diff --git a/test_fast.sh b/test_fast.sh index 30ac7f84cbe8b..f22ab73277e8b 100755 --- a/test_fast.sh +++ b/test_fast.sh @@ -5,4 +5,4 @@ # https://github.com/pytest-dev/pytest/issues/1075 export PYTHONHASHSEED=$(python -c 'import random; print(random.randint(1, 4294967295))') -pytest pandas --skip-slow --skip-network -m "not single" -n 4 +pytest pandas --skip-slow --skip-network -m "not single" -n 4 $@ From 60822e95aa6325d5b6ebe01f8462a982b8c18ac2 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Thu, 2 Mar 2017 21:46:40 +0100 Subject: [PATCH 09/11] Added WhatsNew + Docstrings to_latex to_string --- doc/source/whatsnew/v0.20.0.txt | 1 + pandas/core/frame.py | 5 +++++ pandas/formats/format.py | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.20.0.txt b/doc/source/whatsnew/v0.20.0.txt index 0dcff24de7757..4cb844fb3e18f 100644 --- a/doc/source/whatsnew/v0.20.0.txt +++ b/doc/source/whatsnew/v0.20.0.txt @@ -230,6 +230,7 @@ Other enhancements - ``pd.TimedeltaIndex`` now has a custom datetick formatter specifically designed for nanosecond level precision (:issue:`8711`) - ``pd.types.concat.union_categoricals`` gained the ``ignore_ordered`` argument to allow ignoring the ordered attribute of unioned categoricals (:issue:`13410`). See the :ref:`categorical union docs ` for more information. - ``pandas.io.json.json_normalize()`` with an empty ``list`` will return an empty ``DataFrame`` (:issue:`15534`) +- ``pd.DataFrame.to_latex`` and ``pd.DataFrame.to_string`` now allow optional header aliases. (:issue:`15536`) .. _ISO 8601 duration: https://en.wikipedia.org/wiki/ISO_8601#Durations diff --git a/pandas/core/frame.py b/pandas/core/frame.py index b3e43edc3eb55..b776161196f52 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -1529,6 +1529,8 @@ def to_feather(self, fname): from pandas.io.feather_format import to_feather to_feather(self, fname) + @Substitution('Write out column names. If a list of string is given, \ +it is assumed to be aliases for the column names') @Appender(fmt.docstring_to_string, indents=1) def to_string(self, buf=None, columns=None, col_space=None, header=True, index=True, na_rep='NaN', formatters=None, float_format=None, @@ -1556,6 +1558,7 @@ def to_string(self, buf=None, columns=None, col_space=None, header=True, result = formatter.buf.getvalue() return result + @Substitution('whether to print column labels, default True') @Appender(fmt.docstring_to_string, indents=1) def to_html(self, buf=None, columns=None, col_space=None, header=True, index=True, na_rep='NaN', formatters=None, float_format=None, @@ -1609,6 +1612,8 @@ def to_html(self, buf=None, columns=None, col_space=None, header=True, if buf is None: return formatter.buf.getvalue() + @Substitution('Write out column names. If a list of string is given, \ +it is assumed to be aliases for the column names.') @Appender(fmt.common_docstring + fmt.return_docstring, indents=1) def to_latex(self, buf=None, columns=None, col_space=None, header=True, index=True, na_rep='NaN', formatters=None, float_format=None, diff --git a/pandas/formats/format.py b/pandas/formats/format.py index 8d857694e6329..d162445b1b1b6 100644 --- a/pandas/formats/format.py +++ b/pandas/formats/format.py @@ -53,7 +53,7 @@ col_space : int, optional the minimum width of each column header : bool, optional - whether to print column labels, default True + %s index : bool, optional whether to print index (row) labels, default True na_rep : string, optional From 915e8a072a667a493c0fa5124746e69bae7b4ad3 Mon Sep 17 00:00:00 2001 From: Oskar Weser Date: Fri, 3 Mar 2017 18:06:20 +0100 Subject: [PATCH 10/11] Nicer logic in _to_str_columns +Modified docstring --- doc/source/html-styling.html | 14392 +++++++++++++++++++++++++++++++++ pandas/core/frame.py | 6 +- pandas/formats/format.py | 36 +- 3 files changed, 14407 insertions(+), 27 deletions(-) create mode 100644 doc/source/html-styling.html diff --git a/doc/source/html-styling.html b/doc/source/html-styling.html new file mode 100644 index 0000000000000..eb0c3a12b65d8 --- /dev/null +++ b/doc/source/html-styling.html @@ -0,0 +1,14392 @@ + +
+
+
+
+
+

New in version 0.17.1

+

Provisional: This is a new feature and still under development. We'll be adding features and possibly making breaking changes in future releases. We'd love to hear your feedback.

+

This document is written as a Jupyter Notebook, and can be viewed or downloaded here.

+

You can apply conditional formatting, the visual styling of a DataFrame +depending on the data within, by using the DataFrame.style property. +This is a property that returns a pandas.Styler object, which has +useful methods for formatting and displaying DataFrames.

+

The styling is accomplished using CSS. +You write "style functions" that take scalars, DataFrames or Series, and return like-indexed DataFrames or Series with CSS "attribute: value" pairs for the values. +These functions can be incrementally passed to the Styler which collects the styles before rendering.

+

Contents

+ +
+
+
+
+
+
+
+
+

Building Styles

Pass your style functions into one of the following methods:

+
    +
  • Styler.applymap: elementwise
  • +
  • Styler.apply: column-/row-/table-wise
  • +
+

Both of those methods take a function (and some other keyword arguments) and applies your function to the DataFrame in a certain way. +Styler.applymap works through the DataFrame elementwise. +Styler.apply passes each column or row into your DataFrame one-at-a-time or the entire table at once, depending on the axis keyword argument. +For columnwise use axis=0, rowwise use axis=1, and for the entire table at once use axis=None.

+

For Styler.applymap your function should take a scalar and return a single string with the CSS attribute-value pair.

+

For Styler.apply your function should take a Series or DataFrame (depending on the axis parameter), and return a Series or DataFrame with an identical shape where each value is a string with a CSS attribute-value pair.

+

Let's see some examples.

+ +
+
+
+
+
+
In [1]:
+
+
+
import pandas as pd
+import numpy as np
+
+np.random.seed(24)
+df = pd.DataFrame({'A': np.linspace(1, 10, 10)})
+df = pd.concat([df, pd.DataFrame(np.random.randn(10, 4), columns=list('BCDE'))],
+               axis=1)
+df.iloc[0, 2] = np.nan
+
+ +
+
+
+ +
+
+
+
+
+
+

Here's a boring example of rendering a DataFrame, without any (visible) styles:

+ +
+
+
+
+
+
In [2]:
+
+
+
df.style
+
+ +
+
+
+ +
+
+ + +
Out[2]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

Note: The DataFrame.style attribute is a propetry that returns a Styler object. Styler has a _repr_html_ method defined on it so they are rendered automatically. If you want the actual HTML back for further processing or for writing to file call the .render() method which returns a string.

+

The above output looks very similar to the standard DataFrame HTML representation. But we've done some work behind the scenes to attach CSS classes to each cell. We can view these by calling the .render method.

+ +
+
+
+
+
+
In [3]:
+
+
+
df.style.highlight_null().render().split('\n')[:10]
+
+ +
+
+
+ +
+
+ + +
Out[3]:
+ + +
+
['',
+ '        <style  type="text/css" >',
+ '        ',
+ '        ',
+ '            #T_e35153b9_ff86_11e6_b5d1_c86000c2e7a8row0_col2 {',
+ '            ',
+ '                background-color:  red;',
+ '            ',
+ '            }',
+ '        ']
+
+ +
+ +
+
+ +
+
+
+
+
+
+

The row0_col2 is the identifier for that particular cell. We've also prepended each row/column identifier with a UUID unique to each DataFrame so that the style from one doesn't collide with the styling from another within the same notebook or page (you can set the uuid if you'd like to tie together the styling of two DataFrames).

+

When writing style functions, you take care of producing the CSS attribute / value pairs you want. Pandas matches those up with the CSS classes that identify each cell.

+ +
+
+
+
+
+
+
+
+

Let's write a simple style function that will color negative numbers red and positive numbers black.

+ +
+
+
+
+
+
In [4]:
+
+
+
def color_negative_red(val):
+    """
+    Takes a scalar and returns a string with
+    the css property `'color: red'` for negative
+    strings, black otherwise.
+    """
+    color = 'red' if val < 0 else 'black'
+    return 'color: %s' % color
+
+ +
+
+
+ +
+
+
+
+
+
+

In this case, the cell's style depends only on it's own value. +That means we should use the Styler.applymap method which works elementwise.

+ +
+
+
+
+
+
In [5]:
+
+
+
s = df.style.applymap(color_negative_red)
+s
+
+ +
+
+
+ +
+
+ + +
Out[5]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

Notice the similarity with the standard df.applymap, which operates on DataFrames elementwise. We want you to be able to resuse your existing knowledge of how to interact with DataFrames.

+

Notice also that our function returned a string containing the CSS attribute and value, separated by a colon just like in a <style> tag. This will be a common theme.

+

Finally, the input shapes matched. Styler.applymap calls the function on each scalar input, and the function returns a scalar output.

+ +
+
+
+
+
+
+
+
+

Now suppose you wanted to highlight the maximum value in each column. +We can't use .applymap anymore since that operated elementwise. +Instead, we'll turn to .apply which operates columnwise (or rowwise using the axis keyword). Later on we'll see that something like highlight_max is already defined on Styler so you wouldn't need to write this yourself.

+ +
+
+
+
+
+
In [6]:
+
+
+
def highlight_max(s):
+    '''
+    highlight the maximum in a Series yellow.
+    '''
+    is_max = s == s.max()
+    return ['background-color: yellow' if v else '' for v in is_max]
+
+ +
+
+
+ +
+
+
+
In [7]:
+
+
+
df.style.apply(highlight_max)
+
+ +
+
+
+ +
+
+ + +
Out[7]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

In this case the input is a Series, one column at a time. +Notice that the output shape of highlight_max matches the input shape, an array with len(s) items.

+ +
+
+
+
+
+
+
+
+

We encourage you to use method chains to build up a style piecewise, before finally rending at the end of the chain.

+ +
+
+
+
+
+
In [8]:
+
+
+
df.style.\
+    applymap(color_negative_red).\
+    apply(highlight_max)
+
+ +
+
+
+ +
+
+ + +
Out[8]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

Above we used Styler.apply to pass in each column one at a time.

+

*Debugging Tip*: If you're having trouble writing your style function, try just passing it into DataFrame.apply. Internally, Styler.apply uses DataFrame.apply so the result should be the same.

What if you wanted to highlight just the maximum value in the entire table? +Use .apply(function, axis=None) to indicate that your function wants the entire table, not one column or row at a time. Let's try that next.

+

We'll rewrite our highlight-max to handle either Series (from .apply(axis=0 or 1)) or DataFrames (from .apply(axis=None)). We'll also allow the color to be adjustable, to demonstrate that .apply, and .applymap pass along keyword arguments.

+ +
+
+
+
+
+
In [9]:
+
+
+
def highlight_max(data, color='yellow'):
+    '''
+    highlight the maximum in a Series or DataFrame
+    '''
+    attr = 'background-color: {}'.format(color)
+    if data.ndim == 1:  # Series from .apply(axis=0) or axis=1
+        is_max = data == data.max()
+        return [attr if v else '' for v in is_max]
+    else:  # from .apply(axis=None)
+        is_max = data == data.max().max()
+        return pd.DataFrame(np.where(is_max, attr, ''),
+                            index=data.index, columns=data.columns)
+
+ +
+
+
+ +
+
+
+
+
+
+

When using Styler.apply(func, axis=None), the function must return a DataFrame with the same index and column labels.

+ +
+
+
+
+
+
In [10]:
+
+
+
df.style.apply(highlight_max, color='darkorange', axis=None)
+
+ +
+
+
+ +
+
+ + +
Out[10]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

Building Styles Summary

Style functions should return strings with one or more CSS attribute: value delimited by semicolons. Use

+
    +
  • Styler.applymap(func) for elementwise styles
  • +
  • Styler.apply(func, axis=0) for columnwise styles
  • +
  • Styler.apply(func, axis=1) for rowwise styles
  • +
  • Styler.apply(func, axis=None) for tablewise styles
  • +
+

And crucially the input and output shapes of func must match. If x is the input then func(x).shape == x.shape.

+ +
+
+
+
+
+
+
+
+

Finer Control: Slicing

+
+
+
+
+
+
+
+
+

Both Styler.apply, and Styler.applymap accept a subset keyword. +This allows you to apply styles to specific rows or columns, without having to code that logic into your style function.

+

The value passed to subset behaves simlar to slicing a DataFrame.

+
    +
  • A scalar is treated as a column label
  • +
  • A list (or series or numpy array)
  • +
  • A tuple is treated as (row_indexer, column_indexer)
  • +
+

Consider using pd.IndexSlice to construct the tuple for the last one.

+ +
+
+
+
+
+
In [11]:
+
+
+
df.style.apply(highlight_max, subset=['B', 'C', 'D'])
+
+ +
+
+
+ +
+
+ + +
Out[11]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

For row and column slicing, any valid indexer to .loc will work.

+ +
+
+
+
+
+
In [12]:
+
+
+
df.style.applymap(color_negative_red,
+                  subset=pd.IndexSlice[2:5, ['B', 'D']])
+
+ +
+
+
+ +
+
+ + +
Out[12]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

Only label-based slicing is supported right now, not positional.

+

If your style function uses a subset or axis keyword argument, consider wrapping your function in a functools.partial, partialing out that keyword.

+
my_func2 = functools.partial(my_func, subset=42)
+
+ +
+
+
+
+
+
+
+
+

Finer Control: Display Values

We distinguish the display value from the actual value in Styler. +To control the display value, the text is printed in each cell, use Styler.format. Cells can be formatted according to a format spec string or a callable that takes a single value and returns a string.

+ +
+
+
+
+
+
In [13]:
+
+
+
df.style.format("{:.2%}")
+
+ +
+
+
+ +
+
+ + +
Out[13]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 100.00% + + + + + 132.92% + + + + + nan% + + + + + -31.63% + + + + + -99.08% + + +
+ 1 + + + + + 200.00% + + + + + -107.08% + + + + + -143.87% + + + + + 56.44% + + + + + 29.57% + + +
+ 2 + + + + + 300.00% + + + + + -162.64% + + + + + 21.96% + + + + + 67.88% + + + + + 188.93% + + +
+ 3 + + + + + 400.00% + + + + + 96.15% + + + + + 10.40% + + + + + -48.12% + + + + + 85.02% + + +
+ 4 + + + + + 500.00% + + + + + 145.34% + + + + + 105.77% + + + + + 16.56% + + + + + 51.50% + + +
+ 5 + + + + + 600.00% + + + + + -133.69% + + + + + 56.29% + + + + + 139.29% + + + + + -6.33% + + +
+ 6 + + + + + 700.00% + + + + + 12.17% + + + + + 120.76% + + + + + -0.20% + + + + + 162.78% + + +
+ 7 + + + + + 800.00% + + + + + 35.45% + + + + + 103.75% + + + + + -38.57% + + + + + 51.98% + + +
+ 8 + + + + + 900.00% + + + + + 168.66% + + + + + -132.60% + + + + + 142.90% + + + + + -208.94% + + +
+ 9 + + + + + 1000.00% + + + + + -12.98% + + + + + 63.15% + + + + + -58.65% + + + + + 29.07% + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

Use a dictionary to format specific columns.

+ +
+
+
+
+
+
In [14]:
+
+
+
df.style.format({'B': "{:0<4.0f}", 'D': '{:+.2f}'})
+
+ +
+
+
+ +
+
+ + +
Out[14]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1000 + + + + + nan + + + + + -0.32 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -100 + + + + + -1.43871 + + + + + +0.56 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -200 + + + + + 0.219565 + + + + + +0.68 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 1000 + + + + + 0.104011 + + + + + -0.48 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1000 + + + + + 1.05774 + + + + + +0.17 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -100 + + + + + 0.562861 + + + + + +1.39 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0000 + + + + + 1.2076 + + + + + -0.00 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0000 + + + + + 1.03753 + + + + + -0.39 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 2000 + + + + + -1.32596 + + + + + +1.43 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -000 + + + + + 0.631523 + + + + + -0.59 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

Or pass in a callable (or dictionary of callables) for more flexible handling.

+ +
+
+
+
+
+
In [15]:
+
+
+
df.style.format({"B": lambda x: "±{:.2f}".format(abs(x))})
+
+ +
+
+
+ +
+
+ + +
Out[15]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + ±1.33 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + ±1.07 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + ±1.63 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + ±0.96 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + ±1.45 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + ±1.34 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + ±0.12 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + ±0.35 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + ±1.69 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + ±0.13 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

Builtin Styles

+
+
+
+
+
+
+
+
+

Finally, we expect certain styling functions to be common enough that we've included a few "built-in" to the Styler, so you don't have to write them yourself.

+ +
+
+
+
+
+
In [16]:
+
+
+
df.style.highlight_null(null_color='red')
+
+ +
+
+
+ +
+
+ + +
Out[16]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

You can create "heatmaps" with the background_gradient method. These require matplotlib, and we'll use Seaborn to get a nice colormap.

+ +
+
+
+
+
+
In [17]:
+
+
+
import seaborn as sns
+
+cm = sns.light_palette("green", as_cmap=True)
+
+s = df.style.background_gradient(cmap=cm)
+s
+
+ +
+
+
+ +
+
+ + +
+
+
+---------------------------------------------------------------------------
+ImportError                               Traceback (most recent call last)
+<ipython-input-17-21d716029213> in <module>()
+----> 1 import seaborn as sns
+      2 
+      3 cm = sns.light_palette("green", as_cmap=True)
+      4 
+      5 s = df.style.background_gradient(cmap=cm)
+
+ImportError: No module named 'seaborn'
+
+
+ +
+
+ +
+
+
+
+
+
+

Styler.background_gradient takes the keyword arguments low and high. Roughly speaking these extend the range of your data by low and high percent so that when we convert the colors, the colormap's entire range isn't used. This is useful so that you can actually read the text still.

+ +
+
+
+
+
+
In [18]:
+
+
+
# Uses the full color range
+df.loc[:4].style.background_gradient(cmap='viridis')
+
+ +
+
+
+ +
+
+ + +
+
+
/home/mcocdawc/bin/anaconda3/lib/python3.5/site-packages/matplotlib/colors.py:581: RuntimeWarning: invalid value encountered in less
+  cbook._putmask(xa, xa < 0.0, -1)
+
+
+
+ +
Out[18]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ +
+ +
+ +
+
+ +
+
+
+
In [19]:
+
+
+
# Compreess the color range
+(df.loc[:4]
+    .style
+    .background_gradient(cmap='viridis', low=.5, high=0)
+    .highlight_null('red'))
+
+ +
+
+
+ +
+
+ + +
+
+
/home/mcocdawc/bin/anaconda3/lib/python3.5/site-packages/matplotlib/colors.py:581: RuntimeWarning: invalid value encountered in less
+  cbook._putmask(xa, xa < 0.0, -1)
+
+
+
+ +
Out[19]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

You can include "bar charts" in your DataFrame.

+ +
+
+
+
+
+
In [20]:
+
+
+
df.style.bar(subset=['A', 'B'], color='#d65f5f')
+
+ +
+
+
+ +
+
+ + +
Out[20]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

There's also .highlight_min and .highlight_max.

+ +
+
+
+
+
+
In [21]:
+
+
+
df.style.highlight_max(axis=0)
+
+ +
+
+
+ +
+
+ + +
Out[21]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
In [22]:
+
+
+
df.style.highlight_min(axis=0)
+
+ +
+
+
+ +
+
+ + +
Out[22]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

Use Styler.set_properties when the style doesn't actually depend on the values.

+ +
+
+
+
+
+
In [23]:
+
+
+
df.style.set_properties(**{'background-color': 'black',
+                           'color': 'lawngreen',
+                           'border-color': 'white'})
+
+ +
+
+
+ +
+
+ + +
Out[23]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

Sharing Styles

+
+
+
+
+
+
+
+
+

Say you have a lovely style built up for a DataFrame, and now you want to apply the same style to a second DataFrame. Export the style with df1.style.export, and import it on the second DataFrame with df1.style.set

+ +
+
+
+
+
+
In [24]:
+
+
+
df2 = -df
+style1 = df.style.applymap(color_negative_red)
+style1
+
+ +
+
+
+ +
+
+ + +
Out[24]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
In [25]:
+
+
+
style2 = df2.style
+style2.use(style1.export())
+style2
+
+ +
+
+
+ +
+
+ + +
Out[25]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + -1 + + + + + -1.32921 + + + + + nan + + + + + 0.31628 + + + + + 0.99081 + + +
+ 1 + + + + + -2 + + + + + 1.07082 + + + + + 1.43871 + + + + + -0.564417 + + + + + -0.295722 + + +
+ 2 + + + + + -3 + + + + + 1.6264 + + + + + -0.219565 + + + + + -0.678805 + + + + + -1.88927 + + +
+ 3 + + + + + -4 + + + + + -0.961538 + + + + + -0.104011 + + + + + 0.481165 + + + + + -0.850229 + + +
+ 4 + + + + + -5 + + + + + -1.45342 + + + + + -1.05774 + + + + + -0.165562 + + + + + -0.515018 + + +
+ 5 + + + + + -6 + + + + + 1.33694 + + + + + -0.562861 + + + + + -1.39285 + + + + + 0.063328 + + +
+ 6 + + + + + -7 + + + + + -0.121668 + + + + + -1.2076 + + + + + 0.00204021 + + + + + -1.6278 + + +
+ 7 + + + + + -8 + + + + + -0.354493 + + + + + -1.03753 + + + + + 0.385684 + + + + + -0.519818 + + +
+ 8 + + + + + -9 + + + + + -1.68658 + + + + + 1.32596 + + + + + -1.42898 + + + + + 2.08935 + + +
+ 9 + + + + + -10 + + + + + 0.12982 + + + + + -0.631523 + + + + + 0.586538 + + + + + -0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

Notice that you're able share the styles even though they're data aware. The styles are re-evaluated on the new DataFrame they've been used upon.

+ +
+
+
+
+
+
+
+
+

Other options

You've seen a few methods for data-driven styling. +Styler also provides a few other options for styles that don't depend on the data.

+
    +
  • precision
  • +
  • captions
  • +
  • table-wide styles
  • +
+

Each of these can be specified in two ways:

+
    +
  • A keyword argument to pandas.core.Styler
  • +
  • A call to one of the .set_ methods, e.g. .set_caption
  • +
+

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.

+ +
+
+
+
+
+
+
+
+

Precision

+
+
+
+
+
+
+
+
+

You can control the precision of floats using pandas' regular display.precision option.

+ +
+
+
+
+
+
In [26]:
+
+
+
with pd.option_context('display.precision', 2):
+    html = (df.style
+              .applymap(color_negative_red)
+              .apply(highlight_max))
+html
+
+ +
+
+
+ +
+
+ + +
Out[26]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.3 + + + + + nan + + + + + -0.32 + + + + + -0.99 + + +
+ 1 + + + + + 2 + + + + + -1.1 + + + + + -1.4 + + + + + 0.56 + + + + + 0.3 + + +
+ 2 + + + + + 3 + + + + + -1.6 + + + + + 0.22 + + + + + 0.68 + + + + + 1.9 + + +
+ 3 + + + + + 4 + + + + + 0.96 + + + + + 0.1 + + + + + -0.48 + + + + + 0.85 + + +
+ 4 + + + + + 5 + + + + + 1.5 + + + + + 1.1 + + + + + 0.17 + + + + + 0.52 + + +
+ 5 + + + + + 6 + + + + + -1.3 + + + + + 0.56 + + + + + 1.4 + + + + + -0.063 + + +
+ 6 + + + + + 7 + + + + + 0.12 + + + + + 1.2 + + + + + -0.002 + + + + + 1.6 + + +
+ 7 + + + + + 8 + + + + + 0.35 + + + + + 1 + + + + + -0.39 + + + + + 0.52 + + +
+ 8 + + + + + 9 + + + + + 1.7 + + + + + -1.3 + + + + + 1.4 + + + + + -2.1 + + +
+ 9 + + + + + 10 + + + + + -0.13 + + + + + 0.63 + + + + + -0.59 + + + + + 0.29 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

Or through a set_precision method.

+ +
+
+
+
+
+
In [27]:
+
+
+
df.style\
+  .applymap(color_negative_red)\
+  .apply(highlight_max)\
+  .set_precision(2)
+
+ +
+
+
+ +
+
+ + +
Out[27]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.3 + + + + + nan + + + + + -0.32 + + + + + -0.99 + + +
+ 1 + + + + + 2 + + + + + -1.1 + + + + + -1.4 + + + + + 0.56 + + + + + 0.3 + + +
+ 2 + + + + + 3 + + + + + -1.6 + + + + + 0.22 + + + + + 0.68 + + + + + 1.9 + + +
+ 3 + + + + + 4 + + + + + 0.96 + + + + + 0.1 + + + + + -0.48 + + + + + 0.85 + + +
+ 4 + + + + + 5 + + + + + 1.5 + + + + + 1.1 + + + + + 0.17 + + + + + 0.52 + + +
+ 5 + + + + + 6 + + + + + -1.3 + + + + + 0.56 + + + + + 1.4 + + + + + -0.063 + + +
+ 6 + + + + + 7 + + + + + 0.12 + + + + + 1.2 + + + + + -0.002 + + + + + 1.6 + + +
+ 7 + + + + + 8 + + + + + 0.35 + + + + + 1 + + + + + -0.39 + + + + + 0.52 + + +
+ 8 + + + + + 9 + + + + + 1.7 + + + + + -1.3 + + + + + 1.4 + + + + + -2.1 + + +
+ 9 + + + + + 10 + + + + + -0.13 + + + + + 0.63 + + + + + -0.59 + + + + + 0.29 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

Setting the precision only affects the printed number; the full-precision values are always passed to your style functions. You can always use df.round(2).style if you'd prefer to round from the start.

+ +
+
+
+
+
+
+
+
+

Captions

+
+
+
+
+
+
+
+
+

Regular table captions can be added in a few ways.

+ +
+
+
+
+
+
In [28]:
+
+
+
df.style.set_caption('Colormaps, with a caption.')\
+    .background_gradient(cmap=cm)
+
+ +
+
+
+ +
+
+ + +
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+<ipython-input-28-1c18b6874409> in <module>()
+----> 1 df.style.set_caption('Colormaps, with a caption.')    .background_gradient(cmap=cm)
+
+NameError: name 'cm' is not defined
+
+
+ +
+
+ +
+
+
+
+
+
+

Table Styles

+
+
+
+
+
+
+
+
+

The next option you have are "table styles". +These are styles that apply to the table as a whole, but don't look at the data. +Certain sytlings, including pseudo-selectors like :hover can only be used this way.

+ +
+
+
+
+
+
In [29]:
+
+
+
from IPython.display import HTML
+
+def hover(hover_color="#ffff99"):
+    return dict(selector="tr:hover",
+                props=[("background-color", "%s" % hover_color)])
+
+styles = [
+    hover(),
+    dict(selector="th", props=[("font-size", "150%"),
+                               ("text-align", "center")]),
+    dict(selector="caption", props=[("caption-side", "bottom")])
+]
+html = (df.style.set_table_styles(styles)
+          .set_caption("Hover to highlight."))
+html
+
+ +
+
+
+ +
+
+ + +
Out[29]:
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Hover to highlight.
+ + + + + + A + + + + + B + + + + + C + + + + + D + + + + + E + + +
+ 0 + + + + + 1 + + + + + 1.32921 + + + + + nan + + + + + -0.31628 + + + + + -0.99081 + + +
+ 1 + + + + + 2 + + + + + -1.07082 + + + + + -1.43871 + + + + + 0.564417 + + + + + 0.295722 + + +
+ 2 + + + + + 3 + + + + + -1.6264 + + + + + 0.219565 + + + + + 0.678805 + + + + + 1.88927 + + +
+ 3 + + + + + 4 + + + + + 0.961538 + + + + + 0.104011 + + + + + -0.481165 + + + + + 0.850229 + + +
+ 4 + + + + + 5 + + + + + 1.45342 + + + + + 1.05774 + + + + + 0.165562 + + + + + 0.515018 + + +
+ 5 + + + + + 6 + + + + + -1.33694 + + + + + 0.562861 + + + + + 1.39285 + + + + + -0.063328 + + +
+ 6 + + + + + 7 + + + + + 0.121668 + + + + + 1.2076 + + + + + -0.00204021 + + + + + 1.6278 + + +
+ 7 + + + + + 8 + + + + + 0.354493 + + + + + 1.03753 + + + + + -0.385684 + + + + + 0.519818 + + +
+ 8 + + + + + 9 + + + + + 1.68658 + + + + + -1.32596 + + + + + 1.42898 + + + + + -2.08935 + + +
+ 9 + + + + + 10 + + + + + -0.12982 + + + + + 0.631523 + + + + + -0.586538 + + + + + 0.29072 + + +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+

table_styles should be a list of dictionaries. +Each dictionary should have the selector and props keys. +The value for selector should be a valid CSS selector. +Recall that all the styles are already attached to an id, unique to +each Styler. This selector is in addition to that id. +The value for props should be a list of tuples of ('attribute', 'value').

+

table_styles are extremely flexible, but not as fun to type out by hand. +We hope to collect some useful ones either in pandas, or preferable in a new package that builds on top the tools here.

+ +
+
+
+
+
+
+
+
+

CSS Classes

Certain CSS classes are attached to cells.

+
    +
  • Index and Column names include index_name and level<k> where k is its level in a MultiIndex
  • +
  • Index label cells include
      +
    • row_heading
    • +
    • row<n> where n is the numeric position of the row
    • +
    • level<k> where k is the level in a MultiIndex
    • +
    +
  • +
  • Column label cells include
      +
    • col_heading
    • +
    • col<n> where n is the numeric position of the column
    • +
    • level<k> where k is the level in a MultiIndex
    • +
    +
  • +
  • Blank cells include blank
  • +
  • Data cells include data
  • +
+ +
+
+
+
+
+
+
+
+

Limitations

    +
  • DataFrame only (use Series.to_frame().style)
  • +
  • The index and columns must be unique
  • +
  • No large repr, and performance isn't great; this is intended for summary DataFrames
  • +
  • You can only style the values, not the index or columns
  • +
  • You can only apply styles, you can't insert new HTML entities
  • +
+

Some of these will be addressed in the future.

+ +
+
+
+
+
+
+
+
+

Terms

    +
  • Style function: a function that's passed into Styler.apply or Styler.applymap and returns values like 'css attribute: value'
  • +
  • Builtin style functions: style functions that are methods on Styler
  • +
  • table style: a dictionary with the two keys selector and props. selector is the CSS selector that props will apply to. props is a list of (attribute, value) tuples. A list of table styles passed into Styler.
  • +
+ +
+
+
+
+
+
+
+
+

Fun stuff

Here are a few interesting examples.

+

Styler interacts pretty well with widgets. If you're viewing this online instead of running the notebook yourself, you're missing out on interactively adjusting the color palette.

+ +
+
+
+
+
+
In [30]:
+
+
+
from IPython.html import widgets
+@widgets.interact
+def f(h_neg=(0, 359, 1), h_pos=(0, 359), s=(0., 99.9), l=(0., 99.9)):
+    return df.style.background_gradient(
+        cmap=sns.palettes.diverging_palette(h_neg=h_neg, h_pos=h_pos, s=s, l=l,
+                                            as_cmap=True)
+    )
+
+ +
+
+
+ +
+
+ + +
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+<ipython-input-30-62e68348ecde> in f(h_neg, h_pos, s, l)
+      3 def f(h_neg=(0, 359, 1), h_pos=(0, 359), s=(0., 99.9), l=(0., 99.9)):
+      4     return df.style.background_gradient(
+----> 5         cmap=sns.palettes.diverging_palette(h_neg=h_neg, h_pos=h_pos, s=s, l=l,
+      6                                             as_cmap=True)
+      7     )
+
+NameError: name 'sns' is not defined
+
+
+ +
+
+ +
+
+
+
In [31]:
+
+
+
def magnify():
+    return [dict(selector="th",
+                 props=[("font-size", "4pt")]),
+            dict(selector="td",
+                 props=[('padding', "0em 0em")]),
+            dict(selector="th:hover",
+                 props=[("font-size", "12pt")]),
+            dict(selector="tr:hover td:hover",
+                 props=[('max-width', '200px'),
+                        ('font-size', '12pt')])
+]
+
+ +
+
+
+ +
+
+
+
In [32]:
+
+
+
np.random.seed(25)
+cmap = cmap=sns.diverging_palette(5, 250, as_cmap=True)
+df = pd.DataFrame(np.random.randn(20, 25)).cumsum()
+
+df.style.background_gradient(cmap, axis=1)\
+    .set_properties(**{'max-width': '80px', 'font-size': '1pt'})\
+    .set_caption("Hover to magify")\
+    .set_precision(2)\
+    .set_table_styles(magnify())
+
+ +
+
+
+ +
+
+ + +
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+<ipython-input-32-298160b5e3a5> in <module>()
+      1 np.random.seed(25)
+----> 2 cmap = cmap=sns.diverging_palette(5, 250, as_cmap=True)
+      3 df = pd.DataFrame(np.random.randn(20, 25)).cumsum()
+      4 
+      5 df.style.background_gradient(cmap, axis=1)    .set_properties(**{'max-width': '80px', 'font-size': '1pt'})    .set_caption("Hover to magify")    .set_precision(2)    .set_table_styles(magnify())
+
+NameError: name 'sns' is not defined
+
+
+ +
+
+ +
+
+
+
+
+
+

Extensibility

The core of pandas is, and will remain, its "high-performance, easy-to-use data structures". +With that in mind, we hope that DataFrame.style accomplishes two goals

+
    +
  • Provide an API that is pleasing to use interactively and is "good enough" for many tasks
  • +
  • Provide the foundations for dedicated libraries to build on
  • +
+

If you build a great library on top of this, let us know and we'll link to it.

+

Subclassing

This section contains a bit of information about the implementation of Styler. +Since the feature is so new all of this is subject to change, even more so than the end-use API.

+

As users apply styles (via .apply, .applymap or one of the builtins), we don't actually calculate anything. +Instead, we append functions and arguments to a list self._todo. +When asked (typically in .render we'll walk through the list and execute each function (this is in self._compute(). +These functions update an internal defaultdict(list), self.ctx which maps DataFrame row / column positions to CSS attribute, value pairs.

+

We take the extra step through self._todo so that we can export styles and set them on other Stylers.

+

Rendering uses Jinja templates. +The .translate method takes self.ctx and builds another dictionary ready to be passed into Styler.template.render, the Jinja template.

+

Alternate templates

We've used Jinja templates to build up the HTML. +The template is stored as a class variable Styler.template.. Subclasses can override that.

+
class CustomStyle(Styler):
+    template = Template("""...""")
+
+ +
+
+
\ No newline at end of file diff --git a/pandas/core/frame.py b/pandas/core/frame.py index b776161196f52..a857e18d96272 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -1529,7 +1529,7 @@ def to_feather(self, fname): from pandas.io.feather_format import to_feather to_feather(self, fname) - @Substitution('Write out column names. If a list of string is given, \ + @Substitution(header='Write out column names. If a list of string is given, \ it is assumed to be aliases for the column names') @Appender(fmt.docstring_to_string, indents=1) def to_string(self, buf=None, columns=None, col_space=None, header=True, @@ -1558,7 +1558,7 @@ def to_string(self, buf=None, columns=None, col_space=None, header=True, result = formatter.buf.getvalue() return result - @Substitution('whether to print column labels, default True') + @Substitution(header='whether to print column labels, default True') @Appender(fmt.docstring_to_string, indents=1) def to_html(self, buf=None, columns=None, col_space=None, header=True, index=True, na_rep='NaN', formatters=None, float_format=None, @@ -1612,7 +1612,7 @@ def to_html(self, buf=None, columns=None, col_space=None, header=True, if buf is None: return formatter.buf.getvalue() - @Substitution('Write out column names. If a list of string is given, \ + @Substitution(header='Write out column names. If a list of string is given, \ it is assumed to be aliases for the column names.') @Appender(fmt.common_docstring + fmt.return_docstring, indents=1) def to_latex(self, buf=None, columns=None, col_space=None, header=True, diff --git a/pandas/formats/format.py b/pandas/formats/format.py index d162445b1b1b6..d90edbe7b7e7c 100644 --- a/pandas/formats/format.py +++ b/pandas/formats/format.py @@ -20,9 +20,9 @@ is_float, is_numeric_dtype, is_datetime64_dtype, - is_timedelta64_dtype) + is_timedelta64_dtype, + is_list_like) from pandas.types.generic import ABCSparseArray - from pandas.core.base import PandasObject from pandas.core.index import Index, MultiIndex, _ensure_index from pandas import compat @@ -53,7 +53,7 @@ col_space : int, optional the minimum width of each column header : bool, optional - %s + %(header)s index : bool, optional whether to print index (row) labels, default True na_rep : string, optional @@ -487,10 +487,8 @@ def _to_str_columns(self): # may include levels names also str_index = self._get_formatted_index(frame) - str_columns = self._get_formatted_column_labels(frame) - has_aliases = isinstance(self.header, (tuple, list, np.ndarray, Index)) - if not (has_aliases or self.header): + if not is_list_like(self.header) and not self.header: stringified = [] for i, c in enumerate(frame): fmt_values = self._format_col(i) @@ -498,25 +496,15 @@ def _to_str_columns(self): minimum=(self.col_space or 0), adj=self.adj) stringified.append(fmt_values) - elif has_aliases: - if len(self.header) != len(self.columns): - raise ValueError(('Writing %d cols but got %d aliases' - % (len(self.columns), len(self.header)))) - stringified = [] - for i, c in enumerate(frame): - cheader = [self.header[i]] - header_colwidth = max(self.col_space or 0, - *(self.adj.len(x) for x in cheader)) - fmt_values = self._format_col(i) - fmt_values = _make_fixed_width(fmt_values, self.justify, - minimum=header_colwidth, - adj=self.adj) - - max_len = max(np.max([self.adj.len(x) for x in fmt_values]), - header_colwidth) - cheader = self.adj.justify(cheader, max_len, mode=self.justify) - stringified.append(cheader + fmt_values) else: + if is_list_like(self.header): + if len(self.header) != len(self.columns): + raise ValueError(('Writing %d cols but got %d aliases' + % (len(self.columns), len(self.header)))) + str_columns = [[label] for label in self.header] + else: + str_columns = self._get_formatted_column_labels(frame) + stringified = [] for i, c in enumerate(frame): cheader = str_columns[i] From 2be1fb187907c88d71d368308a003a63cf7168fb Mon Sep 17 00:00:00 2001 From: Jeff Reback Date: Mon, 6 Mar 2017 08:24:01 -0500 Subject: [PATCH 11/11] fix rebase --- doc/source/groupby.rst | 10 - doc/source/html-styling.html | 14392 -------------------- doc/source/whatsnew/v0.20.0.txt | 6 - pandas/core/panel.py | 6 +- pandas/core/reshape.py | 6 +- pandas/core/strings.py | 2 +- pandas/indexes/frozen.py | 24 +- pandas/tests/groupby/test_value_counts.py | 2 +- pandas/tests/indexes/test_frozen.py | 33 +- pandas/tools/concat.py | 2 +- test_fast.sh | 2 +- 11 files changed, 22 insertions(+), 14463 deletions(-) delete mode 100644 doc/source/html-styling.html diff --git a/doc/source/groupby.rst b/doc/source/groupby.rst index 2d406de7c0c9b..8484ccd69a983 100644 --- a/doc/source/groupby.rst +++ b/doc/source/groupby.rst @@ -126,16 +126,6 @@ We could naturally group by either the ``A`` or ``B`` columns or both: grouped = df.groupby('A') grouped = df.groupby(['A', 'B']) -.. versionadded:: 0.20 - -If we also have a MultiIndex on columns ``A`` and ``B``, we can group by all -but the specified columns. - -.. ipython:: python - - df2 = df.set_index(['A', 'B']) - grouped = df2.groupby(level=df2.index.names.difference(['B']) - These will split the DataFrame on its index (rows). We could also split by the columns: diff --git a/doc/source/html-styling.html b/doc/source/html-styling.html deleted file mode 100644 index eb0c3a12b65d8..0000000000000 --- a/doc/source/html-styling.html +++ /dev/null @@ -1,14392 +0,0 @@ - -
-
-
-
-
-

New in version 0.17.1

-

Provisional: This is a new feature and still under development. We'll be adding features and possibly making breaking changes in future releases. We'd love to hear your feedback.

-

This document is written as a Jupyter Notebook, and can be viewed or downloaded here.

-

You can apply conditional formatting, the visual styling of a DataFrame -depending on the data within, by using the DataFrame.style property. -This is a property that returns a pandas.Styler object, which has -useful methods for formatting and displaying DataFrames.

-

The styling is accomplished using CSS. -You write "style functions" that take scalars, DataFrames or Series, and return like-indexed DataFrames or Series with CSS "attribute: value" pairs for the values. -These functions can be incrementally passed to the Styler which collects the styles before rendering.

-

Contents

- -
-
-
-
-
-
-
-
-

Building Styles

Pass your style functions into one of the following methods:

-
    -
  • Styler.applymap: elementwise
  • -
  • Styler.apply: column-/row-/table-wise
  • -
-

Both of those methods take a function (and some other keyword arguments) and applies your function to the DataFrame in a certain way. -Styler.applymap works through the DataFrame elementwise. -Styler.apply passes each column or row into your DataFrame one-at-a-time or the entire table at once, depending on the axis keyword argument. -For columnwise use axis=0, rowwise use axis=1, and for the entire table at once use axis=None.

-

For Styler.applymap your function should take a scalar and return a single string with the CSS attribute-value pair.

-

For Styler.apply your function should take a Series or DataFrame (depending on the axis parameter), and return a Series or DataFrame with an identical shape where each value is a string with a CSS attribute-value pair.

-

Let's see some examples.

- -
-
-
-
-
-
In [1]:
-
-
-
import pandas as pd
-import numpy as np
-
-np.random.seed(24)
-df = pd.DataFrame({'A': np.linspace(1, 10, 10)})
-df = pd.concat([df, pd.DataFrame(np.random.randn(10, 4), columns=list('BCDE'))],
-               axis=1)
-df.iloc[0, 2] = np.nan
-
- -
-
-
- -
-
-
-
-
-
-

Here's a boring example of rendering a DataFrame, without any (visible) styles:

- -
-
-
-
-
-
In [2]:
-
-
-
df.style
-
- -
-
-
- -
-
- - -
Out[2]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Note: The DataFrame.style attribute is a propetry that returns a Styler object. Styler has a _repr_html_ method defined on it so they are rendered automatically. If you want the actual HTML back for further processing or for writing to file call the .render() method which returns a string.

-

The above output looks very similar to the standard DataFrame HTML representation. But we've done some work behind the scenes to attach CSS classes to each cell. We can view these by calling the .render method.

- -
-
-
-
-
-
In [3]:
-
-
-
df.style.highlight_null().render().split('\n')[:10]
-
- -
-
-
- -
-
- - -
Out[3]:
- - -
-
['',
- '        <style  type="text/css" >',
- '        ',
- '        ',
- '            #T_e35153b9_ff86_11e6_b5d1_c86000c2e7a8row0_col2 {',
- '            ',
- '                background-color:  red;',
- '            ',
- '            }',
- '        ']
-
- -
- -
-
- -
-
-
-
-
-
-

The row0_col2 is the identifier for that particular cell. We've also prepended each row/column identifier with a UUID unique to each DataFrame so that the style from one doesn't collide with the styling from another within the same notebook or page (you can set the uuid if you'd like to tie together the styling of two DataFrames).

-

When writing style functions, you take care of producing the CSS attribute / value pairs you want. Pandas matches those up with the CSS classes that identify each cell.

- -
-
-
-
-
-
-
-
-

Let's write a simple style function that will color negative numbers red and positive numbers black.

- -
-
-
-
-
-
In [4]:
-
-
-
def color_negative_red(val):
-    """
-    Takes a scalar and returns a string with
-    the css property `'color: red'` for negative
-    strings, black otherwise.
-    """
-    color = 'red' if val < 0 else 'black'
-    return 'color: %s' % color
-
- -
-
-
- -
-
-
-
-
-
-

In this case, the cell's style depends only on it's own value. -That means we should use the Styler.applymap method which works elementwise.

- -
-
-
-
-
-
In [5]:
-
-
-
s = df.style.applymap(color_negative_red)
-s
-
- -
-
-
- -
-
- - -
Out[5]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Notice the similarity with the standard df.applymap, which operates on DataFrames elementwise. We want you to be able to resuse your existing knowledge of how to interact with DataFrames.

-

Notice also that our function returned a string containing the CSS attribute and value, separated by a colon just like in a <style> tag. This will be a common theme.

-

Finally, the input shapes matched. Styler.applymap calls the function on each scalar input, and the function returns a scalar output.

- -
-
-
-
-
-
-
-
-

Now suppose you wanted to highlight the maximum value in each column. -We can't use .applymap anymore since that operated elementwise. -Instead, we'll turn to .apply which operates columnwise (or rowwise using the axis keyword). Later on we'll see that something like highlight_max is already defined on Styler so you wouldn't need to write this yourself.

- -
-
-
-
-
-
In [6]:
-
-
-
def highlight_max(s):
-    '''
-    highlight the maximum in a Series yellow.
-    '''
-    is_max = s == s.max()
-    return ['background-color: yellow' if v else '' for v in is_max]
-
- -
-
-
- -
-
-
-
In [7]:
-
-
-
df.style.apply(highlight_max)
-
- -
-
-
- -
-
- - -
Out[7]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

In this case the input is a Series, one column at a time. -Notice that the output shape of highlight_max matches the input shape, an array with len(s) items.

- -
-
-
-
-
-
-
-
-

We encourage you to use method chains to build up a style piecewise, before finally rending at the end of the chain.

- -
-
-
-
-
-
In [8]:
-
-
-
df.style.\
-    applymap(color_negative_red).\
-    apply(highlight_max)
-
- -
-
-
- -
-
- - -
Out[8]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Above we used Styler.apply to pass in each column one at a time.

-

*Debugging Tip*: If you're having trouble writing your style function, try just passing it into DataFrame.apply. Internally, Styler.apply uses DataFrame.apply so the result should be the same.

What if you wanted to highlight just the maximum value in the entire table? -Use .apply(function, axis=None) to indicate that your function wants the entire table, not one column or row at a time. Let's try that next.

-

We'll rewrite our highlight-max to handle either Series (from .apply(axis=0 or 1)) or DataFrames (from .apply(axis=None)). We'll also allow the color to be adjustable, to demonstrate that .apply, and .applymap pass along keyword arguments.

- -
-
-
-
-
-
In [9]:
-
-
-
def highlight_max(data, color='yellow'):
-    '''
-    highlight the maximum in a Series or DataFrame
-    '''
-    attr = 'background-color: {}'.format(color)
-    if data.ndim == 1:  # Series from .apply(axis=0) or axis=1
-        is_max = data == data.max()
-        return [attr if v else '' for v in is_max]
-    else:  # from .apply(axis=None)
-        is_max = data == data.max().max()
-        return pd.DataFrame(np.where(is_max, attr, ''),
-                            index=data.index, columns=data.columns)
-
- -
-
-
- -
-
-
-
-
-
-

When using Styler.apply(func, axis=None), the function must return a DataFrame with the same index and column labels.

- -
-
-
-
-
-
In [10]:
-
-
-
df.style.apply(highlight_max, color='darkorange', axis=None)
-
- -
-
-
- -
-
- - -
Out[10]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Building Styles Summary

Style functions should return strings with one or more CSS attribute: value delimited by semicolons. Use

-
    -
  • Styler.applymap(func) for elementwise styles
  • -
  • Styler.apply(func, axis=0) for columnwise styles
  • -
  • Styler.apply(func, axis=1) for rowwise styles
  • -
  • Styler.apply(func, axis=None) for tablewise styles
  • -
-

And crucially the input and output shapes of func must match. If x is the input then func(x).shape == x.shape.

- -
-
-
-
-
-
-
-
-

Finer Control: Slicing

-
-
-
-
-
-
-
-
-

Both Styler.apply, and Styler.applymap accept a subset keyword. -This allows you to apply styles to specific rows or columns, without having to code that logic into your style function.

-

The value passed to subset behaves simlar to slicing a DataFrame.

-
    -
  • A scalar is treated as a column label
  • -
  • A list (or series or numpy array)
  • -
  • A tuple is treated as (row_indexer, column_indexer)
  • -
-

Consider using pd.IndexSlice to construct the tuple for the last one.

- -
-
-
-
-
-
In [11]:
-
-
-
df.style.apply(highlight_max, subset=['B', 'C', 'D'])
-
- -
-
-
- -
-
- - -
Out[11]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

For row and column slicing, any valid indexer to .loc will work.

- -
-
-
-
-
-
In [12]:
-
-
-
df.style.applymap(color_negative_red,
-                  subset=pd.IndexSlice[2:5, ['B', 'D']])
-
- -
-
-
- -
-
- - -
Out[12]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Only label-based slicing is supported right now, not positional.

-

If your style function uses a subset or axis keyword argument, consider wrapping your function in a functools.partial, partialing out that keyword.

-
my_func2 = functools.partial(my_func, subset=42)
-
- -
-
-
-
-
-
-
-
-

Finer Control: Display Values

We distinguish the display value from the actual value in Styler. -To control the display value, the text is printed in each cell, use Styler.format. Cells can be formatted according to a format spec string or a callable that takes a single value and returns a string.

- -
-
-
-
-
-
In [13]:
-
-
-
df.style.format("{:.2%}")
-
- -
-
-
- -
-
- - -
Out[13]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 100.00% - - - - - 132.92% - - - - - nan% - - - - - -31.63% - - - - - -99.08% - - -
- 1 - - - - - 200.00% - - - - - -107.08% - - - - - -143.87% - - - - - 56.44% - - - - - 29.57% - - -
- 2 - - - - - 300.00% - - - - - -162.64% - - - - - 21.96% - - - - - 67.88% - - - - - 188.93% - - -
- 3 - - - - - 400.00% - - - - - 96.15% - - - - - 10.40% - - - - - -48.12% - - - - - 85.02% - - -
- 4 - - - - - 500.00% - - - - - 145.34% - - - - - 105.77% - - - - - 16.56% - - - - - 51.50% - - -
- 5 - - - - - 600.00% - - - - - -133.69% - - - - - 56.29% - - - - - 139.29% - - - - - -6.33% - - -
- 6 - - - - - 700.00% - - - - - 12.17% - - - - - 120.76% - - - - - -0.20% - - - - - 162.78% - - -
- 7 - - - - - 800.00% - - - - - 35.45% - - - - - 103.75% - - - - - -38.57% - - - - - 51.98% - - -
- 8 - - - - - 900.00% - - - - - 168.66% - - - - - -132.60% - - - - - 142.90% - - - - - -208.94% - - -
- 9 - - - - - 1000.00% - - - - - -12.98% - - - - - 63.15% - - - - - -58.65% - - - - - 29.07% - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Use a dictionary to format specific columns.

- -
-
-
-
-
-
In [14]:
-
-
-
df.style.format({'B': "{:0<4.0f}", 'D': '{:+.2f}'})
-
- -
-
-
- -
-
- - -
Out[14]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1000 - - - - - nan - - - - - -0.32 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -100 - - - - - -1.43871 - - - - - +0.56 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -200 - - - - - 0.219565 - - - - - +0.68 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 1000 - - - - - 0.104011 - - - - - -0.48 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1000 - - - - - 1.05774 - - - - - +0.17 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -100 - - - - - 0.562861 - - - - - +1.39 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0000 - - - - - 1.2076 - - - - - -0.00 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0000 - - - - - 1.03753 - - - - - -0.39 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 2000 - - - - - -1.32596 - - - - - +1.43 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -000 - - - - - 0.631523 - - - - - -0.59 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Or pass in a callable (or dictionary of callables) for more flexible handling.

- -
-
-
-
-
-
In [15]:
-
-
-
df.style.format({"B": lambda x: "±{:.2f}".format(abs(x))})
-
- -
-
-
- -
-
- - -
Out[15]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - ±1.33 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - ±1.07 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - ±1.63 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - ±0.96 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - ±1.45 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - ±1.34 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - ±0.12 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - ±0.35 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - ±1.69 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - ±0.13 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Builtin Styles

-
-
-
-
-
-
-
-
-

Finally, we expect certain styling functions to be common enough that we've included a few "built-in" to the Styler, so you don't have to write them yourself.

- -
-
-
-
-
-
In [16]:
-
-
-
df.style.highlight_null(null_color='red')
-
- -
-
-
- -
-
- - -
Out[16]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

You can create "heatmaps" with the background_gradient method. These require matplotlib, and we'll use Seaborn to get a nice colormap.

- -
-
-
-
-
-
In [17]:
-
-
-
import seaborn as sns
-
-cm = sns.light_palette("green", as_cmap=True)
-
-s = df.style.background_gradient(cmap=cm)
-s
-
- -
-
-
- -
-
- - -
-
-
----------------------------------------------------------------------------
-ImportError                               Traceback (most recent call last)
-<ipython-input-17-21d716029213> in <module>()
-----> 1 import seaborn as sns
-      2 
-      3 cm = sns.light_palette("green", as_cmap=True)
-      4 
-      5 s = df.style.background_gradient(cmap=cm)
-
-ImportError: No module named 'seaborn'
-
-
- -
-
- -
-
-
-
-
-
-

Styler.background_gradient takes the keyword arguments low and high. Roughly speaking these extend the range of your data by low and high percent so that when we convert the colors, the colormap's entire range isn't used. This is useful so that you can actually read the text still.

- -
-
-
-
-
-
In [18]:
-
-
-
# Uses the full color range
-df.loc[:4].style.background_gradient(cmap='viridis')
-
- -
-
-
- -
-
- - -
-
-
/home/mcocdawc/bin/anaconda3/lib/python3.5/site-packages/matplotlib/colors.py:581: RuntimeWarning: invalid value encountered in less
-  cbook._putmask(xa, xa < 0.0, -1)
-
-
-
- -
Out[18]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- -
- -
- -
-
- -
-
-
-
In [19]:
-
-
-
# Compreess the color range
-(df.loc[:4]
-    .style
-    .background_gradient(cmap='viridis', low=.5, high=0)
-    .highlight_null('red'))
-
- -
-
-
- -
-
- - -
-
-
/home/mcocdawc/bin/anaconda3/lib/python3.5/site-packages/matplotlib/colors.py:581: RuntimeWarning: invalid value encountered in less
-  cbook._putmask(xa, xa < 0.0, -1)
-
-
-
- -
Out[19]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

You can include "bar charts" in your DataFrame.

- -
-
-
-
-
-
In [20]:
-
-
-
df.style.bar(subset=['A', 'B'], color='#d65f5f')
-
- -
-
-
- -
-
- - -
Out[20]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

There's also .highlight_min and .highlight_max.

- -
-
-
-
-
-
In [21]:
-
-
-
df.style.highlight_max(axis=0)
-
- -
-
-
- -
-
- - -
Out[21]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
In [22]:
-
-
-
df.style.highlight_min(axis=0)
-
- -
-
-
- -
-
- - -
Out[22]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Use Styler.set_properties when the style doesn't actually depend on the values.

- -
-
-
-
-
-
In [23]:
-
-
-
df.style.set_properties(**{'background-color': 'black',
-                           'color': 'lawngreen',
-                           'border-color': 'white'})
-
- -
-
-
- -
-
- - -
Out[23]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Sharing Styles

-
-
-
-
-
-
-
-
-

Say you have a lovely style built up for a DataFrame, and now you want to apply the same style to a second DataFrame. Export the style with df1.style.export, and import it on the second DataFrame with df1.style.set

- -
-
-
-
-
-
In [24]:
-
-
-
df2 = -df
-style1 = df.style.applymap(color_negative_red)
-style1
-
- -
-
-
- -
-
- - -
Out[24]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
In [25]:
-
-
-
style2 = df2.style
-style2.use(style1.export())
-style2
-
- -
-
-
- -
-
- - -
Out[25]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - -1 - - - - - -1.32921 - - - - - nan - - - - - 0.31628 - - - - - 0.99081 - - -
- 1 - - - - - -2 - - - - - 1.07082 - - - - - 1.43871 - - - - - -0.564417 - - - - - -0.295722 - - -
- 2 - - - - - -3 - - - - - 1.6264 - - - - - -0.219565 - - - - - -0.678805 - - - - - -1.88927 - - -
- 3 - - - - - -4 - - - - - -0.961538 - - - - - -0.104011 - - - - - 0.481165 - - - - - -0.850229 - - -
- 4 - - - - - -5 - - - - - -1.45342 - - - - - -1.05774 - - - - - -0.165562 - - - - - -0.515018 - - -
- 5 - - - - - -6 - - - - - 1.33694 - - - - - -0.562861 - - - - - -1.39285 - - - - - 0.063328 - - -
- 6 - - - - - -7 - - - - - -0.121668 - - - - - -1.2076 - - - - - 0.00204021 - - - - - -1.6278 - - -
- 7 - - - - - -8 - - - - - -0.354493 - - - - - -1.03753 - - - - - 0.385684 - - - - - -0.519818 - - -
- 8 - - - - - -9 - - - - - -1.68658 - - - - - 1.32596 - - - - - -1.42898 - - - - - 2.08935 - - -
- 9 - - - - - -10 - - - - - 0.12982 - - - - - -0.631523 - - - - - 0.586538 - - - - - -0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Notice that you're able share the styles even though they're data aware. The styles are re-evaluated on the new DataFrame they've been used upon.

- -
-
-
-
-
-
-
-
-

Other options

You've seen a few methods for data-driven styling. -Styler also provides a few other options for styles that don't depend on the data.

-
    -
  • precision
  • -
  • captions
  • -
  • table-wide styles
  • -
-

Each of these can be specified in two ways:

-
    -
  • A keyword argument to pandas.core.Styler
  • -
  • A call to one of the .set_ methods, e.g. .set_caption
  • -
-

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.

- -
-
-
-
-
-
-
-
-

Precision

-
-
-
-
-
-
-
-
-

You can control the precision of floats using pandas' regular display.precision option.

- -
-
-
-
-
-
In [26]:
-
-
-
with pd.option_context('display.precision', 2):
-    html = (df.style
-              .applymap(color_negative_red)
-              .apply(highlight_max))
-html
-
- -
-
-
- -
-
- - -
Out[26]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.3 - - - - - nan - - - - - -0.32 - - - - - -0.99 - - -
- 1 - - - - - 2 - - - - - -1.1 - - - - - -1.4 - - - - - 0.56 - - - - - 0.3 - - -
- 2 - - - - - 3 - - - - - -1.6 - - - - - 0.22 - - - - - 0.68 - - - - - 1.9 - - -
- 3 - - - - - 4 - - - - - 0.96 - - - - - 0.1 - - - - - -0.48 - - - - - 0.85 - - -
- 4 - - - - - 5 - - - - - 1.5 - - - - - 1.1 - - - - - 0.17 - - - - - 0.52 - - -
- 5 - - - - - 6 - - - - - -1.3 - - - - - 0.56 - - - - - 1.4 - - - - - -0.063 - - -
- 6 - - - - - 7 - - - - - 0.12 - - - - - 1.2 - - - - - -0.002 - - - - - 1.6 - - -
- 7 - - - - - 8 - - - - - 0.35 - - - - - 1 - - - - - -0.39 - - - - - 0.52 - - -
- 8 - - - - - 9 - - - - - 1.7 - - - - - -1.3 - - - - - 1.4 - - - - - -2.1 - - -
- 9 - - - - - 10 - - - - - -0.13 - - - - - 0.63 - - - - - -0.59 - - - - - 0.29 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Or through a set_precision method.

- -
-
-
-
-
-
In [27]:
-
-
-
df.style\
-  .applymap(color_negative_red)\
-  .apply(highlight_max)\
-  .set_precision(2)
-
- -
-
-
- -
-
- - -
Out[27]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.3 - - - - - nan - - - - - -0.32 - - - - - -0.99 - - -
- 1 - - - - - 2 - - - - - -1.1 - - - - - -1.4 - - - - - 0.56 - - - - - 0.3 - - -
- 2 - - - - - 3 - - - - - -1.6 - - - - - 0.22 - - - - - 0.68 - - - - - 1.9 - - -
- 3 - - - - - 4 - - - - - 0.96 - - - - - 0.1 - - - - - -0.48 - - - - - 0.85 - - -
- 4 - - - - - 5 - - - - - 1.5 - - - - - 1.1 - - - - - 0.17 - - - - - 0.52 - - -
- 5 - - - - - 6 - - - - - -1.3 - - - - - 0.56 - - - - - 1.4 - - - - - -0.063 - - -
- 6 - - - - - 7 - - - - - 0.12 - - - - - 1.2 - - - - - -0.002 - - - - - 1.6 - - -
- 7 - - - - - 8 - - - - - 0.35 - - - - - 1 - - - - - -0.39 - - - - - 0.52 - - -
- 8 - - - - - 9 - - - - - 1.7 - - - - - -1.3 - - - - - 1.4 - - - - - -2.1 - - -
- 9 - - - - - 10 - - - - - -0.13 - - - - - 0.63 - - - - - -0.59 - - - - - 0.29 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

Setting the precision only affects the printed number; the full-precision values are always passed to your style functions. You can always use df.round(2).style if you'd prefer to round from the start.

- -
-
-
-
-
-
-
-
-

Captions

-
-
-
-
-
-
-
-
-

Regular table captions can be added in a few ways.

- -
-
-
-
-
-
In [28]:
-
-
-
df.style.set_caption('Colormaps, with a caption.')\
-    .background_gradient(cmap=cm)
-
- -
-
-
- -
-
- - -
-
-
----------------------------------------------------------------------------
-NameError                                 Traceback (most recent call last)
-<ipython-input-28-1c18b6874409> in <module>()
-----> 1 df.style.set_caption('Colormaps, with a caption.')    .background_gradient(cmap=cm)
-
-NameError: name 'cm' is not defined
-
-
- -
-
- -
-
-
-
-
-
-

Table Styles

-
-
-
-
-
-
-
-
-

The next option you have are "table styles". -These are styles that apply to the table as a whole, but don't look at the data. -Certain sytlings, including pseudo-selectors like :hover can only be used this way.

- -
-
-
-
-
-
In [29]:
-
-
-
from IPython.display import HTML
-
-def hover(hover_color="#ffff99"):
-    return dict(selector="tr:hover",
-                props=[("background-color", "%s" % hover_color)])
-
-styles = [
-    hover(),
-    dict(selector="th", props=[("font-size", "150%"),
-                               ("text-align", "center")]),
-    dict(selector="caption", props=[("caption-side", "bottom")])
-]
-html = (df.style.set_table_styles(styles)
-          .set_caption("Hover to highlight."))
-html
-
- -
-
-
- -
-
- - -
Out[29]:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Hover to highlight.
- - - - - - A - - - - - B - - - - - C - - - - - D - - - - - E - - -
- 0 - - - - - 1 - - - - - 1.32921 - - - - - nan - - - - - -0.31628 - - - - - -0.99081 - - -
- 1 - - - - - 2 - - - - - -1.07082 - - - - - -1.43871 - - - - - 0.564417 - - - - - 0.295722 - - -
- 2 - - - - - 3 - - - - - -1.6264 - - - - - 0.219565 - - - - - 0.678805 - - - - - 1.88927 - - -
- 3 - - - - - 4 - - - - - 0.961538 - - - - - 0.104011 - - - - - -0.481165 - - - - - 0.850229 - - -
- 4 - - - - - 5 - - - - - 1.45342 - - - - - 1.05774 - - - - - 0.165562 - - - - - 0.515018 - - -
- 5 - - - - - 6 - - - - - -1.33694 - - - - - 0.562861 - - - - - 1.39285 - - - - - -0.063328 - - -
- 6 - - - - - 7 - - - - - 0.121668 - - - - - 1.2076 - - - - - -0.00204021 - - - - - 1.6278 - - -
- 7 - - - - - 8 - - - - - 0.354493 - - - - - 1.03753 - - - - - -0.385684 - - - - - 0.519818 - - -
- 8 - - - - - 9 - - - - - 1.68658 - - - - - -1.32596 - - - - - 1.42898 - - - - - -2.08935 - - -
- 9 - - - - - 10 - - - - - -0.12982 - - - - - 0.631523 - - - - - -0.586538 - - - - - 0.29072 - - -
- -
- -
- -
-
- -
-
-
-
-
-
-

table_styles should be a list of dictionaries. -Each dictionary should have the selector and props keys. -The value for selector should be a valid CSS selector. -Recall that all the styles are already attached to an id, unique to -each Styler. This selector is in addition to that id. -The value for props should be a list of tuples of ('attribute', 'value').

-

table_styles are extremely flexible, but not as fun to type out by hand. -We hope to collect some useful ones either in pandas, or preferable in a new package that builds on top the tools here.

- -
-
-
-
-
-
-
-
-

CSS Classes

Certain CSS classes are attached to cells.

-
    -
  • Index and Column names include index_name and level<k> where k is its level in a MultiIndex
  • -
  • Index label cells include
      -
    • row_heading
    • -
    • row<n> where n is the numeric position of the row
    • -
    • level<k> where k is the level in a MultiIndex
    • -
    -
  • -
  • Column label cells include
      -
    • col_heading
    • -
    • col<n> where n is the numeric position of the column
    • -
    • level<k> where k is the level in a MultiIndex
    • -
    -
  • -
  • Blank cells include blank
  • -
  • Data cells include data
  • -
- -
-
-
-
-
-
-
-
-

Limitations

    -
  • DataFrame only (use Series.to_frame().style)
  • -
  • The index and columns must be unique
  • -
  • No large repr, and performance isn't great; this is intended for summary DataFrames
  • -
  • You can only style the values, not the index or columns
  • -
  • You can only apply styles, you can't insert new HTML entities
  • -
-

Some of these will be addressed in the future.

- -
-
-
-
-
-
-
-
-

Terms

    -
  • Style function: a function that's passed into Styler.apply or Styler.applymap and returns values like 'css attribute: value'
  • -
  • Builtin style functions: style functions that are methods on Styler
  • -
  • table style: a dictionary with the two keys selector and props. selector is the CSS selector that props will apply to. props is a list of (attribute, value) tuples. A list of table styles passed into Styler.
  • -
- -
-
-
-
-
-
-
-
-

Fun stuff

Here are a few interesting examples.

-

Styler interacts pretty well with widgets. If you're viewing this online instead of running the notebook yourself, you're missing out on interactively adjusting the color palette.

- -
-
-
-
-
-
In [30]:
-
-
-
from IPython.html import widgets
-@widgets.interact
-def f(h_neg=(0, 359, 1), h_pos=(0, 359), s=(0., 99.9), l=(0., 99.9)):
-    return df.style.background_gradient(
-        cmap=sns.palettes.diverging_palette(h_neg=h_neg, h_pos=h_pos, s=s, l=l,
-                                            as_cmap=True)
-    )
-
- -
-
-
- -
-
- - -
-
-
----------------------------------------------------------------------------
-NameError                                 Traceback (most recent call last)
-<ipython-input-30-62e68348ecde> in f(h_neg, h_pos, s, l)
-      3 def f(h_neg=(0, 359, 1), h_pos=(0, 359), s=(0., 99.9), l=(0., 99.9)):
-      4     return df.style.background_gradient(
-----> 5         cmap=sns.palettes.diverging_palette(h_neg=h_neg, h_pos=h_pos, s=s, l=l,
-      6                                             as_cmap=True)
-      7     )
-
-NameError: name 'sns' is not defined
-
-
- -
-
- -
-
-
-
In [31]:
-
-
-
def magnify():
-    return [dict(selector="th",
-                 props=[("font-size", "4pt")]),
-            dict(selector="td",
-                 props=[('padding', "0em 0em")]),
-            dict(selector="th:hover",
-                 props=[("font-size", "12pt")]),
-            dict(selector="tr:hover td:hover",
-                 props=[('max-width', '200px'),
-                        ('font-size', '12pt')])
-]
-
- -
-
-
- -
-
-
-
In [32]:
-
-
-
np.random.seed(25)
-cmap = cmap=sns.diverging_palette(5, 250, as_cmap=True)
-df = pd.DataFrame(np.random.randn(20, 25)).cumsum()
-
-df.style.background_gradient(cmap, axis=1)\
-    .set_properties(**{'max-width': '80px', 'font-size': '1pt'})\
-    .set_caption("Hover to magify")\
-    .set_precision(2)\
-    .set_table_styles(magnify())
-
- -
-
-
- -
-
- - -
-
-
----------------------------------------------------------------------------
-NameError                                 Traceback (most recent call last)
-<ipython-input-32-298160b5e3a5> in <module>()
-      1 np.random.seed(25)
-----> 2 cmap = cmap=sns.diverging_palette(5, 250, as_cmap=True)
-      3 df = pd.DataFrame(np.random.randn(20, 25)).cumsum()
-      4 
-      5 df.style.background_gradient(cmap, axis=1)    .set_properties(**{'max-width': '80px', 'font-size': '1pt'})    .set_caption("Hover to magify")    .set_precision(2)    .set_table_styles(magnify())
-
-NameError: name 'sns' is not defined
-
-
- -
-
- -
-
-
-
-
-
-

Extensibility

The core of pandas is, and will remain, its "high-performance, easy-to-use data structures". -With that in mind, we hope that DataFrame.style accomplishes two goals

-
    -
  • Provide an API that is pleasing to use interactively and is "good enough" for many tasks
  • -
  • Provide the foundations for dedicated libraries to build on
  • -
-

If you build a great library on top of this, let us know and we'll link to it.

-

Subclassing

This section contains a bit of information about the implementation of Styler. -Since the feature is so new all of this is subject to change, even more so than the end-use API.

-

As users apply styles (via .apply, .applymap or one of the builtins), we don't actually calculate anything. -Instead, we append functions and arguments to a list self._todo. -When asked (typically in .render we'll walk through the list and execute each function (this is in self._compute(). -These functions update an internal defaultdict(list), self.ctx which maps DataFrame row / column positions to CSS attribute, value pairs.

-

We take the extra step through self._todo so that we can export styles and set them on other Stylers.

-

Rendering uses Jinja templates. -The .translate method takes self.ctx and builds another dictionary ready to be passed into Styler.template.render, the Jinja template.

-

Alternate templates

We've used Jinja templates to build up the HTML. -The template is stored as a class variable Styler.template.. Subclasses can override that.

-
class CustomStyle(Styler):
-    template = Template("""...""")
-
- -
-
-
\ No newline at end of file diff --git a/doc/source/whatsnew/v0.20.0.txt b/doc/source/whatsnew/v0.20.0.txt index 4cb844fb3e18f..e45484ed17251 100644 --- a/doc/source/whatsnew/v0.20.0.txt +++ b/doc/source/whatsnew/v0.20.0.txt @@ -30,8 +30,6 @@ New features - Integration with the ``feather-format``, including a new top-level ``pd.read_feather()`` and ``DataFrame.to_feather()`` method, see :ref:`here `. - ``Series.str.replace()`` now accepts a callable, as replacement, which is passed to ``re.sub`` (:issue:`15055`) - ``Series.str.replace()`` now accepts a compiled regular expression as a pattern (:issue:`15446`) -- ``.str.replace`` now accepts a callable, as replacement, which is passed to ``re.sub`` (:issue:`15055`) -- ``FrozenList`` has gained the ``.difference()`` setop method (:issue:`15475`) @@ -604,16 +602,12 @@ Deprecations - ``Series.sortlevel`` and ``DataFrame.sortlevel`` have been deprecated in favor of ``Series.sort_index`` and ``DataFrame.sort_index`` (:issue:`15099`) - importing ``concat`` from ``pandas.tools.merge`` has been deprecated in favor of imports from the ``pandas`` namespace. This should only affect explict imports (:issue:`15358`) - ``Series/DataFrame/Panel.consolidate()`` been deprecated as a public method. (:issue:`15483`) -<<<<<<< HEAD - The following top-level pandas functions have been deprecated and will be removed in a future version (:issue:`13790`) * ``pd.pnow()``, replaced by ``Period.now()`` * ``pd.Term``, is removed, as it is not applicable to user code. Instead use in-line string expressions in the where clause when searching in HDFStore * ``pd.Expr``, is removed, as it is not applicable to user code. * ``pd.match()``, is removed. * ``pd.groupby()``, replaced by using the ``.groupby()`` method directly on a ``Series/DataFrame`` -======= -- ``FrozenList`` addition (new object and inplace) have been deprecated in favor of the ``.union()`` method. (:issue: `15475`) ->>>>>>> 478003b... ENH: Added FrozenList difference setop .. _whatsnew_0200.prior_deprecations: diff --git a/pandas/core/panel.py b/pandas/core/panel.py index c5ea513223dce..4a6c6cf291316 100644 --- a/pandas/core/panel.py +++ b/pandas/core/panel.py @@ -940,9 +940,9 @@ def construct_index_parts(idx, major=True): minor_labels, minor_levels, minor_names = construct_index_parts( self.minor_axis, major=False) - levels = list(major_levels) + list(minor_levels) - labels = list(major_labels) + list(minor_labels) - names = list(major_names) + list(minor_names) + levels = major_levels + minor_levels + labels = major_labels + minor_labels + names = major_names + minor_names index = MultiIndex(levels=levels, labels=labels, names=names, verify_integrity=False) diff --git a/pandas/core/reshape.py b/pandas/core/reshape.py index 4dad410d52739..7bcd9f2d30b79 100644 --- a/pandas/core/reshape.py +++ b/pandas/core/reshape.py @@ -254,8 +254,8 @@ def get_new_columns(self): width = len(self.value_columns) propagator = np.repeat(np.arange(width), stride) if isinstance(self.value_columns, MultiIndex): - new_levels = self.value_columns.levels.union((self.removed_level,)) - new_names = self.value_columns.names.union((self.removed_name,)) + new_levels = self.value_columns.levels + (self.removed_level,) + new_names = self.value_columns.names + (self.removed_name,) new_labels = [lab.take(propagator) for lab in self.value_columns.labels] @@ -844,7 +844,7 @@ def melt(frame, id_vars=None, value_vars=None, var_name=None, for col in id_vars: mdata[col] = np.tile(frame.pop(col).values, K) - mcolumns = list(id_vars) + list(var_name) + list([value_name]) + mcolumns = id_vars + var_name + [value_name] mdata[value_name] = frame.values.ravel('F') for i, col in enumerate(var_name): diff --git a/pandas/core/strings.py b/pandas/core/strings.py index 686613e1fca0b..46ba48b4cd846 100644 --- a/pandas/core/strings.py +++ b/pandas/core/strings.py @@ -822,7 +822,7 @@ def str_extractall(arr, pat, flags=0): if 0 < len(index_list): from pandas import MultiIndex index = MultiIndex.from_tuples( - index_list, names=arr.index.names.union(["match"])) + index_list, names=arr.index.names + ["match"]) else: index = None result = arr._constructor_expanddim(match_list, index=index, diff --git a/pandas/indexes/frozen.py b/pandas/indexes/frozen.py index 47e2557333ec7..e043ba64bbad7 100644 --- a/pandas/indexes/frozen.py +++ b/pandas/indexes/frozen.py @@ -13,8 +13,6 @@ from pandas.types.cast import _coerce_indexer_dtype from pandas.formats.printing import pprint_thing -import warnings - class FrozenList(PandasObject, list): @@ -27,14 +25,11 @@ class FrozenList(PandasObject, list): # typechecks def __add__(self, other): - warnings.warn("__add__ is deprecated, use union(...)", FutureWarning) - return self.union(other) - - def __iadd__(self, other): - warnings.warn("__iadd__ is deprecated, use union(...)", FutureWarning) if isinstance(other, tuple): other = list(other) - return super(FrozenList, self).__iadd__(other) + return self.__class__(super(FrozenList, self).__add__(other)) + + __iadd__ = __add__ # Python 2 compat def __getslice__(self, i, j): @@ -85,19 +80,6 @@ def __repr__(self): __setitem__ = __setslice__ = __delitem__ = __delslice__ = _disabled pop = append = extend = remove = sort = insert = _disabled - def union(self, other): - """Returns a FrozenList with other concatenated to the end of self""" - if isinstance(other, tuple): - other = list(other) - return self.__class__(super(FrozenList, self).__add__(other)) - - def difference(self, other): - """Returns a FrozenList with the same elements as self, but with elements - that are also in other removed.""" - other = set(other) - temp = [x for x in self if x not in other] - return self.__class__(temp) - class FrozenNDArray(PandasObject, np.ndarray): diff --git a/pandas/tests/groupby/test_value_counts.py b/pandas/tests/groupby/test_value_counts.py index ff01df2693c7c..801d0da070112 100644 --- a/pandas/tests/groupby/test_value_counts.py +++ b/pandas/tests/groupby/test_value_counts.py @@ -28,7 +28,7 @@ def check_value_counts(df, keys, bins): gr = df.groupby(keys, sort=isort) right = gr['3rd'].apply(Series.value_counts, **kwargs) - right.index.names = right.index.names[:-1].union(['3rd']) + right.index.names = right.index.names[:-1] + ['3rd'] # have to sort on index because of unstable sort on values left, right = map(rebuild_index, (left, right)) # xref GH9212 diff --git a/pandas/tests/indexes/test_frozen.py b/pandas/tests/indexes/test_frozen.py index a5fbf066adc83..a82409fbf9513 100644 --- a/pandas/tests/indexes/test_frozen.py +++ b/pandas/tests/indexes/test_frozen.py @@ -15,35 +15,20 @@ def setUp(self): self.klass = FrozenList def test_add(self): - q = FrozenList([1]) - with tm.assert_produces_warning(FutureWarning, - check_stacklevel=False): - q = q + [2, 3] - expected = FrozenList([1, 2, 3]) - self.check_result(q, expected) - - def test_iadd(self): - q = FrozenList([1]) - with tm.assert_produces_warning(FutureWarning, - check_stacklevel=False): - q += [2, 3] - expected = FrozenList([1, 2, 3]) - self.check_result(q, expected) - - def test_union(self): - result = self.container.union((1, 2, 3)) + result = self.container + (1, 2, 3) expected = FrozenList(self.lst + [1, 2, 3]) self.check_result(result, expected) - def test_difference(self): - result = self.container.difference([2]) - expected = FrozenList([1, 3, 4, 5]) + result = (1, 2, 3) + self.container + expected = FrozenList([1, 2, 3] + self.lst) self.check_result(result, expected) - def test_difference_dupe(self): - result = FrozenList([1, 2, 3, 2]).difference([2]) - expected = FrozenList([1, 3]) - self.check_result(result, expected) + def test_inplace(self): + q = r = self.container + q += [5] + self.check_result(q, self.lst + [5]) + # other shouldn't be mutated + self.check_result(r, self.lst) class TestFrozenNDArray(CheckImmutable, CheckStringMixin, tm.TestCase): diff --git a/pandas/tools/concat.py b/pandas/tools/concat.py index ae9d7af9d98ff..6405106118472 100644 --- a/pandas/tools/concat.py +++ b/pandas/tools/concat.py @@ -574,7 +574,7 @@ def _make_concat_multiindex(indexes, keys, levels=None, names=None): " not have the same number of levels") # also copies - names = list(names) + list(_get_consensus_names(indexes)) + names = names + _get_consensus_names(indexes) return MultiIndex(levels=levels, labels=label_list, names=names, verify_integrity=False) diff --git a/test_fast.sh b/test_fast.sh index f22ab73277e8b..30ac7f84cbe8b 100755 --- a/test_fast.sh +++ b/test_fast.sh @@ -5,4 +5,4 @@ # https://github.com/pytest-dev/pytest/issues/1075 export PYTHONHASHSEED=$(python -c 'import random; print(random.randint(1, 4294967295))') -pytest pandas --skip-slow --skip-network -m "not single" -n 4 $@ +pytest pandas --skip-slow --skip-network -m "not single" -n 4