Skip to content

Commit cde9690

Browse files
committed
Merge PR #2436, wide DataFrame repr
2 parents 24133e8 + 2cefd24 commit cde9690

File tree

5 files changed

+213
-10
lines changed

5 files changed

+213
-10
lines changed

pandas/core/common.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1179,7 +1179,7 @@ def in_interactive_session():
11791179
returns True if running under python/ipython interactive shell
11801180
"""
11811181
import __main__ as main
1182-
return not hasattr(main, '__file__')
1182+
return not hasattr(main, '__file__') or get_option('test.interactive')
11831183

11841184
def in_qtconsole():
11851185
"""

pandas/core/config_init.py

+24
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,20 @@
101101
Controls the justification of column headers. used by DataFrameFormatter.
102102
"""
103103

104+
pc_expand_repr_doc="""
105+
: boolean
106+
Default False
107+
Whether to print out the full DataFrame repr for wide DataFrames
108+
across multiple lines.
109+
If False, the summary representation is shown.
110+
"""
111+
112+
pc_line_width_doc="""
113+
: int
114+
Default 80
115+
When printing wide DataFrames, this is the width of each line.
116+
"""
117+
104118
with cf.config_prefix('print'):
105119
cf.register_option('precision', 7, pc_precision_doc, validator=is_int)
106120
cf.register_option('float_format', None, float_format_doc)
@@ -122,3 +136,13 @@
122136
validator=is_bool)
123137
cf.register_option('encoding', detect_console_encoding(), pc_encoding_doc,
124138
validator=is_text)
139+
cf.register_option('expand_frame_repr', False, pc_expand_repr_doc)
140+
cf.register_option('line_width', 80, pc_line_width_doc)
141+
142+
tc_interactive_doc="""
143+
: boolean
144+
Default False
145+
Whether to simulate interactive mode for purposes of testing
146+
"""
147+
with cf.config_prefix('test'):
148+
cf.register_option('interactive', False, tc_interactive_doc)

pandas/core/format.py

+40-2
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ class DataFrameFormatter(object):
186186
def __init__(self, frame, buf=None, columns=None, col_space=None,
187187
header=True, index=True, na_rep='NaN', formatters=None,
188188
justify=None, float_format=None, sparsify=None,
189-
index_names=True, **kwds):
189+
index_names=True, line_width=None, **kwds):
190190
self.frame = frame
191191
self.buf = buf if buf is not None else StringIO()
192192
self.show_index_names = index_names
@@ -202,6 +202,7 @@ def __init__(self, frame, buf=None, columns=None, col_space=None,
202202
self.col_space = col_space
203203
self.header = header
204204
self.index = index
205+
self.line_width = line_width
205206

206207
if justify is None:
207208
self.justify = get_option("print.colheader_justify")
@@ -282,10 +283,36 @@ def to_string(self, force_unicode=None):
282283
text = info_line
283284
else:
284285
strcols = self._to_str_columns()
285-
text = adjoin(1, *strcols)
286+
if self.line_width is None:
287+
text = adjoin(1, *strcols)
288+
else:
289+
text = self._join_multiline(*strcols)
286290

287291
self.buf.writelines(text)
288292

293+
def _join_multiline(self, *strcols):
294+
lwidth = self.line_width
295+
strcols = list(strcols)
296+
if self.index:
297+
idx = strcols.pop(0)
298+
lwidth -= np.array([len(x) for x in idx]).max()
299+
300+
col_widths = [np.array([len(x) for x in col]).max()
301+
if len(col) > 0 else 0
302+
for col in strcols]
303+
col_bins = _binify(col_widths, lwidth)
304+
305+
str_lst = []
306+
st = 0
307+
for ed in col_bins:
308+
row = strcols[st:ed]
309+
row.insert(0, idx)
310+
if ed <= len(strcols):
311+
row.append([' \\'] + [' '] * (len(self.frame) - 1))
312+
str_lst.append(adjoin(1, *row))
313+
st = ed
314+
return '\n\n'.join(str_lst)
315+
289316
def to_latex(self, force_unicode=None, column_format=None):
290317
"""
291318
Render a DataFrame to a LaTeX tabular environment output.
@@ -1376,6 +1403,17 @@ def _put_lines(buf, lines):
13761403
lines = [unicode(x) for x in lines]
13771404
buf.write('\n'.join(lines))
13781405

