Skip to content

Commit 6172d9a

Browse files
committed
changed according to comments
1 parent c445cd2 commit 6172d9a

File tree

4 files changed

+102
-119
lines changed

4 files changed

+102
-119
lines changed

pandas/core/indexes/multi.py

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -918,8 +918,7 @@ def _format_attrs(self):
918918
"""
919919
Return a list of tuples of the (attr,formatted_value)
920920
"""
921-
attrs = []
922-
attrs.append(('dtype', "'{}'".format(self.dtype)))
921+
attrs = [('dtype', "'{}'".format(self.dtype))]
923922
if self.names is not None and any(self.names):
924923
attrs.append(('names', default_pprint(self.names)))
925924
max_seq_items = get_option('display.max_seq_items') or len(self)
@@ -935,29 +934,7 @@ def _format_data(self, name=None):
935934
Return the formatted data as a unicode string
936935
"""
937936
return format_object_summary(self, self._formatter_func,
938-
name=name, is_multi=True)
939-
940-
def __unicode__(self):
941-
"""
942-
Return a string representation for this MultiIndex.
943-
944-
Invoked by unicode(df) in py2 only. Yields a Unicode String in both
945-
py2/py3.
946-
"""
947-
klass = self.__class__.__name__
948-
data = self._format_data()
949-
attrs = self._format_attrs()
950-
space = self._format_space()
951-
952-
prepr = (",%s" % space).join("%s=%s" % (k, v) for k, v in attrs)
953-
954-
# no data provided, just attributes
955-
if data is None:
956-
data = ''
957-
958-
res = "%s(%s%s)" % (klass, data, prepr)
959-
960-
return res
937+
name=name, line_break_each_value=True)
961938

962939
def _format_native_types(self, na_rep='nan', **kwargs):
963940
new_levels = []

pandas/io/formats/printing.py

Lines changed: 55 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ class TableSchemaFormatter(BaseFormatter):
265265

266266

267267
def format_object_summary(obj, formatter, is_justify=True, name=None,
268-
indent_for_name=True, is_multi=False):
268+
indent_for_name=True, line_break_each_value=False):
269269
"""
270270
Return the formatted obj as a unicode string
271271
@@ -282,8 +282,10 @@ def format_object_summary(obj, formatter, is_justify=True, name=None,
282282
indent_for_name : bool, default True
283283
Whether subsequent lines should be be indented to
284284
align with the name.
285-
is_multi : bool, default False
286-
Is ``obj`` a :class:`MultiIndex` or not
285+
line_break_each_value : bool, default False
286+
If True, inserts a line break for each value of ``obj``.
287+
If False, only break lines when the a line of values gets wider
288+
than the display width
287289
288290
Returns
289291
-------
@@ -308,7 +310,11 @@ def format_object_summary(obj, formatter, is_justify=True, name=None,
308310
space2 = "\n " # space for the opening '['
309311

310312
n = len(obj)
311-
sep = ',' if not is_multi else (',\n ' + ' ' * len(name))
313+
if not line_break_each_value:
314+
sep = ','
315+
else:
316+
# If we want to align on each value, we need a different separator.
317+
sep = (',\n ' + ' ' * len(name))
312318
max_seq_items = get_option('display.max_seq_items') or n
313319

314320
# are we a truncated display
@@ -336,10 +342,10 @@ def best_len(values):
336342

337343
if n == 0:
338344
summary = '[]{}'.format(close)
339-
elif n == 1 and not is_multi:
345+
elif n == 1 and not line_break_each_value:
340346
first = formatter(obj[0])
341347
summary = '[{}]{}'.format(first, close)
342-
elif n == 2 and not is_multi:
348+
elif n == 2 and not line_break_each_value:
343349
first = formatter(obj[0])
344350
last = formatter(obj[-1])
345351
summary = '[{}, {}]{}'.format(first, last, close)
@@ -355,22 +361,31 @@ def best_len(values):
355361

356362
# adjust all values to max length if needed
357363
if is_justify:
358-
head, tail = _justify(head, tail, display_width, best_len,
359-
is_truncated, is_multi)
360-
if is_multi:
364+
if line_break_each_value:
365+
head, tail = _justify(head, tail)
366+
elif (is_truncated or not (len(', '.join(head)) < display_width and
367+
len(', '.join(tail)) < display_width)):
368+
max_length = max(best_len(head), best_len(tail))
369+
head = [x.rjust(max_length) for x in head]
370+
tail = [x.rjust(max_length) for x in tail]
371+
# If we are not truncated and we are only a single
372+
# line, then don't justify
373+
374+
if line_break_each_value:
375+
# truncate vertically if wider than max_space
361376
max_space = display_width - len(space2)
362377
item = tail[0]
363-
for i in reversed(range(1, len(item) + 1)):
364-
if len(_pprint_seq(item, max_seq_items=i)) < max_space:
378+
for max_items in reversed(range(1, len(item) + 1)):
379+
if len(_pprint_seq(item, max_seq_items=max_items)) < max_space:
365380
break
366-
head = [_pprint_seq(x, max_seq_items=i) for x in head]
367-
tail = [_pprint_seq(x, max_seq_items=i) for x in tail]
381+
head = [_pprint_seq(x, max_seq_items=max_items) for x in head]
382+
tail = [_pprint_seq(x, max_seq_items=max_items) for x in tail]
368383

369384
summary = ""
370385
line = space2
371386

372-
for i in range(len(head)):
373-
word = head[i] + sep + ' '
387+
for max_items in range(len(head)):
388+
word = head[max_items] + sep + ' '
374389
summary, line = _extend_line(summary, line, word,
375390
display_width, space2)
376391

@@ -379,8 +394,8 @@ def best_len(values):
379394
summary += line.rstrip() + space2 + '...'
380395
line = space2
381396

382-
for i in range(len(tail) - 1):
383-
word = tail[i] + sep + ' '
397+
for max_items in range(len(tail) - 1):
398+
word = tail[max_items] + sep + ' '
384399
summary, line = _extend_line(summary, line, word,
385400
display_width, space2)
386401

@@ -394,7 +409,7 @@ def best_len(values):
394409
close = ']' + close.rstrip(' ')
395410
summary += close
396411

397-
if len(summary) > (display_width) or is_multi:
412+
if len(summary) > (display_width) or line_break_each_value:
398413
summary += space1
399414
else: # one row
400415
summary += ' '
@@ -405,50 +420,41 @@ def best_len(values):
405420
return summary
406421

407422

408-
def _justify(head, tail, display_width, best_len,
409-
is_truncated=False, is_multi=False):
410-
"""
411-
Justify each item in head and tail, so they align properly.
423+
def _justify(head, tail):
412424
"""
413-
if is_multi:
414-
max_length = _max_level_item_length(head + tail)
415-
head = [tuple(x.rjust(max_len) for x, max_len in zip(seq, max_length))
416-
for seq in head]
417-
tail = [tuple(x.rjust(max_len) for x, max_len in zip(seq, max_length))
418-
for seq in tail]
419-
elif (is_truncated or not (len(', '.join(head)) < display_width and
420-
len(', '.join(tail)) < display_width)):
421-
max_length = max(best_len(head), best_len(tail))
422-
head = [x.rjust(max_length) for x in head]
423-
tail = [x.rjust(max_length) for x in tail]
424-
425-
return head, tail
426-
427-
428-
def _max_level_item_length(seq):
429-
"""
430-
For each position for the sequences in ``seq``, find the largest length.
431-
432-
Used for justifying individual values in a :class:`pandas.MultiIndex`.
425+
Justify each item in each list-like in head and tail, so each item
426+
right-aligns when the two list-likes are stacked vertically.
433427
434428
Parameters
435429
----------
436-
seq : list-like of list-likes of strings
430+
head : list-like of list-likes of strings
431+
tail : list-like of list-likes of strings
437432
438433
Returns
439434
-------
440-
max_length : list of ints
435+
head : list of tuples of strings
436+
tail : list of tuples of strings
441437
442438
Examples
443439
--------
444-
>>> _max_level_item_length([['s', 'ab'], ['abc', 'a']])
445-
[3, 2]
440+
>>> _justify([['a', 'b']], [['abc', 'abcd']])
441+
([(' a', ' b')], [('abc', 'abcd')])
446442
"""
447-
max_length = [0] * len(seq[0])
448-
for inner_seq in seq:
443+
combined = head + tail # type: Sequence[Sequence[str]]
444+
445+
# For each position for the sequences in ``combined``,
446+
# find the length of the largest string.
447+
max_length = [0] * len(combined[0]) # type: List[int]
448+
for inner_seq in combined:
449449
length = [len(item) for item in inner_seq]
450450
max_length = [max(x, y) for x, y in zip(max_length, length)]
451-
return max_length
451+
452+
# justify each item in each list-like in head and tail using max_length
453+
head = [tuple(x.rjust(max_len) for x, max_len in zip(seq, max_length))
454+
for seq in head]
455+
tail = [tuple(x.rjust(max_len) for x, max_len in zip(seq, max_length))
456+
for seq in tail]
457+
return head, tail
452458

453459

454460
def format_object_attrs(obj):

pandas/tests/indexes/multi/conftest.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import numpy as np
44
import pytest
55

6+
import pandas as pd
67
from pandas import Index, MultiIndex
78

89

@@ -54,3 +55,28 @@ def holder():
5455
def compat_props():
5556
# a MultiIndex must have these properties associated with it
5657
return ['shape', 'ndim', 'size']
58+
59+
60+
@pytest.fixture
61+
def narrow_multi_index():
62+
"""
63+
Return a MultiIndex that is narrower than the display (<80 characters).
64+
"""
65+
n = 1000
66+
ci = pd.CategoricalIndex(list('a' * n) + (['abc'] * n))
67+
dti = pd.date_range('2000-01-01', freq='s', periods=n * 2)
68+
return pd.MultiIndex.from_arrays([ci, ci.codes + 9, dti],
69+
names=['a', 'b', 'dti'])
70+
71+
72+
@pytest.fixture
73+
def wide_multi_index():
74+
"""
75+
Return a MultiIndex that is wider than the display (>80 characters).
76+
"""
77+
n = 1000
78+
ci = pd.CategoricalIndex(list('a' * n) + (['abc'] * n))
79+
dti = pd.date_range('2000-01-01', freq='s', periods=n * 2)
80+
levels = [ci, ci.codes + 9, dti, dti, dti]
81+
names = ['a', 'b', 'dti_1', 'dti_2', 'dti_3']
82+
return pd.MultiIndex.from_arrays(levels, names=names)

pandas/tests/indexes/multi/test_format.py

Lines changed: 19 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -58,31 +58,11 @@ def test_repr_with_unicode_data():
5858
assert "\\" not in repr(index) # we don't want unicode-escaped
5959

6060

61-
@pytest.mark.skip(reason="#22511 will remove this test")
62-
def test_repr_roundtrip():
63-
61+
def test_repr_roundtrip_raises():
6462
mi = MultiIndex.from_product([list('ab'), range(3)],
6563
names=['first', 'second'])
66-
str(mi)
67-
68-
tm.assert_index_equal(eval(repr(mi)), mi, exact=True)
69-
70-
mi_u = MultiIndex.from_product(
71-
[list('ab'), range(3)], names=['first', 'second'])
72-
result = eval(repr(mi_u))
73-
tm.assert_index_equal(result, mi_u, exact=True)
74-
75-
# formatting
76-
str(mi)
77-
78-
# long format
79-
mi = MultiIndex.from_product([list('abcdefg'), range(10)],
80-
names=['first', 'second'])
81-
82-
tm.assert_index_equal(eval(repr(mi)), mi, exact=True)
83-
84-
result = eval(repr(mi_u))
85-
tm.assert_index_equal(result, mi_u, exact=True)
64+
with pytest.raises(TypeError):
65+
eval(repr(mi))
8666

8767

8868
def test_unicode_string_with_unicode():
@@ -107,25 +87,16 @@ def test_repr_max_seq_item_setting(idx):
10787

10888
class TestRepr(object):
10989

110-
def setup_class(self):
111-
n = 1000
112-
ci = pd.CategoricalIndex(list('a' * n) + (['abc'] * n))
113-
dti = pd.date_range('2000-01-01', freq='s', periods=n * 2)
114-
self.narrow_mi = pd.MultiIndex.from_arrays([ci, ci.codes + 9, dti],
115-
names=['a', 'b', 'dti'])
116-
117-
levels = [ci, ci.codes + 9, dti, dti, dti]
118-
names = ['a', 'b', 'dti_1', 'dti_2', 'dti_3']
119-
self.wide_mi = pd.MultiIndex.from_arrays(levels, names=names)
120-
12190
def test_repr(self, idx):
12291
result = idx[:1].__repr__()
123-
expected = """MultiIndex([('foo', 'one')],
92+
expected = """\
93+
MultiIndex([('foo', 'one')],
12494
dtype='object', names=['first', 'second'])"""
12595
assert result == expected
12696

12797
result = idx.__repr__()
128-
expected = """MultiIndex([('foo', 'one'),
98+
expected = """\
99+
MultiIndex([('foo', 'one'),
129100
('foo', 'two'),
130101
('bar', 'one'),
131102
('baz', 'two'),
@@ -136,22 +107,24 @@ def test_repr(self, idx):
136107

137108
with pd.option_context('display.max_seq_items', 5):
138109
result = idx.__repr__()
139-
expected = """MultiIndex([('foo', 'one'),
110+
expected = """\
111+
MultiIndex([('foo', 'one'),
140112
('foo', 'two'),
141113
...
142114
('qux', 'one'),
143115
('qux', 'two')],
144116
dtype='object', names=['first', 'second'], length=6)"""
145117
assert result == expected
146118

147-
def test_rjust(self):
148-
result = self.narrow_mi[:1].__repr__()
119+
def test_rjust(self, narrow_multi_index):
120+
mi = narrow_multi_index
121+
result = mi[:1].__repr__()
149122
expected = """\
150123
MultiIndex([('a', 9, '2000-01-01 00:00:00')],
151124
dtype='object', names=['a', 'b', 'dti'])"""
152125
assert result == expected
153126

154-
result = self.narrow_mi[::500].__repr__()
127+
result = mi[::500].__repr__()
155128
expected = """\
156129
MultiIndex([( 'a', 9, '2000-01-01 00:00:00'),
157130
( 'a', 9, '2000-01-01 00:08:20'),
@@ -160,7 +133,7 @@ def test_rjust(self):
160133
dtype='object', names=['a', 'b', 'dti'])"""
161134
assert result == expected
162135

163-
result = self.narrow_mi.__repr__()
136+
result = mi.__repr__()
164137
expected = """\
165138
MultiIndex([( 'a', 9, '2000-01-01 00:00:00'),
166139
( 'a', 9, '2000-01-01 00:00:01'),
@@ -186,13 +159,14 @@ def test_rjust(self):
186159
dtype='object', names=['a', 'b', 'dti'], length=2000)"""
187160
assert result == expected
188161

189-
def test_tuple_width(self):
190-
result = self.wide_mi[:1].__repr__()
162+
def test_tuple_width(self, wide_multi_index):
163+
mi = wide_multi_index
164+
result = mi[:1].__repr__()
191165
expected = """MultiIndex([('a', 9, '2000-01-01 00:00:00', '2000-01-01 00:00:00', ...)],
192166
dtype='object', names=['a', 'b', 'dti_1', 'dti_2', 'dti_3'])"""
193167
assert result == expected
194168

195-
result = self.wide_mi[:10].__repr__()
169+
result = mi[:10].__repr__()
196170
expected = """\
197171
MultiIndex([('a', 9, '2000-01-01 00:00:00', '2000-01-01 00:00:00', ...),
198172
('a', 9, '2000-01-01 00:00:01', '2000-01-01 00:00:01', ...),
@@ -207,7 +181,7 @@ def test_tuple_width(self):
207181
dtype='object', names=['a', 'b', 'dti_1', 'dti_2', 'dti_3'])"""
208182
assert result == expected
209183

210-
result = self.wide_mi.__repr__()
184+
result = mi.__repr__()
211185
expected = """\
212186
MultiIndex([( 'a', 9, '2000-01-01 00:00:00', '2000-01-01 00:00:00', ...),
213187
( 'a', 9, '2000-01-01 00:00:01', '2000-01-01 00:00:01', ...),

0 commit comments

Comments
 (0)