Skip to content

Commit dfe026a

Browse files
committed
fix #274 : implemented set_printoptions() context_manager to set printing options for arrays
1 parent 0606e25 commit dfe026a

File tree

7 files changed

+205
-11
lines changed

7 files changed

+205
-11
lines changed

doc/source/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,7 @@ Miscellaneous
664664
from_frame
665665
from_series
666666
get_example_filepath
667+
set_printoptions
667668
labels_array
668669
union
669670
stack

doc/source/changes/version_0_30.rst.inc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,41 @@ Miscellaneous improvements
224224
* implemented :py:obj:`LArray.reverse()` method to reverse one or several axes of an array (closes :issue:`631`).
225225

226226

227+
* added :py:obj:`set_printoptions` allowing to set options for printing arrays within a ``with`` block or globally:
228+
229+
>>> from larray import *
230+
>>> arr = ndtest((500, 100), dtype=float) / 3
231+
232+
You can use ``set_options`` either as a context manager:
233+
234+
>>> with set_printoptions(display_width=60, edgeitems=2, precision=2):
235+
... print(arr)
236+
a\b b0 b1 ... b98 b99
237+
a0 0 0.33 ... 32.67 33
238+
a1 33.33 33.67 ... 66 66.33
239+
... ... ... ... ... ...
240+
a498 16600 16600.33 ... 16632.67 16633
241+
a499 16633.33 16633.67 ... 16666 16666.33
242+
243+
Or to set global options:
244+
245+
>>> set_printoptions(display_width=40, maxlines=10, precision=2)
246+
>>> print(arr)
247+
a\b b0 ... b99
248+
a0 0 ... 33
249+
a1 33.33 ... 66.33
250+
a2 66.67 ... 99.67
251+
a3 100 ... 133
252+
a4 133.33 ... 166.33
253+
... ... ... ...
254+
a495 16500 ... 16533
255+
a496 16533.33 ... 16566.33
256+
a497 16566.67 ... 16599.67
257+
a498 16600 ... 16633
258+
a499 16633.33 ... 16666.33
259+
260+
Closes :issue:`274`.
261+
227262
Fixes
228263
-----
229264

larray/core/array.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
from larray.util.misc import (table2str, size2str, basestring, izip, rproduct, ReprString, duplicates,
6666
float_error_handler_factory, _isnoneslice, light_product, unique_list, common_type,
6767
renamed_to, deprecate_kwarg, LHDFStore, lazy_attribute)
68+
from larray.util.options import OPTIONS, DISPLAY_PRECISION, DISPLAY_WIDTH, MAXLINES, EDGEITEMS
6869

6970

7071
def all(values, axis=None):
@@ -2277,8 +2278,12 @@ def __str__(self):
22772278
elif not len(self):
22782279
return 'LArray([])'
22792280
else:
2280-
table = list(self.as_table(maxlines=200, edgeitems=5))
2281-
return table2str(table, 'nan', fullinfo=True, maxwidth=200, keepcols=self.ndim - 1)
2281+
maxlines = OPTIONS[MAXLINES] if OPTIONS[MAXLINES] is not None else 200
2282+
maxwidth = OPTIONS[DISPLAY_WIDTH]
2283+
precision = OPTIONS[DISPLAY_PRECISION]
2284+
table = list(self.as_table(maxlines))
2285+
return table2str(table, 'nan', fullinfo=True, maxwidth=maxwidth, keepcols=self.ndim - 1,
2286+
precision=precision)
22822287
__repr__ = __str__
22832288

22842289
def __iter__(self):
@@ -2287,8 +2292,9 @@ def __iter__(self):
22872292
def __contains__(self, key):
22882293
return any(key in axis for axis in self.axes)
22892294

