Skip to content

WIP: Better formatting for .set_precision #11667

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
100 changes: 88 additions & 12 deletions pandas/core/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from contextlib import contextmanager
from uuid import uuid1
import copy
import numbers
from collections import defaultdict

try:
Expand All @@ -19,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
Expand Down Expand Up @@ -112,11 +114,7 @@ class Styler(object):
<tr>
{% for c in r %}
<{{c.type}} id="T_{{uuid}}{{c.id}}" class="{{c.class}}">
{% if c.value is number %}
{{c.value|round(precision)}}
{% else %}
{{c.value}}
{% endif %}
{{c.display_value}}
{% endfor %}
</tr>
{% endfor %}
Expand Down Expand Up @@ -144,9 +142,27 @@ 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}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just call .set_precision instead of repeating this (and all of this logic should be there).

Further I think that a .set_precision() should reset to the default (e.g. pd.options.display.precision), so pls put in a test for this.

elif isinstance(precision, numbers.Integral):
precision = {'__default__': pd.options.display.precision}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use com.is_integer(...) instead here

elif isinstance(precision, dict):
precision = {k: precision[k] for k in precision} # prevent tampering
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is going to be a column mapping? need to check that

if '__default__' not in precision:
precision['__default__'] = pd.options.display.precision
else:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would alway set the precision to the default (or if a number is passed override to that). Then you can further set precision BY column with another call.

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
# 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):
'''
Expand Down Expand Up @@ -197,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)

Expand All @@ -208,14 +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_es.append({"type": "td", "value": self.data.iloc[r][c],
"class": " ".join(cs), "id": "_".join(cs[1:])})
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 ['']
Expand All @@ -233,6 +259,43 @@ 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 and the column and row index of x.

now `.set_precision(2)` becomes

```
.format('{:2g}')
```
'''
from itertools import product
from collections.abc import MutableMapping

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:
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
Expand Down Expand Up @@ -399,7 +462,7 @@ def applymap(self, func, subset=None, **kwargs):
kwargs))
return self

def set_precision(self, precision):
def set_precision(self, precision=None, subsets={}):
"""
Set the precision used to render.

Expand All @@ -413,7 +476,20 @@ def set_precision(self, precision):
-------
self
"""
self.precision = precision

if not subsets 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, numbers.Integral):
self.precision['__default__'] = precision

for k in subsets:
self.precision[k] = subsets[k]

return self

def set_table_attributes(self, attributes):
Expand Down