1406+
def _binify(cols, width):
1407+
bins = []
1408+
curr_width = 0
1409+
for i, w in enumerate(cols):
1410+
curr_width += w
1411+
if curr_width + 2 > width:
1412+
bins.append(i)
1413+
curr_width = w
1414+
elif i + 1== len(cols):
1415+
bins.append(i + 1)
1416+
return bins
13791417

13801418
if __name__ == '__main__':
13811419
arr = np.array([746.03, 0.00, 5620.00, 1592.36])

pandas/core/frame.py

+44-7
Original file line numberDiff line numberDiff line change
@@ -592,25 +592,28 @@ def _need_info_repr_(self):
592592
max_rows = (terminal_height if get_option("print.max_rows") == 0
593593
else get_option("print.max_rows"))
594594
max_columns = get_option("print.max_columns")
595+
expand_repr = get_option("print.expand_frame_repr")
595596

596597
if max_columns > 0:
597598
if len(self.index) <= max_rows and \
598-
len(self.columns) <= max_columns:
599+
(len(self.columns) <= max_columns or expand_repr):
599600
return False
600601
else:
601602
return True
602603
else:
603604
# save us
604605
if (len(self.index) > max_rows or
605606
(com.in_interactive_session() and
606-
len(self.columns) > terminal_width // 2)):
607+
len(self.columns) > terminal_width // 2 and
608+
not expand_repr)):
607609
return True
608610
else:
609611
buf = StringIO()
610612
self.to_string(buf=buf)
611613
value = buf.getvalue()
612-
if (max([len(l) for l in value.split('\n')]) > terminal_width and
613-
com.in_interactive_session()):
614+
if (max([len(l) for l in value.split('\n')]) > terminal_width
615+
and com.in_interactive_session()
616+
and not expand_repr):
614617
return True
615618
else:
616619
return False
@@ -646,13 +649,45 @@ def __unicode__(self):
646649
if self._need_info_repr_():
647650
self.info(buf=buf, verbose=self._verbose_info)
648651
else:
649-
self.to_string(buf=buf)
652+
is_wide = self._need_wide_repr()
653+
line_width = None
654+
if is_wide:
655+
line_width = get_option('print.line_width')
656+
self.to_string(buf=buf, line_width=line_width)
650657

651658
value = buf.getvalue()
652659
assert type(value) == unicode
653660

654661
return value
655662

