From 8dc00c3b9d5a94e9f2526208f4af15185de6c5c8 Mon Sep 17 00:00:00 2001 From: Matti Lyra Date: Fri, 20 Nov 2015 18:29:44 +0100 Subject: [PATCH 01/10] Added the option of defining precisions and format string per column, and removed formatting from index. --- pandas/core/style.py | 46 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/pandas/core/style.py b/pandas/core/style.py index 6ee5befd4ec02..e1a560614debe 100644 --- a/pandas/core/style.py +++ b/pandas/core/style.py @@ -6,6 +6,7 @@ from contextlib import contextmanager from uuid import uuid1 import copy +import numbers from collections import defaultdict try: @@ -113,7 +114,13 @@ class Styler(object): {% for c in r %} <{{c.type}} id="T_{{uuid}}{{c.id}}" class="{{c.class}}"> {% if c.value is number %} - {{c.value|round(precision)}} + {% if c.precision is defined and c.precision is number %} + {{c.value|round(c.precision)}} + {% elif c.precision is defined and c.precision is string %} + {{str.format(c.precision, c.value)}} + {% else %} + {{c.value|round(precision)}} + {% endif %} {% else %} {{c.value}} {% endif %} @@ -144,7 +151,18 @@ def __init__(self, data, precision=None, table_styles=None, uuid=None, self.table_styles = table_styles self.caption = caption if precision is None: - precision = pd.options.display.precision + precision = {'__default__': pd.options.display.precision} + elif isinstance(precision, numbers.Integral): + precision = {'__default__': pd.options.display.precision} + elif isinstance(precision, dict): + precision = {k: precision[k] for k in precision} # prevent tampering + if '__default__' not in precision: + precision['__default__'] = pd.options.display.precision + else: + raise TypeError('Precision is of an unknown type, it has to be None,\ + an integer or a dict containing formats for specific\ + columns.') + self.precision = precision self.table_attributes = table_attributes @@ -214,8 +232,11 @@ def _translate(self): for c, col in enumerate(self.data.columns): cs = [DATA_CLASS, "row%s" % r, "col%s" % c] cs.extend(cell_context.get("data", {}).get(r, {}).get(c, [])) - row_es.append({"type": "td", "value": self.data.iloc[r][c], - "class": " ".join(cs), "id": "_".join(cs[1:])}) + row_d = {"type": "td", "value": self.data.iloc[r][c], + "class": " ".join(cs), "id": "_".join(cs[1:])} + if c in self.precision: + row_d['precision'] = self.precision[c] + row_es.append(row_d) props = [] for x in ctx[r, c]: # have to handle empty styles like [''] @@ -399,7 +420,7 @@ def applymap(self, func, subset=None, **kwargs): kwargs)) return self - def set_precision(self, precision): + def set_precision(self, precision=None, **kwargs): """ Set the precision used to render. @@ -413,7 +434,20 @@ def set_precision(self, precision): ------- self """ - self.precision = precision + + if not kwargs and precision is None: + # reset everything + self.precision = {'__default__': pd.options.display.precision} + return self + + if precision is None: + self.precision['__default__'] = pd.options.display.precision + elif isinstance(precision, types.Integral): + self.precision['__default__'] = precision + + for k in kwargs: + self.precision[k] = kwargs[k] + return self def set_table_attributes(self, attributes): From dc197a90cdb46af83831c5db4f22848c0f2568b9 Mon Sep 17 00:00:00 2001 From: Matti Lyra Date: Fri, 20 Nov 2015 18:31:52 +0100 Subject: [PATCH 02/10] Changed types.Integral to numbers.Integral --- pandas/core/style.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/style.py b/pandas/core/style.py index e1a560614debe..ad7ee65ab9120 100644 --- a/pandas/core/style.py +++ b/pandas/core/style.py @@ -442,7 +442,7 @@ def set_precision(self, precision=None, **kwargs): if precision is None: self.precision['__default__'] = pd.options.display.precision - elif isinstance(precision, types.Integral): + elif isinstance(precision, numbers.Integral): self.precision['__default__'] = precision for k in kwargs: From e6793db90b8b54ac7c9372a68cd59b0e46fa9adf Mon Sep 17 00:00:00 2001 From: Matti Lyra Date: Fri, 20 Nov 2015 18:33:17 +0100 Subject: [PATCH 03/10] Added the .__default__ to the default precision format in the template string. --- pandas/core/style.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/style.py b/pandas/core/style.py index ad7ee65ab9120..8f1a32499d175 100644 --- a/pandas/core/style.py +++ b/pandas/core/style.py @@ -119,7 +119,7 @@ class Styler(object): {% elif c.precision is defined and c.precision is string %} {{str.format(c.precision, c.value)}} {% else %} - {{c.value|round(precision)}} + {{c.value|round(precision.__default__)}} {% endif %} {% else %} {{c.value}} From ea43ac8e3811a78d11df553a326e377366b77afb Mon Sep 17 00:00:00 2001 From: Matti Lyra Date: Fri, 20 Nov 2015 18:36:20 +0100 Subject: [PATCH 04/10] Changed the **kwargs to an explicit dictionary in .set_precision to allow for tuple keys. --- pandas/core/style.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pandas/core/style.py b/pandas/core/style.py index 8f1a32499d175..2f302182f0791 100644 --- a/pandas/core/style.py +++ b/pandas/core/style.py @@ -234,8 +234,9 @@ def _translate(self): cs.extend(cell_context.get("data", {}).get(r, {}).get(c, [])) row_d = {"type": "td", "value": self.data.iloc[r][c], "class": " ".join(cs), "id": "_".join(cs[1:])} - if c in self.precision: - row_d['precision'] = self.precision[c] + + if col in self.precision: + row_d['precision'] = self.precision[col] row_es.append(row_d) props = [] for x in ctx[r, c]: @@ -420,7 +421,7 @@ def applymap(self, func, subset=None, **kwargs): kwargs)) return self - def set_precision(self, precision=None, **kwargs): + def set_precision(self, precision=None, column_formats={}): """ Set the precision used to render. @@ -435,7 +436,7 @@ def set_precision(self, precision=None, **kwargs): self """ - if not kwargs and precision is None: + if not column_formats and precision is None: # reset everything self.precision = {'__default__': pd.options.display.precision} return self @@ -445,8 +446,8 @@ def set_precision(self, precision=None, **kwargs): elif isinstance(precision, numbers.Integral): self.precision['__default__'] = precision - for k in kwargs: - self.precision[k] = kwargs[k] + for k in column_formats: + self.precision[k] = column_formats[k] return self From a91d34f235b240e55e6792aa2f01964ff3024d4d Mon Sep 17 00:00:00 2001 From: Matti Lyra Date: Fri, 20 Nov 2015 18:44:59 +0100 Subject: [PATCH 05/10] Fixed a func calling error in template string. --- pandas/core/style.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/style.py b/pandas/core/style.py index 2f302182f0791..6d82923f537bb 100644 --- a/pandas/core/style.py +++ b/pandas/core/style.py @@ -117,7 +117,7 @@ class Styler(object): {% if c.precision is defined and c.precision is number %} {{c.value|round(c.precision)}} {% elif c.precision is defined and c.precision is string %} - {{str.format(c.precision, c.value)}} + {{c.precision.format(c.value)}} {% else %} {{c.value|round(precision.__default__)}} {% endif %} From 02dcc9a70682ab35de2ac0e76c693c3efb9f6430 Mon Sep 17 00:00:00 2001 From: Matti Lyra Date: Fri, 20 Nov 2015 19:02:37 +0100 Subject: [PATCH 06/10] Changed column_formats to subsets in .set_prevision --- pandas/core/style.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/core/style.py b/pandas/core/style.py index 6d82923f537bb..26d4ab2e68c03 100644 --- a/pandas/core/style.py +++ b/pandas/core/style.py @@ -421,7 +421,7 @@ def applymap(self, func, subset=None, **kwargs): kwargs)) return self - def set_precision(self, precision=None, column_formats={}): + def set_precision(self, precision=None, subsets={}): """ Set the precision used to render. @@ -436,7 +436,7 @@ def set_precision(self, precision=None, column_formats={}): self """ - if not column_formats and precision is None: + if not subsets and precision is None: # reset everything self.precision = {'__default__': pd.options.display.precision} return self @@ -446,8 +446,8 @@ def set_precision(self, precision=None, column_formats={}): elif isinstance(precision, numbers.Integral): self.precision['__default__'] = precision - for k in column_formats: - self.precision[k] = column_formats[k] + for k in subsets: + self.precision[k] = subsets[k] return self From 9f5396e54e1f02291f1401f596a660005831feef Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sun, 6 Dec 2015 08:37:01 -0600 Subject: [PATCH 07/10] buggy, partial implemntation of .format Conflicts: pandas/core/style.py --- pandas/core/style.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/pandas/core/style.py b/pandas/core/style.py index 26d4ab2e68c03..074e9a79e6881 100644 --- a/pandas/core/style.py +++ b/pandas/core/style.py @@ -255,6 +255,48 @@ def _translate(self): precision=precision, table_styles=table_styles, caption=caption, table_attributes=self.table_attributes) + def format(self, formatter, subset=None): + ''' + formatter is either an `a` or a dict `{column_name: a}` where + a is one of + - str: wrapped in: ``a.format(x)`` + - callable: called with x. + + now `.set_precision(2)` becomes + + ``` + .format('{:2g}') + ``` + ''' + from itertools import product + from collections.abc import MutableMapping + + just_row_subset = (com.is_list_like(subset) and + not isinstance(subset, tuple)) + + if issubclass(type(formatter), MutableMapping): + # formatter is a dict + for col, col_formatter in formatter.items(): + # get the row index locations out of subset + j = self.data.columns.get_indexer_for([col])[0] + if not callable(col_formatter): + formatter_func = lambda x: col_formatter.format(x) + + if subset is not None and just_row_subset: + locs = self.data.index.get_indexer_for(subset) + elif subset is not None: + locs = self.data.index.get_indexer_for(subset) + else: + locs = range(len(self.data)) + for i in locs: + self._display_funcs[(i, j)] = formatter_func + # single scalar to format all cells with + else: + locs = product(*(range(x) for x in self.data.shape)) + for i, j in locs: + self._display_funcs[(i, j)] = lambda x: formatter.format(x) + return self + def render(self): """ Render the built up styles to HTML From 23948b96dd292a207b4a8f4a5a0418428161dcd3 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Sat, 5 Dec 2015 11:41:17 -0600 Subject: [PATCH 08/10] WIP: display_format for style [ci skip] Conflicts: pandas/core/style.py --- pandas/core/style.py | 47 ++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/pandas/core/style.py b/pandas/core/style.py index 074e9a79e6881..b90eb6d490c0d 100644 --- a/pandas/core/style.py +++ b/pandas/core/style.py @@ -20,6 +20,7 @@ import numpy as np import pandas as pd from pandas.compat import lzip +import pandas.core.common as com from pandas.core.indexing import _maybe_numeric_slice, _non_reducing_slice try: import matplotlib.pyplot as plt @@ -113,17 +114,7 @@ class Styler(object): {% for c in r %} <{{c.type}} id="T_{{uuid}}{{c.id}}" class="{{c.class}}"> - {% if c.value is number %} - {% if c.precision is defined and c.precision is number %} - {{c.value|round(c.precision)}} - {% elif c.precision is defined and c.precision is string %} - {{c.precision.format(c.value)}} - {% else %} - {{c.value|round(precision.__default__)}} - {% endif %} - {% else %} - {{c.value}} - {% endif %} + {{c.display_value}} {% endfor %} {% endfor %} @@ -165,6 +156,13 @@ def __init__(self, data, precision=None, table_styles=None, uuid=None, self.precision = precision self.table_attributes = table_attributes + # display_funcs maps (row, col) -> formatting function + def default_display_func(x): + if com.is_float(x): + return '{:>.{precision}g}'.format(x, precision=self.precision) + else: + return x + self._display_funcs = defaultdict(lambda: default_display_func) def _repr_html_(self): ''' @@ -215,7 +213,10 @@ def _translate(self): cs = [COL_HEADING_CLASS, "level%s" % r, "col%s" % c] cs.extend(cell_context.get( "col_headings", {}).get(r, {}).get(c, [])) - row_es.append({"type": "th", "value": clabels[r][c], + value = clabels[r][c] + row_es.append({"type": "th", + "value": value, + "display_value": value, "class": " ".join(cs)}) head.append(row_es) @@ -226,18 +227,21 @@ def _translate(self): "row_headings", {}).get(r, {}).get(c, [])) row_es = [{"type": "th", "value": rlabels[r][c], - "class": " ".join(cs)} + "class": " ".join(cs), + "display_value": rlabels[r][c]} for c in range(len(rlabels[r]))] for c, col in enumerate(self.data.columns): cs = [DATA_CLASS, "row%s" % r, "col%s" % c] cs.extend(cell_context.get("data", {}).get(r, {}).get(c, [])) - row_d = {"type": "td", "value": self.data.iloc[r][c], - "class": " ".join(cs), "id": "_".join(cs[1:])} - - if col in self.precision: - row_d['precision'] = self.precision[col] - row_es.append(row_d) + formatter = self._display_funcs[(r, c)] + value = self.data.iloc[r, c] + row_es.append({"type": "td", + "value": value, + "class": " ".join(cs), + "id": "_".join(cs[1:]), + "display_value": formatter(value) + }) props = [] for x in ctx[r, c]: # have to handle empty styles like [''] @@ -255,6 +259,7 @@ def _translate(self): precision=precision, table_styles=table_styles, caption=caption, table_attributes=self.table_attributes) +<<<<<<< HEAD def format(self, formatter, subset=None): ''' formatter is either an `a` or a dict `{column_name: a}` where @@ -296,6 +301,10 @@ def format(self, formatter, subset=None): for i, j in locs: self._display_funcs[(i, j)] = lambda x: formatter.format(x) return self +======= + def format(self, columns=None): + pass +>>>>>>> c02a4f4... WIP: display_format for style def render(self): """ From a4ea5098f3d327da2e35b74d14883b6e7244224c Mon Sep 17 00:00:00 2001 From: Matti Lyra Date: Wed, 9 Dec 2015 18:27:28 +0100 Subject: [PATCH 09/10] Cherry picked changed from TomAugspurger/style-display-format --- pandas/core/style.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pandas/core/style.py b/pandas/core/style.py index b90eb6d490c0d..92de8eeae46bd 100644 --- a/pandas/core/style.py +++ b/pandas/core/style.py @@ -259,7 +259,6 @@ def _translate(self): precision=precision, table_styles=table_styles, caption=caption, table_attributes=self.table_attributes) -<<<<<<< HEAD def format(self, formatter, subset=None): ''' formatter is either an `a` or a dict `{column_name: a}` where @@ -301,10 +300,6 @@ def format(self, formatter, subset=None): for i, j in locs: self._display_funcs[(i, j)] = lambda x: formatter.format(x) return self -======= - def format(self, columns=None): - pass ->>>>>>> c02a4f4... WIP: display_format for style def render(self): """ From 092d7dacec03b81f88af4425d38f8fb3c6e7eadb Mon Sep 17 00:00:00 2001 From: Matti Lyra Date: Wed, 9 Dec 2015 18:37:32 +0100 Subject: [PATCH 10/10] Fixed broken commit --- pandas/core/style.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pandas/core/style.py b/pandas/core/style.py index 92de8eeae46bd..57348fb3c8955 100644 --- a/pandas/core/style.py +++ b/pandas/core/style.py @@ -263,8 +263,8 @@ def format(self, formatter, subset=None): ''' formatter is either an `a` or a dict `{column_name: a}` where a is one of - - str: wrapped in: ``a.format(x)`` - - callable: called with x. + - str: wrapped in: `a`.format(x)` + - callable: called with x and the column and row index of x. now `.set_precision(2)` becomes @@ -275,9 +275,6 @@ def format(self, formatter, subset=None): from itertools import product from collections.abc import MutableMapping - just_row_subset = (com.is_list_like(subset) and - not isinstance(subset, tuple)) - if issubclass(type(formatter), MutableMapping): # formatter is a dict for col, col_formatter in formatter.items(): @@ -286,9 +283,7 @@ def format(self, formatter, subset=None): if not callable(col_formatter): formatter_func = lambda x: col_formatter.format(x) - if subset is not None and just_row_subset: - locs = self.data.index.get_indexer_for(subset) - elif subset is not None: + if subset is not None: locs = self.data.index.get_indexer_for(subset) else: locs = range(len(self.data))