Fix repr of DataFrame with IntervalIndex
State: closed

@TomAugspurger after the repr PR, the docs build catched an error: the repr of a DataFrame with an IntervalIndex started failing:

```
In [1]: df = pd.DataFrame({'A': [1, 2, 3, 4]}, 
   ...:                   index=pd.IntervalIndex.from_breaks([0, 1, 2, 3, 4]))                                                                                                  

In [2]: df                                                                                                                                                                      
Out[2]: ---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
~/miniconda3/envs/dev/lib/python3.5/site-packages/IPython/core/formatters.py in __call__(self, obj)
    700                 type_pprinters=self.type_printers,
    701                 deferred_pprinters=self.deferred_printers)
--> 702             printer.pretty(obj)
    703             printer.flush()
    704             return stream.getvalue()

~/miniconda3/envs/dev/lib/python3.5/site-packages/IPython/lib/pretty.py in pretty(self, obj)
    400                         if cls is not object \
    401                                 and callable(cls.__dict__.get('__repr__')):
--> 402                             return _repr_pprint(obj, self, cycle)
    403 
    404             return _default_pprint(obj, self, cycle)

~/miniconda3/envs/dev/lib/python3.5/site-packages/IPython/lib/pretty.py in _repr_pprint(obj, p, cycle)
    695     """A pprint that just redirects to the normal repr function."""
    696     # Find newlines and replace them with p.break_()
--> 697     output = repr(obj)
    698     for idx,output_line in enumerate(output.splitlines()):
    699         if idx:

~/scipy/pandas/pandas/core/base.py in __repr__(self)
     75         Yields Bytestring in Py2, Unicode String in py3.
     76         """
---> 77         return str(self)
     78 
     79 

~/scipy/pandas/pandas/core/base.py in __str__(self)
     54 
     55         if compat.PY3:
---> 56             return self.__unicode__()
     57         return self.__bytes__()
     58 

~/scipy/pandas/pandas/core/frame.py in __unicode__(self)
    626             width = None
    627         self.to_string(buf=buf, max_rows=max_rows, max_cols=max_cols,
--> 628                        line_width=width, show_dimensions=show_dimensions)
    629 
    630         return buf.getvalue()

~/scipy/pandas/pandas/core/frame.py in to_string(self, buf, columns, col_space, header, index, na_rep, formatters, float_format, sparsify, index_names, justify, max_rows, max_cols, show_dimensions, decimal, line_width)
    707                                            decimal=decimal,
    708                                            line_width=line_width)
--> 709         formatter.to_string()
    710 
    711         if buf is None:

~/scipy/pandas/pandas/io/formats/format.py in to_string(self)
    601         else:
    602 
--> 603             strcols = self._to_str_columns()
    604             if self.line_width is None:  # no need to wrap around just print
    605                 # the whole frame

~/scipy/pandas/pandas/io/formats/format.py in _to_str_columns(self)
    510         # may include levels names also
    511 
--> 512         str_index = self._get_formatted_index(frame)
    513 
    514         if not is_list_like(self.header) and not self.header:

~/scipy/pandas/pandas/io/formats/format.py in _get_formatted_index(self, frame)
    807                                      names=show_index_names, formatter=fmt)
    808         else:
--> 809             fmt_index = [index.format(name=show_index_names, formatter=fmt)]
    810         fmt_index = [tuple(_make_fixed_width(list(x), justify='left',
    811                                              minimum=(self.col_space or 0),

~/scipy/pandas/pandas/core/indexes/base.py in format(self, name, formatter, **kwargs)
    993             return header + list(self.map(formatter))
    994 
--> 995         return self._format_with_header(header, **kwargs)
    996 
    997     def _format_with_header(self, header, na_rep='NaN', **kwargs):

~/scipy/pandas/pandas/core/indexes/interval.py in _format_with_header(self, header, **kwargs)
   1012 
   1013     def _format_with_header(self, header, **kwargs):
-> 1014         return header + list(self._format_native_types(**kwargs))
   1015 
   1016     def _format_native_types(self, na_rep='', quoting=None, **kwargs):

~/scipy/pandas/pandas/core/indexes/interval.py in _format_native_types(self, na_rep, quoting, **kwargs)
   1016     def _format_native_types(self, na_rep='', quoting=None, **kwargs):
   1017         """ actually format my specific types """
-> 1018         from pandas.io.formats.format import IntervalArrayFormatter
   1019         return IntervalArrayFormatter(values=self,
   1020                                       na_rep=na_rep,

ImportError: cannot import name 'IntervalArrayFormatter'
```