663+
def _need_wide_repr(self):
664+
if com.in_qtconsole():
665+
terminal_width, terminal_height = 100, 100
666+
else:
667+
terminal_width, terminal_height = get_terminal_size()
668+
max_columns = get_option("print.max_columns")
669+
expand_repr = get_option("print.expand_frame_repr")
670+
671+
if max_columns > 0:
672+
if len(self.columns) > max_columns and expand_repr:
673+
return True
674+
else:
675+
# save us
676+
if (com.in_interactive_session() and
677+
len(self.columns) > terminal_width // 2 and
678+
expand_repr):
679+
return True
680+
else:
681+
buf = StringIO()
682+
self.to_string(buf=buf)
683+
value = buf.getvalue()
684+
if (max([len(l) for l in value.split('\n')]) > terminal_width
685+
and com.in_interactive_session()
686+
and expand_repr):
687+
return True
688+
689+
return False
690+
656691
def __repr__(self):
657692
"""
658693
Return a string representation for a particular DataFrame
@@ -1454,7 +1489,8 @@ def to_excel(self, excel_writer, sheet_name='sheet1', na_rep='',
14541489
def to_string(self, buf=None, columns=None, col_space=None, colSpace=None,
14551490
header=True, index=True, na_rep='NaN', formatters=None,
14561491
float_format=None, sparsify=None, nanRep=None,
1457-
index_names=True, justify=None, force_unicode=None):
1492+
index_names=True, justify=None, force_unicode=None,
1493+
line_width=None):
14581494
"""
14591495
Render a DataFrame to a console-friendly tabular output.
14601496
"""
@@ -1480,7 +1516,8 @@ def to_string(self, buf=None, columns=None, col_space=None, colSpace=None,
14801516
sparsify=sparsify,
14811517
justify=justify,
14821518
index_names=index_names,
1483-
header=header, index=index)
1519+
header=header, index=index,
1520+
line_width=line_width)
14841521
formatter.to_string()
14851522

14861523
if buf is None:

pandas/tests/test_format.py

+104
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,110 @@ def test_frame_info_encoding(self):
400400
repr(df.T)
401401
fmt.set_printoptions(max_rows=200)
402402

403+
def test_wide_repr(self):
404+
set_option('test.interactive', True)
405+
col = lambda l, k: [tm.rands(k) for _ in xrange(l)]
406+
df = DataFrame([col(20, 25) for _ in range(10)])
407+
rep_str = repr(df)
408+
set_option('print.expand_frame_repr', True)
409+
wide_repr = repr(df)
410+
self.assert_(rep_str != wide_repr)
411+
412+
set_option('print.line_width', 120)
413+
wider_repr = repr(df)
414+
self.assert_(len(wider_repr) < len(wide_repr))
415+
416+
set_option('print.expand_frame_repr', False)
417+
set_option('test.interactive', False)
418+
set_option('print.line_width', 80)
419+
420+
def test_wide_repr_named(self):
421+
set_option('test.interactive', True)
422+
col = lambda l, k: [tm.rands(k) for _ in xrange(l)]
423+
df = DataFrame([col(20, 25) for _ in range(10)])
424+
df.index.name = 'DataFrame Index'
425+
rep_str = repr(df)
426+
set_option('print.expand_frame_repr', True)
427+
wide_repr = repr(df)
428+
self.assert_(rep_str != wide_repr)
429+
430+
set_option('print.line_width', 120)
431+
wider_repr = repr(df)
432+
self.assert_(len(wider_repr) < len(wide_repr))
433+
434+
for line in wide_repr.splitlines()[1::13]:
435+
self.assert_('DataFrame Index' in line)
436+
437+
set_option('print.expand_frame_repr', False)
438+
set_option('test.interactive', False)
439+
set_option('print.line_width', 80)
440+
441+
def test_wide_repr_multiindex(self):
442+
set_option('test.interactive', True)
443+
col = lambda l, k: [tm.rands(k) for _ in xrange(l)]
444+
midx = pandas.MultiIndex.from_arrays([np.array(col(10, 5)),
445+
np.array(col(10, 5))])
446+
df = DataFrame([col(20, 25) for _ in range(10)],
447+
index=midx)
448+
df.index.names = ['Level 0', 'Level 1']
449+
rep_str = repr(df)
450+
set_option('print.expand_frame_repr', True)
451+
wide_repr = repr(df)
452+
self.assert_(rep_str != wide_repr)
453+
454+
set_option('print.line_width', 120)
455+
wider_repr = repr(df)
456+
self.assert_(len(wider_repr) < len(wide_repr))
457+
458+
for line in wide_repr.splitlines()[1::13]:
459+
self.assert_('Level 0 Level 1' in line)
460+
461+
set_option('print.expand_frame_repr', False)
462+
set_option('test.interactive', False)
463+
set_option('print.line_width', 80)
464+
465+
def test_wide_repr_multiindex_cols(self):
466+
set_option('test.interactive', True)
467+
col = lambda l, k: [tm.rands(k) for _ in xrange(l)]
468+
midx = pandas.MultiIndex.from_arrays([np.array(col(10, 5)),
469+
np.array(col(10, 5))])
470+
mcols = pandas.MultiIndex.from_arrays([np.array(col(20, 3)),
471+
np.array(col(20, 3))])
472+
df = DataFrame([col(20, 25) for _ in range(10)],
473+
index=midx, columns=mcols)
474+
df.index.names = ['Level 0', 'Level 1']
475+
rep_str = repr(df)
476+
set_option('print.expand_frame_repr', True)
477+
wide_repr = repr(df)
478+
self.assert_(rep_str != wide_repr)
479+
480+
set_option('print.line_width', 120)
481+
wider_repr = repr(df)
482+
self.assert_(len(wider_repr) < len(wide_repr))
483+
484+
self.assert_(len(wide_repr.splitlines()) == 14 * 10 - 1)
485+
486+
set_option('print.expand_frame_repr', False)
487+
set_option('test.interactive', False)
488+
set_option('print.line_width', 80)
489+
490+
def test_wide_repr_unicode(self):
491+
set_option('test.interactive', True)
492+
col = lambda l, k: [tm.randu(k) for _ in xrange(l)]
493+
df = DataFrame([col(20, 25) for _ in range(10)])
494+
rep_str = repr(df)
495+
set_option('print.expand_frame_repr', True)
496+
wide_repr = repr(df)
497+
self.assert_(rep_str != wide_repr)
498+
499+
set_option('print.line_width', 120)
500+
wider_repr = repr(df)
501+
self.assert_(len(wider_repr) < len(wide_repr))
502+
503+
set_option('print.expand_frame_repr', False)
504+
set_option('test.interactive', False)
505+
set_option('print.line_width', 80)
506+
403507
def test_to_string(self):
404508
from pandas import read_table
405509
import re

0 commit comments

Comments
 (0)