Skip to content

ENH: multiindex formatting #12929

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 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions pandas/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,27 +608,40 @@ def _formatter_func(self):
"""
return default_pprint

def _format_data(self):
def _format_data(self, display_width=None, justify=False,
max_seq_items=None):
"""
Return the formatted data as a unicode string
Parameters
----------
display_width: number of spaces for max width, optional
inferred to console size or display.width option if None
justify: boolean, default False
force justification
max_seq_items: integer, default None
max number of items to display in a sequence
"""
from pandas.formats.format import get_console_size, _get_adjustment
display_width, _ = get_console_size()
if display_width is None:
display_width = get_option('display.width') or 80
display_width, _ = get_console_size()
if display_width is None:
display_width = get_option('display.width') or 80

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

n = len(self)
sep = ','
max_seq_items = get_option('display.max_seq_items') or n
max_seq_items = max_seq_items \
or get_option('display.max_seq_items') or n
formatter = self._formatter_func

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

# are we a truncated display
is_truncated = n > max_seq_items
Expand Down Expand Up @@ -663,7 +676,7 @@ def best_len(values):
else:

if n > max_seq_items:
n = min(max_seq_items // 2, 10)
n = min(max(max_seq_items // 2, 10), 50)
head = [formatter(x) for x in self[:n]]
tail = [formatter(x) for x in self[-n:]]
else:
Expand Down
60 changes: 56 additions & 4 deletions pandas/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,11 +411,63 @@ def _format_attrs(self):
"""
Return a list of tuples of the (attr,formatted_value)
"""
# extension space (includes levels/labels)
space3 = "\n%s" % (' ' * (len(self.__class__.__name__) + 3 + 6))
space4 = "\n%s" % (' ' * (len(self.__class__.__name__) + 4 + 6))

level_seq_items = get_option('display.max_seq_items') or 100
label_seq_items = max(level_seq_items // 2, 10)

def fd(l, max_seq_items=None, display_width=None, justify=True):
# call ._format_data with specified paramaters

return l._format_data(max_seq_items=max_seq_items,
display_width=display_width,
justify=justify)

# let's see what our best display width for levels / labels are
max_levels = max([len(fd(l, max_seq_items=level_seq_items))
for l in self._levels])
max_labels = max([len(fd(Index(l), max_seq_items=label_seq_items))
for l in self._labels])
display_width = max(max_levels, max_labels)
min_display_width = get_option('display.width') or 80
if display_width < min_display_width:
display_width = min_display_width

def strip(line):
# strip final whitespace + newline
line = line.rstrip('\n ')

# strip header space on each line
# replacing with space3 (and nothing for first)
lines = [l.lstrip() for l in line.split('\n')]
if len(lines) == 1:
return line
return lines[0] + space4 + space4.join(lines[1:])

# levels
levels = []
for l in self._levels:
formatted = fd(l,
max_seq_items=level_seq_items,
display_width=display_width)
levels.append(strip(formatted))
levels = '[' + (space3.join(levels))[:-1] + ']'

# labels
labels = []
for l in self._labels:
formatted = fd(Index(l),
max_seq_items=label_seq_items,
display_width=display_width)
labels.append(strip(formatted))
labels = '[' + (space3.join(labels))[:-1] + ']'

attrs = [
('levels', ibase.default_pprint(self._levels,
max_seq_items=False)),
('labels', ibase.default_pprint(self._labels,
max_seq_items=False))]
('levels', levels),
('labels', labels),
]
if not all(name is None for name in self.names):
attrs.append(('names', ibase.default_pprint(self.names)))
if self.sortorder is not None:
Expand Down
11 changes: 7 additions & 4 deletions pandas/tests/indexes/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,13 @@ def test_dtype_str(self):
def test_repr_max_seq_item_setting(self):
# GH10182
idx = self.create_index()
idx = idx.repeat(50)
with pd.option_context("display.max_seq_items", None):
repr(idx)
self.assertFalse('...' in str(idx))

# format tested sep
if not isinstance(idx, MultiIndex):
idx = idx.repeat(50)
with pd.option_context("display.max_seq_items", None):
repr(idx)
self.assertFalse('...' in str(idx))

def test_wrong_number_names(self):
def testit(ind):
Expand Down
109 changes: 56 additions & 53 deletions pandas/tests/indexes/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1443,69 +1443,72 @@ def test_string_index_repr(self):
self.assertEqual(coerce(idx), expected)

# truncated
idx = pd.Index(['a', 'bb', 'ccc'] * 100)
if PY3:
expected = u"""\
Index(['a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a',
...
'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc'],
dtype='object', length=300)"""
with cf.option_context('display.width', 200,
'display.max_seq_items', 10):

self.assertEqual(repr(idx), expected)
else:
expected = u"""\
Index([u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a',
...
u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc'],
dtype='object', length=300)"""
idx = pd.Index(['a', 'bb', 'ccc'] * 100)
if PY3:
expected = u"""\
Index(['a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a',
...
'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc'],
dtype='object', length=300)"""

self.assertEqual(coerce(idx), expected)
self.assertEqual(repr(idx), expected)
else:
expected = u"""\
Index([u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a',
...
u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc'],
dtype='object', length=300)"""

# short
idx = pd.Index([u'あ', u'いい', u'ううう'])
if PY3:
expected = u"""Index(['あ', 'いい', 'ううう'], dtype='object')"""
self.assertEqual(repr(idx), expected)
else:
expected = u"""\
Index([u'あ', u'いい', u'ううう'], dtype='object')"""
self.assertEqual(coerce(idx), expected)
self.assertEqual(coerce(idx), expected)

# multiple lines
idx = pd.Index([u'あ', u'いい', u'ううう'] * 10)
if PY3:
expected = u"""Index(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',
'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',
'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
dtype='object')"""
# short
idx = pd.Index([u'あ', u'いい', u'ううう'])
if PY3:
expected = u"""Index(['あ', 'いい', 'ううう'], dtype='object')"""
self.assertEqual(repr(idx), expected)
else:
expected = u"""\
Index([u'あ', u'いい', u'ううう'], dtype='object')"""
self.assertEqual(coerce(idx), expected)

self.assertEqual(repr(idx), expected)
else:
expected = u"""Index([u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ',
u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい',
u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう'],
dtype='object')"""
# multiple lines
idx = pd.Index([u'あ', u'いい', u'ううう'] * 10)
if PY3:
expected = u"""Index(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',
'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう',
'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
dtype='object')"""

self.assertEqual(coerce(idx), expected)
self.assertEqual(repr(idx), expected)
else:
expected = u"""Index([u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ',
u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい',
u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう'],
dtype='object')"""

# truncated
idx = pd.Index([u'あ', u'いい', u'ううう'] * 100)
if PY3:
expected = u"""Index(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ',
...
'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
dtype='object', length=300)"""
self.assertEqual(coerce(idx), expected)

self.assertEqual(repr(idx), expected)
else:
expected = u"""Index([u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ',
...
u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう'],
dtype='object', length=300)"""
# truncated
idx = pd.Index([u'あ', u'いい', u'ううう'] * 100)
if PY3:
expected = u"""Index(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ',
...
'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
dtype='object', length=300)"""

self.assertEqual(coerce(idx), expected)
self.assertEqual(repr(idx), expected)
else:
expected = u"""Index([u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ',
...
u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう'],
dtype='object', length=300)"""

self.assertEqual(coerce(idx), expected)

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

# short
Expand Down
82 changes: 39 additions & 43 deletions pandas/tests/indexes/test_category.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,23 +562,16 @@ def test_string_categorical_index_repr(self):
self.assertEqual(unicode(idx), expected)

# truncated
idx = pd.CategoricalIndex(['a', 'bb', 'ccc'] * 100)
if PY3:
expected = u"""CategoricalIndex(['a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a',
...
'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc', 'a', 'bb', 'ccc'],
categories=['a', 'bb', 'ccc'], ordered=False, dtype='category', length=300)"""
with cf.option_context('display.width', 80):
idx = pd.CategoricalIndex(['a', 'bb', 'ccc'] * 100)
if PY3:
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

self.assertEqual(repr(idx), expected)
else:
expected = u"""CategoricalIndex([u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a', u'bb',
u'ccc', u'a',
...
u'ccc', u'a', u'bb', u'ccc', u'a', u'bb', u'ccc', u'a',
u'bb', u'ccc'],
categories=[u'a', u'bb', u'ccc'], ordered=False, dtype='category', length=300)"""
self.assertEqual(repr(idx), expected)
else:
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

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

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

self.assertEqual(unicode(idx), expected)

# truncated
idx = pd.CategoricalIndex([u'あ', u'いい', u'ううう'] * 100)
if PY3:
expected = u"""CategoricalIndex(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ',
...
'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category', length=300)"""
with cf.option_context('display.width', 200,
'display.max_seq_items', 10):

self.assertEqual(repr(idx), expected)
else:
expected = u"""CategoricalIndex([u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい',
u'ううう', u'あ',
...
u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ',
u'いい', u'ううう'],
categories=[u'あ', u'いい', u'ううう'], ordered=False, dtype='category', length=300)"""
# truncated
idx = pd.CategoricalIndex([u'あ', u'いい', u'ううう'] * 100)
if PY3:
expected = u"""CategoricalIndex(['あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ',
...
'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう', 'あ', 'いい', 'ううう'],
categories=['あ', 'いい', 'ううう'], ordered=False, dtype='category', length=300)"""

self.assertEqual(unicode(idx), expected)
self.assertEqual(repr(idx), expected)
else:
expected = u"""CategoricalIndex([u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい',
u'ううう', u'あ',
...
u'ううう', u'あ', u'いい', u'ううう', u'あ', u'いい', u'ううう', u'あ',
u'いい', u'ううう'],
categories=[u'あ', u'いい', u'ううう'], ordered=False, dtype='category', length=300)"""

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

self.assertEqual(repr(idx), expected)
else:
expected = u"""CategoricalIndex([u'あ', u'い', u'う', u'え', u'お', u'か', u'き', u'く', u'け', u'こ',
u'さ', u'し', u'す', u'せ', u'そ'],
categories=[u'あ', u'い', u'う', u'え', u'お', u'か', u'き', u'く', ...], ordered=False, dtype='category')"""
# larger categories
idx = pd.CategoricalIndex(list(u'あいうえおかきくけこさしすせそ'))
if PY3:
expected = u"""CategoricalIndex(['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し',
'す', 'せ', 'そ'],
categories=['あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', ...], ordered=False, dtype='category')"""

self.assertEqual(unicode(idx), expected)
self.assertEqual(repr(idx), expected)
else:
expected = u"""CategoricalIndex([u'あ', u'い', u'う', u'え', u'お', u'か', u'き', u'く', u'け', u'こ',
u'さ', u'し', u'す', u'せ', u'そ'],
categories=[u'あ', u'い', u'う', u'え', u'お', u'か', u'き', u'く', ...], ordered=False, dtype='category')"""

self.assertEqual(unicode(idx), expected)

# Emable Unicode option -----------------------------------------
with cf.option_context('display.unicode.east_asian_width', True):
Expand Down
Loading