What is in this PR "fixes" the immediate error, but, I see a difference with what was before:

On 0.23.4:
```
In [2]: i = pd.io.formats.format.IntervalArrayFormatter(pd.interval_range(1, 5))

In [3]: i.get_result()
Out[3]: ['(1, 2]', '(2, 3]', '(3, 4]', '(4, 5]']
```

On master:
```
In [22]: i = pd.io.formats.format.ExtensionArrayFormatter(pd.interval_range(1, 5))                                                                                              

In [23]: i.get_result()                                                                                                                                                         
Out[23]: [' (1, 2]', ' (2, 3]', ' (3, 4]', ' (4, 5]']
```

So there is now an extra space. So which means also the DataFrame repr starts with a space.

(still need to add tests, and it might this also breaks existing tests due to the whitespace change)


Labels:
name: Bug
name: Output-Formatting
description: __repr__ of pandas objects, to_string
name: Blocker
description: Blocking issue or pull request for an upcoming release
name: Interval
description: Interval data type

Comments:
Hello @jorisvandenbossche! Thanks for submitting the PR.

 - There are no PEP8 issues in the file [`pandas/core/indexes/interval.py`](https://github.com/jorisvandenbossche/pandas/blob/ec88f66c9daf8b2f3e0688886d4e803b9ebd1ea3/pandas/core/indexes/interval.py) !


# [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=h1) Report
> Merging [#24134](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/03134cb92ed61d073438daac7a7c2a0e094efede?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `0%`.

[![Impacted file tree graph](https://codecov.io/gh/pandas-dev/pandas/pull/24134/graphs/tree.svg?width=650&token=eZ4WkYLtcO&height=150&src=pr)](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master   #24134   +/-   ##
=======================================
  Coverage    92.2%    92.2%           
=======================================
  Files         162      162           
  Lines       51701    51701           
=======================================
  Hits        47671    47671           
  Misses       4030     4030
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `90.6% <0%> (ø)` | :arrow_up: |
| #single | `43.02% <0%> (ø)` | :arrow_up: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/core/indexes/interval.py](https://codecov.io/gh/pandas-dev/pandas/pull/24134/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9pbnRlcnZhbC5weQ==) | `94.73% <0%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=footer). Last update [03134cb...ec88f66](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

# [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=h1) Report
> Merging [#24134](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/c0b9eea7067d6089fabdc31ac2ed4cc16d063568?src=pr&el=desc) will **increase** coverage by `0.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/pandas-dev/pandas/pull/24134/graphs/tree.svg?width=650&token=eZ4WkYLtcO&height=150&src=pr)](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master   #24134      +/-   ##
==========================================
+ Coverage    92.2%   92.22%   +0.01%     
==========================================
  Files         162      162              
  Lines       51700    51769      +69     
==========================================
+ Hits        47669    47742      +73     
+ Misses       4031     4027       -4
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `90.62% <100%> (+0.01%)` | :arrow_up: |
| #single | `43.01% <70%> (-0.02%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/io/formats/format.py](https://codecov.io/gh/pandas-dev/pandas/pull/24134/diff?src=pr&el=tree#diff-cGFuZGFzL2lvL2Zvcm1hdHMvZm9ybWF0LnB5) | `97.97% <100%> (+0.01%)` | :arrow_up: |
| [pandas/core/indexes/interval.py](https://codecov.io/gh/pandas-dev/pandas/pull/24134/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9pbnRlcnZhbC5weQ==) | `95.26% <100%> (+0.52%)` | :arrow_up: |
| [pandas/core/arrays/categorical.py](https://codecov.io/gh/pandas-dev/pandas/pull/24134/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvYXJyYXlzL2NhdGVnb3JpY2FsLnB5) | `95.3% <0%> (-0.1%)` | :arrow_down: |
| [pandas/core/frame.py](https://codecov.io/gh/pandas-dev/pandas/pull/24134/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZnJhbWUucHk=) | `96.91% <0%> (ø)` | :arrow_up: |
| [pandas/core/indexes/base.py](https://codecov.io/gh/pandas-dev/pandas/pull/24134/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9iYXNlLnB5) | `96.27% <0%> (ø)` | :arrow_up: |
| [pandas/core/indexes/category.py](https://codecov.io/gh/pandas-dev/pandas/pull/24134/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9jYXRlZ29yeS5weQ==) | `97.9% <0%> (ø)` | :arrow_up: |
| [pandas/core/arrays/datetimes.py](https://codecov.io/gh/pandas-dev/pandas/pull/24134/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvYXJyYXlzL2RhdGV0aW1lcy5weQ==) | `98.56% <0%> (ø)` | :arrow_up: |
| [pandas/core/generic.py](https://codecov.io/gh/pandas-dev/pandas/pull/24134/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZ2VuZXJpYy5weQ==) | `96.65% <0%> (ø)` | :arrow_up: |
| [pandas/io/pytables.py](https://codecov.io/gh/pandas-dev/pandas/pull/24134/diff?src=pr&el=tree#diff-cGFuZGFzL2lvL3B5dGFibGVzLnB5) | `92.31% <0%> (ø)` | :arrow_up: |
| [pandas/core/internals/managers.py](https://codecov.io/gh/pandas-dev/pandas/pull/24134/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW50ZXJuYWxzL21hbmFnZXJzLnB5) | `95.93% <0%> (+0.01%)` | :arrow_up: |
| ... and [6 more](https://codecov.io/gh/pandas-dev/pandas/pull/24134/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=footer). Last update [c0b9eea...f95ae76](https://codecov.io/gh/pandas-dev/pandas/pull/24134?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

I'll look into this later today.

It seems that we're now hitting the else in https://github.com/pandas-dev/pandas/blob/c91115172ce1e7324ff9422cb38f520496df2573/pandas/io/formats/format.py#L933-L939.
Updated to pass through a `leading_space` argument and tests for the formatters that we removed.
> this is happing because IntervalIndex and Series[Interval] are now both using GenericArrayFormatter to format the values. Series need a leading space, but indexes don't. 

I don't really know the formatting code, but would it make sense to move this "adding a leading space or not" to the code that actually combines the list of strings into the actual repr? As it is only when concatting them with ',' in the middle for the Index repr that you need those spaces? 
(just wondering, not for this PR, but might be an idea to clean this up)
I'm not too familiar with it either, but that seems sensible to me. It'd be
nice to focus the array formatter on formatting
the values, and a higher-level formatter for joining together those
formatted arrays.

On Mon, Dec 10, 2018 at 6:02 PM Joris Van den Bossche <
notifications@github.com> wrote:

> this is happing because IntervalIndex and Series[Interval] are now both
> using GenericArrayFormatter to format the values. Series need a leading
> space, but indexes don't.
>
> I don't really know the formatting code, but would it make sense to move
> this "adding a leading space or not" to the code that actually combines the
> list of strings into the actual repr? As it is only when concatting them
> with ',' in the middle for the Index repr that you need those spaces?
> (just wondering, not for this PR, but might be an idea to clean this up)
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/pandas-dev/pandas/pull/24134#issuecomment-446021863>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ABQHIrYCCgEJd4vD1B22GuQBggTQqy6xks5u3vYhgaJpZM4ZHaSi>
> .
>

ok, merging this though @TomAugspurger can you open an issue on https://github.com/pandas-dev/pandas/pull/24134#issuecomment-446054175 which sounds good.