Skip to content

Commit 5c4f95c

Browse files
committed
ENH: multiindex formatting
closes #12423
1 parent db6d009 commit 5c4f95c

File tree

7 files changed

+249
-157
lines changed

7 files changed

+249
-157
lines changed

pandas/indexes/base.py

+19-6
Original file line numberDiff line numberDiff line change
@@ -608,27 +608,40 @@ def _formatter_func(self):
608608
"""
609609
return default_pprint
610610

611-
def _format_data(self):
611+
def _format_data(self, display_width=None, justify=False,
612+
max_seq_items=None):
612613
"""
613614
Return the formatted data as a unicode string
615+
616+
Parameters
617+
----------
618+
display_width: number of spaces for max width, optional
619+
inferred to console size or display.width option if None
620+
justify: boolean, default False
621+
force justification
622+
max_seq_items: integer, default None
623+
max number of items to display in a sequence
624+
614625
"""
615626
from pandas.formats.format import get_console_size, _get_adjustment
616-
display_width, _ = get_console_size()
617627
if display_width is None:
618-
display_width = get_option('display.width') or 80
628+
display_width, _ = get_console_size()
629+
if display_width is None:
630+
display_width = get_option('display.width') or 80
619631

620632
space1 = "\n%s" % (' ' * (len(self.__class__.__name__) + 1))
621633
space2 = "\n%s" % (' ' * (len(self.__class__.__name__) + 2))
622634

623635
n = len(self)
624636
sep = ','
625-
max_seq_items = get_option('display.max_seq_items') or n
637+
max_seq_items = max_seq_items \
638+
or get_option('display.max_seq_items') or n
626639
formatter = self._formatter_func
627640

628641
# do we want to justify (only do so for non-objects)
629642
is_justify = not (self.inferred_type in ('string', 'unicode') or
630643
(self.inferred_type == 'categorical' and
631-
is_object_dtype(self.categories)))
644+
is_object_dtype(self.categories))) or justify
632645

633646
# are we a truncated display
634647
is_truncated = n > max_seq_items
@@ -663,7 +676,7 @@ def best_len(values):
663676
else:
664677

665678
if n > max_seq_items:
666-
n = min(max_seq_items // 2, 10)
679+
n = min(max(max_seq_items // 2, 10), 50)
667680
head = [formatter(x) for x in self[:n]]
668681
tail = [formatter(x) for x in self[-n:]]
669682
else:

pandas/indexes/multi.py

+56-4
Original file line numberDiff line numberDiff line change
@@ -411,11 +411,63 @@ def _format_attrs(self):
411411
"""
412412
Return a list of tuples of the (attr,formatted_value)
413413
"""
414+
# extension space (includes levels/labels)
415+
space3 = "\n%s" % (' ' * (len(self.__class__.__name__) + 3 + 6))
416+
space4 = "\n%s" % (' ' * (len(self.__class__.__name__) + 4 + 6))
417+
418+
level_seq_items = get_option('display.max_seq_items') or 100
419+
label_seq_items = max(level_seq_items // 2, 10)
420+
421+
def fd(l, max_seq_items=None, display_width=None, justify=True):
422+
# call ._format_data with specified paramaters
423+
424+
return l._format_data(max_seq_items=max_seq_items,
425+
display_width=display_width,
426+
justify=justify)
427+
428+
# let's see what our best display width for levels / labels are
429+
max_levels = max([len(fd(l, max_seq_items=level_seq_items))
430+
for l in self._levels])
431+
max_labels = max([len(fd(Index(l), max_seq_items=label_seq_items))
432+
for l in self._labels])
433+
display_width = max(max_levels, max_labels)
434+
min_display_width = get_option('display.width') or 80
435+
if display_width < min_display_width:
436+
display_width = min_display_width
437+
438+
def strip(line):
439+
# strip final whitespace + newline
440+
line = line.rstrip('\n ')
441+
442+
# strip header space on each line
443+
# replacing with space3 (and nothing for first)
444+
lines = [l.lstrip() for l in line.split('\n')]
445+
if len(lines) == 1:
446+
return line
447+
return lines[0] + space4 + space4.join(lines[1:])
448+
449+
# levels
450+
levels = []
451+
for l in self._levels:
452+
formatted = fd(l,
453+
max_seq_items=level_seq_items,
454+
display_width=display_width)
455+
levels.append(strip(formatted))
456+
levels = '[' + (space3.join(levels))[:-1] + ']'
457+
458+
# labels
459+
labels = []
460+
for l in self._labels:
461+
formatted = fd(Index(l),
462+
max_seq_items=label_seq_items,
463+
display_width=display_width)
464+
labels.append(strip(formatted))
465+
labels = '[' + (space3.join(labels))[:-1] + ']'
466+
414467
attrs = [
415-
('levels', ibase.default_pprint(self._levels,
416-
max_seq_items=False)),
417-
('labels', ibase.default_pprint(self._labels,
418-
max_seq_items=False))]
468+
('levels', levels),
469+
('labels', labels),
470+
]
419471
if not all(name is None for name in self.names):
420472
attrs.append(('names', ibase.default_pprint(self.names)))
421473
if self.sortorder is not None:

pandas/tests/indexes/common.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,13 @@ def test_dtype_str(self):
157157
def test_repr_max_seq_item_setting(self):
158158
# GH10182
159159
idx = self.create_index()
160-
idx = idx.repeat(50)
161-
with pd.option_context("display.max_seq_items", None):
162-
repr(idx)
163-
self.assertFalse('...' in str(idx))
160+
161+
# format tested sep
162+
if not isinstance(idx, MultiIndex):
163+
idx = idx.repeat(50)
164+
with pd.option_context("display.max_seq_items", None):
165+
repr(idx)
166+
self.assertFalse('...' in str(idx))
164167

165168
def test_wrong_number_names(self):
166169
def testit(ind):

pandas/tests/indexes/test_base.py

+56-53
Original file line numberDiff line numberDiff line change
@@ -1443,69 +1443,72 @@ def test_string_index_repr(self):
14431443
self.assertEqual(coerce(idx), expected)
14441444

14451445
# truncated
1446-
idx = pd.Index(['a', 'bb', 'ccc'] * 100)
1447-
if PY3:
1448-
expected = u"""\
1449-
Index(['a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a',
1450-
...
1451-
'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc'],
1452-
dtype='object', length=300)"""
1446+
with cf.option_context('display.width', 200,
1447+
'display.max_seq_items', 10):
14531448

1454-
self.assertEqual(repr(idx), expected)
1455-
else:
1456-
expected = u"""\
1457-
Index([u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a',
1458-
...
1459-
u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc'],
1460-
dtype='object', length=300)"""
1449+
idx = pd.Index(['a', 'bb', 'ccc'] * 100)
1450+
if PY3:
1451+
expected = u"""\
1452+
Index(['a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a',
1453+
...
1454+
'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc'],
1455+
dtype='object', length=300)"""
14611456

1462-
self.assertEqual(coerce(idx), expected)
1457+
self.assertEqual(repr(idx), expected)
1458+
else:
1459+
expected = u"""\
1460+
Index([u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a',
1461+
...
1462+
u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc'],
1463+
dtype='object', length=300)"""
14631464

1464-
# short
1465-
idx = pd.Index([u'あ', u'いい', u'ううう'])
1466-
if PY3:
1467-
expected = u"""Index(['あ', 'いい', 'ううう'], dtype='object')"""
1468-
self.assertEqual(repr(idx), expected)
1469-
else:
1470-
expected = u"""\
1471-
Index([u'あ', u'いい', u'ううう'], dtype='object')"""
1472-
self.assertEqual(coerce(idx), expected)
1465+
self.assertEqual(coerce(idx), expected)
14731466

1474-
# multiple lines
1475-
idx = pd.Index([u'あ', u'いい', u'ううう'] * 10)
1476-
if PY3:
1477-
expected = u"""Index(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',
1478-
'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',
1479-
'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
1480-
dtype='object')"""
1467+
# short
1468+
idx = pd.Index([u'あ', u'いい', u'ううう'])
1469+
if PY3:
1470+
expected = u"""Index(['あ', 'いい', 'ううう'], dtype='object')"""
1471+
self.assertEqual(repr(idx), expected)
1472+
else:
1473+
expected = u"""\
1474+
Index([u'あ', u'いい', u'ううう'], dtype='object')"""
1475+
self.assertEqual(coerce(idx), expected)
14811476

1482-
self.assertEqual(repr(idx), expected)
1483-
else:
1484-
expected = u"""Index([u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ',
1485-
u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい',
1486-
u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう'],
1487-
dtype='object')"""
1477+
# multiple lines
1478+
idx = pd.Index([u'あ', u'いい', u'ううう'] * 10)
1479+
if PY3:
1480+
expected = u"""Index(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',
1481+
'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',
1482+
'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
1483+
dtype='object')"""
14881484

1489-
self.assertEqual(coerce(idx), expected)
1485+
self.assertEqual(repr(idx), expected)
1486+
else:
1487+
expected = u"""Index([u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ',
1488+
u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい',
1489+
u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう'],
1490+
dtype='object')"""
14901491

1491-
# truncated
1492-
idx = pd.Index([u'あ', u'いい', u'ううう'] * 100)
1493-
if PY3:
1494-
expected = u"""Index(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ',
1495-
...
1496-
'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
1497-
dtype='object', length=300)"""
1492+
self.assertEqual(coerce(idx), expected)
14981493

1499-
self.assertEqual(repr(idx), expected)
1500-
else:
1501-
expected = u"""Index([u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ',
1502-
...
1503-
u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう'],
1504-
dtype='object', length=300)"""
1494+
# truncated
1495+
idx = pd.Index([u'あ', u'いい', u'ううう'] * 100)
1496+
if PY3:
1497+
expected = u"""Index(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ',
1498+
...
1499+
'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
1500+
dtype='object', length=300)"""
15051501

1506-
self.assertEqual(coerce(idx), expected)
1502+
self.assertEqual(repr(idx), expected)
1503+
else:
1504+
expected = u"""Index([u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ',
1505+
...
1506+
u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう'],
1507+
dtype='object', length=300)"""
1508+
1509+
self.assertEqual(coerce(idx), expected)
15071510

1508-
# Emable Unicode option -----------------------------------------
1511+
# Emable Unicode option -----------------------------------------
15091512
with cf.option_context('display.unicode.east_asian_width', True):
15101513

15111514
# short

pandas/tests/indexes/test_category.py

+39-43
Original file line numberDiff line numberDiff line change
@@ -562,23 +562,16 @@ def test_string_categorical_index_repr(self):
562562
self.assertEqual(unicode(idx), expected)
563563

564564
# truncated
565-
idx = pd.CategoricalIndex(['a', 'bb', 'ccc'] * 100)
566-
if PY3:
567-
expected = u"""CategoricalIndex(['a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a',
568-
...
569-
'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc'],
570-
categories=['a', 'bb', 'ccc'], ordered=False, dtype='category', length=300)"""
565+
with cf.option_context('display.width', 80):
566+
idx = pd.CategoricalIndex(['a', 'bb', 'ccc'] * 100)
567+
if PY3:
568+
expected = u"""CategoricalIndex([a', bb', ccc', a', bb', ccc', a', bb',\n ccc', a', bb', ccc', a', bb', ccc', a',\n bb', ccc', a', bb', ccc', a', bb', ccc',\n a', bb', ccc', a', bb', ccc', a', bb',\n ccc', a', bb', ccc', a', bb', ccc', a',\n bb', ccc', a', bb', ccc', a', bb', ccc',\n a', bb',\n ...\n bb', ccc', a', bb', ccc', a', bb', ccc',\n a', bb', ccc', a', bb', ccc', a', bb',\n ccc', a', bb', ccc', a', bb', ccc', a',\n bb', ccc', a', bb', ccc', a', bb', ccc',\n a', bb', ccc', a', bb', ccc', a', bb',\n ccc', a', bb', ccc', a', bb', ccc', a',\n bb', ccc'],\n categories=[a', bb', ccc'], ordered=False, dtype='category', length=300)""" # noqa
571569

572-
self.assertEqual(repr(idx), expected)
573-
else:
574-
expected = u"""CategoricalIndex([u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb',
575-
u'ccc', u'a',
576-
...
577-
u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a',
578-
u'bb', u'ccc'],
579-
categories=[u'a', u'bb', u'ccc'], ordered=False, dtype='category', length=300)"""
570+
self.assertEqual(repr(idx), expected)
571+
else:
572+
expected = u"""CategoricalIndex([u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb',\n u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a',\n u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc',\n u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb',\n u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a',\n u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc',\n u'a', u'bb',\n ...\n u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc',\n u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb',\n u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a',\n u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc',\n u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb',\n u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a',\n u'bb', u'ccc'],\n categories=[u'a', u'bb', u'ccc'], ordered=False, dtype='category', length=300)""" # noqa
580573

581-
self.assertEqual(unicode(idx), expected)
574+
self.assertEqual(unicode(idx), expected)
582575

583576
# larger categories
584577
idx = pd.CategoricalIndex(list('abcdefghijklmmo'))
@@ -622,39 +615,42 @@ def test_string_categorical_index_repr(self):
622615

623616
self.assertEqual(unicode(idx), expected)
624617

625-
# truncated
626-
idx = pd.CategoricalIndex([u'あ', u'いい', u'ううう'] * 100)
627-
if PY3:
628-
expected = u"""CategoricalIndex(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ',
629-
...
630-
'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
631-
categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category', length=300)"""
618+
with cf.option_context('display.width', 200,
619+
'display.max_seq_items', 10):
632620

633-
self.assertEqual(repr(idx), expected)
634-
else:
635-
expected = u"""CategoricalIndex([u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい',
636-
u'ううう', u'あ',
637-
...
638-
u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ',
639-
u'いい', u'ううう'],
640-
categories=[u'あ', u'いい', u'ううう'], ordered=False, dtype='category', length=300)"""
621+
# truncated
622+
idx = pd.CategoricalIndex([u'あ', u'いい', u'ううう'] * 100)
623+
if PY3:
624+
expected = u"""CategoricalIndex(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ',
625+
...
626+
'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
627+
categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category', length=300)"""
641628

642-
self.assertEqual(unicode(idx), expected)
629+
self.assertEqual(repr(idx), expected)
630+
else:
631+
expected = u"""CategoricalIndex([u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい',
632+
u'ううう', u'あ',
633+
...
634+
u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ',
635+
u'いい', u'ううう'],
636+
categories=[u'あ', u'いい', u'ううう'], ordered=False, dtype='category', length=300)"""
643637

644-
# larger categories
645-
idx = pd.CategoricalIndex(list(u'あいうえおかきくけこさしすせそ'))
646-
if PY3:
647-
expected = u"""CategoricalIndex(['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し',
648-
'す', 'せ', 'そ'],
649-
categories=['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', ...], ordered=False, dtype='category')"""
638+
self.assertEqual(unicode(idx), expected)
650639

651-
self.assertEqual(repr(idx), expected)
652-
else:
653-
expected = u"""CategoricalIndex([u'あ', u'い', u'う', u'え', u'お', u'か', u'き', u'く', u'け', u'こ',
654-
u'さ', u'し', u'す', u'せ', u'そ'],
655-
categories=[u'あ', u'い', u'う', u'え', u'お', u'か', u'き', u'く', ...], ordered=False, dtype='category')"""
640+
# larger categories
641+
idx = pd.CategoricalIndex(list(u'あいうえおかきくけこさしすせそ'))
642+
if PY3:
643+
expected = u"""CategoricalIndex(['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し',
644+
'す', 'せ', 'そ'],
645+
categories=['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', ...], ordered=False, dtype='category')"""
656646

657-
self.assertEqual(unicode(idx), expected)
647+
self.assertEqual(repr(idx), expected)
648+
else:
649+
expected = u"""CategoricalIndex([u'あ', u'い', u'う', u'え', u'お', u'か', u'き', u'く', u'け', u'こ',
650+
u'さ', u'し', u'す', u'せ', u'そ'],
651+
categories=[u'あ', u'い', u'う', u'え', u'お', u'か', u'き', u'く', ...], ordered=False, dtype='category')"""
652+
653+
self.assertEqual(unicode(idx), expected)
658654

659655
# Emable Unicode option -----------------------------------------
660656
with cf.option_context('display.unicode.east_asian_width', True):

0 commit comments

Comments
 (0)