Skip to content

Commit a3d9a5d

Browse files
adamkleinwesm
authored andcommitted
FIX, ENH: #395 got float formatting working
1 parent 6e54384 commit a3d9a5d

File tree

6 files changed

+128
-53
lines changed

6 files changed

+128
-53
lines changed

RELEASE.rst

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ pandas 0.7.0
104104
- Some performance enhancements in constructing a Panel from a dict of
105105
DataFrame objects
106106
- Made ``Index._get_duplicates`` a public method by removing the underscore
107+
- Prettier printing of floats, and column spacing fix (GH #395, GH #571)
107108

108109
**Bug fixes**
109110

pandas/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from pandas.sparse.api import *
2424
from pandas.stats.api import *
2525

26-
from pandas.core.common import set_printoptions
26+
from pandas.core.common import set_printoptions, reset_printoptions
2727
from pandas.core.common import set_eng_float_format
2828
from pandas.io.parsers import read_csv, read_table, read_clipboard, ExcelFile
2929
from pandas.io.pytables import HDFStore

pandas/core/common.py

+66-15
Original file line numberDiff line numberDiff line change
@@ -380,17 +380,22 @@ def set_printoptions(precision=None, column_space=None, max_rows=None,
380380
out how big the terminal is and will not display more rows or/and
381381
columns that can fit on it.
382382
"""
383-
global _float_format, _column_space, _max_rows, _max_columns
383+
global _float_format, _column_space, _max_rows, _max_columns, _precision
384384
if precision is not None:
385-
float_format = '% .' + '%d' % precision + 'g'
386-
_float_format = lambda x: float_format % x
385+
_precision = precision
387386
if column_space is not None:
388387
_column_space = column_space
389388
if max_rows is not None:
390389
_max_rows = max_rows
391390
if max_columns is not None:
392391
_max_columns = max_columns
393392

393+
def reset_printoptions():
394+
global _float_format, _column_space, _precision
395+
_float_format = None
396+
_column_space = 12
397+
_precision = 4
398+
394399
class EngFormatter(object):
395400
"""
396401
Formats float values according to engineering format.
@@ -494,8 +499,9 @@ def set_eng_float_format(precision=3, use_eng_prefix=False):
494499
_float_format = EngFormatter(precision, use_eng_prefix)
495500
_column_space = max(12, precision + 9)
496501

497-
_float_format = lambda x: '% .4g' % x
502+
_float_format = None
498503
_column_space = 12
504+
_precision = 4
499505
_max_rows = 500
500506
_max_columns = 0
501507

@@ -506,6 +512,59 @@ def _stringify(col):
506512
else:
507513
return '%s' % col
508514

515+
def _float_format_default(v, width = None):
516+
"""
517+
Take a float and its formatted representation and if it needs extra space
518+
to fit the width, reformat it to that width.
519+
"""
520+
521+
fmt_str = '%% .%dg' % _precision
522+
formatted = fmt_str % v
523+
524+
if width is None:
525+
return formatted
526+
527+
extra_spc = width - len(formatted)
528+
529+
if extra_spc <= 0:
530+
return formatted
531+
532+
if 'e' in formatted:
533+
# we have something like 1e13 or 1.23e13
534+
base, exp = formatted.split('e')
535+
536+
if '.' in base:
537+
# expand fraction by extra space
538+
whole, frac = base.split('.')
539+
fmt_str = '%%.%df' % (len(frac) + extra_spc)
540+
frac = fmt_str % float("0.%s" % frac)
541+
base = whole + frac[1:]
542+
else:
543+
if extra_spc > 1:
544+
# enough room for fraction
545+
fmt_str = '%% .%df' % (extra_spc - 1)
546+
base = fmt_str % float(base)
547+
else:
548+
# enough room for decimal point only
549+
base += '.'
550+
551+
return base + 'e' + exp
552+
else:
553+
# we have something like 123 or 123.456
554+
if '.' in formatted:
555+
# expand fraction by extra space
556+
wholel, fracl = map(len, formatted.split("."))
557+
fmt_str = '%% .%df' % (fracl + extra_spc)
558+
else:
559+
if extra_spc > 1:
560+
# enough room for fraction
561+
fmt_str = '%% .%df' % (extra_spc - 1)
562+
else:
563+
# enough room for decimal point only
564+
fmt_str = '% d.'
565+
566+
return fmt_str % v
567+
509568
def _format(s, space=None, na_rep=None, float_format=None, col_width=None):
510569
def _just_help(x):
511570
if space is None:
@@ -520,18 +579,10 @@ def _just_help(x):
520579

521580
if float_format:
522581
formatted = float_format(s)
523-
else:
582+
elif _float_format:
524583
formatted = _float_format(s)
525-
526-
# TODO: fix this float behavior!
527-
# if we pass col_width, pad-zero the floats so all are same in column
528-
#if col_width is not None and '.' in formatted:
529-
# padzeros = col_width - len(formatted)
530-
# if padzeros > 0 and 'e' in formatted:
531-
# num, exp = formatted.split('e')
532-
# formatted = "%s%se%s" % (num, ('0' * padzeros), exp)
533-
# elif padzeros > 0:
534-
# formatted = formatted + ('0' * padzeros)
584+
else:
585+
formatted = _float_format_default(s, col_width)
535586

536587
return _just_help(formatted)
537588
elif isinstance(s, int):

pandas/core/format.py

+31-36
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ def to_string(self):
6868
Render a DataFrame to a console-friendly tabular output.
6969
"""
7070
frame = self.frame
71-
format_col = self._get_column_formatter()
7271

7372
to_write = []
7473

@@ -83,11 +82,10 @@ def to_string(self):
8382
str_columns = self._get_formatted_column_labels()
8483

8584
if self.header:
86-
stringified = [str_columns[i] + format_col(c)
85+
stringified = [str_columns[i] + self._format_col(c)
8786
for i, c in enumerate(self.columns)]
8887
else:
89-
stringified = [format_col(c) for c in self.columns]
90-
88+
stringified = [self._format_col(c) for c in self.columns]
9189

9290
if self.index:
9391
to_write.append(adjoin(1, str_index, *stringified))
@@ -101,6 +99,34 @@ def to_string(self):
10199

102100
self.buf.writelines(to_write)
103101

102+
def _default_col_formatter(self, v, col_width=None):
103+
from pandas.core.common import _format
104+
105+
return _format(v, space=self.col_space, na_rep=self.na_rep,
106+
float_format=self.float_format, col_width=col_width)
107+
108+
def _format_col(self, col, i=None):
109+
if self.formatters is None:
110+
self.formatters = {}
111+
112+
if col in self.formatters:
113+
formatter = self.formatters[col]
114+
115+
if i is None:
116+
return [formatter(x) for x in self.frame[col]]
117+
else:
118+
return formatter(self.frame[col][i])
119+
else:
120+
formatter = self._default_col_formatter
121+
122+
if i is not None:
123+
return formatter(self.frame[col][i])
124+
else:
125+
formatted = [formatter(x) for x in self.frame[col]]
126+
max_len = max(map(len, formatted))
127+
return [formatter(x, col_width=max_len)
128+
for x in self.frame[col]]
129+
104130
def to_html(self):
105131
"""
106132
Render a DataFrame to a html table.
@@ -130,7 +156,6 @@ def write_tr(buf, l, indent=0, indent_delta=4, header=False):
130156
indent_delta = 2
131157
frame = self.frame
132158
buf = self.buf
133-
format_col = self._get_column_formatter()
134159

135160
write(buf, '<table border="1">', indent)
136161

@@ -181,44 +206,14 @@ def _column_header():
181206
else:
182207
row.append(frame.index[i])
183208
for column in frame.columns:
184-
row.append(format_col(column, i))
209+
row.append(self._format_col(column, i))
185210
write_tr(buf, row, indent, indent_delta)
186211
indent -= indent_delta
187212
write(buf, '</tbody>', indent)
188213
indent -= indent_delta
189214

190215
write(buf, '</table>', indent)
191216

192-
def _get_column_formatter(self):
193-
from pandas.core.common import _format
194-
195-
col_space = self.col_space
196-
197-
def _myformat(col):
198-
formatter = lambda v: _format(v, space=col_space,
199-
na_rep=self.na_rep,
200-
float_format=self.float_format)
201-
# one pass through when float to stringify column, to pad with
202-
# zeros
203-
if issubclass(col.dtype.type, np.floating):
204-
col_width = max(map(len, map(formatter, col)))
205-
formatter = lambda v: _format(v, space=col_space,
206-
na_rep=self.na_rep,
207-
float_format=self.float_format,
208-
col_width=col_width)
209-
return formatter
210-
211-
formatters = {} if self.formatters is None else self.formatters
212-
213-
def _format_col(col, i=None):
214-
formatter = formatters.get(col, _myformat(self.frame[col]))
215-
if i == None:
216-
return [formatter(x) for x in self.frame[col]]
217-
else:
218-
return formatter(self.frame[col][i])
219-
220-
return _format_col
221-
222217
def _get_formatted_column_labels(self):
223218
from pandas.core.index import _sparsify
224219

pandas/core/series.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ def _get_repr(self, name=False, print_header=False, length=True,
512512
padSpace = min(maxlen, 60)
513513

514514
if float_format is None:
515-
float_format = com._float_format
515+
float_format = com._float_format_default
516516

517517
def _format(k, v):
518518
# GH #490

pandas/tests/test_frame.py

+28
Original file line numberDiff line numberDiff line change
@@ -1738,6 +1738,34 @@ def test_to_string_no_index(self):
17381738

17391739
assert(df_s == expected)
17401740

1741+
def test_to_string_float_formatting(self):
1742+
com.reset_printoptions()
1743+
com.set_printoptions(precision=6, column_space=12)
1744+
1745+
df = DataFrame({'x' : [0, 0.25, 3456.000, 12e+45, 1.64e+6,
1746+
1.7e+8, 1.253456, np.pi, -1e6]})
1747+
1748+
df_s = df.to_string()
1749+
1750+
expected = ' x \n0 0.000000\n1 0.250000\n' \
1751+
'2 3456.000\n3 1.20e+46\n4 1.64e+06\n' \
1752+
'5 1.70e+08\n6 1.253456\n7 3.141593\n' \
1753+
'8 -1.00e+06'
1754+
assert(df_s == expected)
1755+
1756+
df = DataFrame({'x' : [3234, 0.253]})
1757+
df_s = df.to_string()
1758+
1759+
expected = ' x \n0 3234.\n1 0.253'
1760+
assert(df_s == expected)
1761+
1762+
com.reset_printoptions()
1763+
1764+
df = DataFrame({'x': [1e9, 0.2512]})
1765+
df_s = df.to_string()
1766+
expected = ' x \n0 1.e+09\n1 0.2512'
1767+
assert(df_s == expected)
1768+
17411769
def test_to_html(self):
17421770
# big mixed
17431771
biggie = DataFrame({'A' : randn(1000),

0 commit comments

Comments
 (0)