2290-
def as_table(self, maxlines=None, edgeitems=5, light=False, wide=True, value_name='value'):
2291-
"""
2295+
def as_table(self, maxlines=None, edgeitems=None, light=False, wide=True, value_name='value'):
2296+
r"""as_table(maxlines=None, edgeitems=5, light=False, wide=True, value_name='value')
2297+
22922298
Generator. Returns next line of the table representing an array.
22932299
22942300
Parameters
@@ -2300,6 +2306,9 @@ def as_table(self, maxlines=None, edgeitems=5, light=False, wide=True, value_nam
23002306
only the first and last `edgeitems` lines are displayed.
23012307
Only active if `maxlines` is not None.
23022308
Equals to 5 by default.
2309+
light: bool, optional
2310+
Whether or not printing the array in the same way as a pandas DataFrame with a MultiIndex
2311+
(see example below). Defaults to False.
23032312
wide : boolean, optional
23042313
Whether or not to write arrays in "wide" format. If True, arrays are exported with the last axis
23052314
represented horizontally. If False, arrays are exported in "narrow" format: one column per axis plus one
@@ -2317,13 +2326,13 @@ def as_table(self, maxlines=None, edgeitems=5, light=False, wide=True, value_nam
23172326
--------
23182327
>>> arr = ndtest((2, 2, 3))
23192328
>>> list(arr.as_table()) # doctest: +NORMALIZE_WHITESPACE
2320-
[['a', 'b\\\\c', 'c0', 'c1', 'c2'],
2329+
[['a', 'b\\c', 'c0', 'c1', 'c2'],
23212330
['a0', 'b0', 0, 1, 2],
23222331
['a0', 'b1', 3, 4, 5],
23232332
['a1', 'b0', 6, 7, 8],
23242333
['a1', 'b1', 9, 10, 11]]
23252334
>>> list(arr.as_table(light=True)) # doctest: +NORMALIZE_WHITESPACE
2326-
[['a', 'b\\\\c', 'c0', 'c1', 'c2'],
2335+
[['a', 'b\\c', 'c0', 'c1', 'c2'],
23272336
['a0', 'b0', 0, 1, 2],
23282337
['', 'b1', 3, 4, 5],
23292338
['a1', 'b0', 6, 7, 8],
@@ -2346,6 +2355,12 @@ def as_table(self, maxlines=None, edgeitems=5, light=False, wide=True, value_nam
23462355
if not self.ndim:
23472356
return
23482357

2358+
# get default options
2359+
if maxlines is None:
2360+
maxlines = OPTIONS[MAXLINES]
2361+
if edgeitems is None:
2362+
edgeitems = OPTIONS[EDGEITEMS]
2363+
23492364
# ert unit geo\time 2012 2011 2010
23502365
# NEER27 I05 AT 101.41 101.63 101.63
23512366
# NEER27 I05 AU 134.86 125.29 117.08
@@ -2398,7 +2413,9 @@ def as_table(self, maxlines=None, edgeitems=5, light=False, wide=True, value_nam
23982413
yield list(tick) + dataline.tolist()
23992414

24002415
def dump(self, header=True, wide=True, value_name='value'):
2401-
"""Dump array as a 2D nested list
2416+
"""dump(header=True, wide=True, value_name='value')
2417+
2418+
Dump array as a 2D nested list
24022419
24032420
Parameters
24042421
----------

larray/tests/test_options.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from __future__ import absolute_import, division, print_function
2+
import pytest
3+
import larray
4+
from larray.util.options import OPTIONS
5+
6+
7+
def test_invalid_option_raises():
8+
with pytest.raises(ValueError):
9+
larray.set_printoptions(not_a_valid_options=True)
10+
11+
12+
def test_set_options_as_global():
13+
original_ops = OPTIONS.copy()
14+
arr = larray.ndtest((500, 100))
15+
larray.set_printoptions(display_width=40, maxlines=10)
16+
expected = """\
17+
a\\b b0 b1 ... b98 b99
18+
a0 0 1 ... 98 99
19+
a1 100 101 ... 198 199
20+
a2 200 201 ... 298 299
21+
a3 300 301 ... 398 399
22+
a4 400 401 ... 498 499
23+
... ... ... ... ... ...
24+
a495 49500 49501 ... 49598 49599
25+
a496 49600 49601 ... 49698 49699
26+
a497 49700 49701 ... 49798 49799
27+
a498 49800 49801 ... 49898 49899
28+
a499 49900 49901 ... 49998 49999"""
29+
assert str(arr) == expected
30+
larray.set_printoptions(**original_ops)

larray/util/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
from __future__ import absolute_import, division, print_function
2+
3+
from larray.util.options import *

larray/util/misc.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,23 @@ def prod(values):
8181
return reduce(operator.mul, values, 1)
8282

8383

84-
def format_value(value, missing, fullinfo=False):
84+
def format_value(value, missing, fullinfo=False, precision=None):
8585
if isinstance(value, float) and not fullinfo:
8686
# nans print as "-1.#J", let's use something nicer
8787
if value != value:
8888
return missing
89+
elif precision is not None:
90+
return '{:2.{precision}f}'.format(value, precision=precision).rstrip('0').rstrip('.')
8991
else:
9092
return '%2.f' % value
9193
elif isinstance(value, np.ndarray) and value.shape:
9294
# prevent numpy's default wrapping
9395
return str(list(value)).replace(',', '')
9496
else:
95-
return str(value)
97+
if isinstance(value, float) and precision is not None:
98+
return '{:2.{precision}f}'.format(value, precision=precision).rstrip('0').rstrip('.')
99+
else:
100+
return str(value)
96101

97102

98103
def get_col_width(table, index):
@@ -131,8 +136,9 @@ def get_min_width(table, index):
131136
return max(longest_word(row[index]) for row in table)
132137

133138

139+
# XXX: what fullinfo is used for and when ?
134140
def table2str(table, missing, fullinfo=False, summarize=True, maxwidth=80, numedges='auto', sep=' ', cont='...',
135-
keepcols=0):
141+
keepcols=0, precision=None):
136142
"""
137143
table is a list of lists
138144
:type table: list of list
@@ -144,7 +150,7 @@ def table2str(table, missing, fullinfo=False, summarize=True, maxwidth=80, numed
144150
for row in table:
145151
if len(row) < numcol:
146152
row.extend([''] * (numcol - len(row)))
147-
formatted = [[format_value(value, missing, fullinfo) for value in row]
153+
formatted = [[format_value(value, missing, fullinfo, precision) for value in row]
148154
for row in table]
149155
maxwidths = [get_col_width(formatted, i) for i in range(numcol)]
150156

larray/util/options.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from __future__ import absolute_import, division, print_function
2+
3+
from larray.util.misc import basestring
4+
5+
6+
__all__ = ['set_printoptions']
7+
8+
9+
DISPLAY_PRECISION = 'precision'
10+
DISPLAY_WIDTH = 'display_width'
11+
MAXLINES = 'maxlines'
12+
EDGEITEMS = 'edgeitems'
13+
14+
15+
OPTIONS = {
16+
DISPLAY_PRECISION: None,
17+
DISPLAY_WIDTH: 200,
18+
MAXLINES: None,
19+
EDGEITEMS: 5,
20+
}
21+
22+
23+
def _positive_integer(value):
24+
if not (isinstance(value, int) and value > 0):
25+
raise ValueError("Expected positive integer")
26+
27+
28+
def _positive_integer_or_none(value):
29+
if value is None:
30+
return
31+
else:
32+
_positive_integer(value)
33+
34+
35+
_VALIDATORS = {
36+
DISPLAY_PRECISION: _positive_integer_or_none,
37+
DISPLAY_WIDTH: _positive_integer,
38+
MAXLINES: _positive_integer_or_none,
39+
EDGEITEMS: _positive_integer,
40+
}
41+
42+
43+
# idea taken from xarray. See xarray/core/options.py module for original implementation.
44+
class set_printoptions(object):
45+
r"""Set options for printing arrays in a controlled context.
46+
47+
Currently supported options:
48+
49+
- ``precision``: number of digits of precision for floating point output. Defaults to 8.
50+
- ``display_width``: maximum display width for ``repr`` on xarray objects. Defaults to 80.
51+
- ``maxlines``: Maximum number of lines to show. Default behavior shows all lines.
52+
- ``edgeitems`` : if number of lines to display is greater than ``maxlines``, only the first and last
53+
``edgeitems`` lines are displayed. Only active if ``maxlines`` is not None. Defaults to 5.
54+
55+
Examples
56+
--------
57+
>>> from larray import *
58+
>>> arr = ndtest((500, 100), dtype=float) / 3
59+
60+
You can use ``set_options`` either as a context manager:
61+
62+
>>> with set_printoptions(display_width=60, edgeitems=2, precision=2):
63+
... print(arr)
64+
a\b b0 b1 ... b98 b99
65+
a0 0 0.33 ... 32.67 33
66+
a1 33.33 33.67 ... 66 66.33
67+
... ... ... ... ... ...
68+
a498 16600 16600.33 ... 16632.67 16633
69+
a499 16633.33 16633.67 ... 16666 16666.33
70+
71+
Or to set global options:
72+
73+
>>> set_printoptions(display_width=40, maxlines=10, precision=2) # doctest: +SKIP
74+
>>> print(arr) # doctest: +SKIP
75+
a\b b0 ... b99
76+
a0 0 ... 33
77+
a1 33.33 ... 66.33
78+
a2 66.67 ... 99.67
79+
a3 100 ... 133
80+
a4 133.33 ... 166.33
81+
... ... ... ...
82+
a495 16500 ... 16533
83+
a496 16533.33 ... 16566.33
84+
a497 16566.67 ... 16599.67
85+
a498 16600 ... 16633
86+
a499 16633.33 ... 16666.33
87+
"""
88+
89+
def __init__(self, **kwargs):
90+
self.old = {}
91+
for k, v in kwargs.items():
92+
if k not in OPTIONS:
93+
raise ValueError('Argument {} is not in the set of valid options {}'.format(k, set(OPTIONS)))
94+
if k in _VALIDATORS:
95+
_VALIDATORS[k](v)
96+
self.old[k] = OPTIONS[k]
97+
OPTIONS.update(kwargs)
98+
99+
def __enter__(self):
100+
return
101+
102+
def __exit__(self, type, value, traceback):
103+
OPTIONS.update(self.old)

0 commit comments

Comments
 (0)