BUG: fix to_latex() when using MultiIndex with NaN in (#14249) (v2)
State: closed

This is an improved version of #19910 cleaning up MultiIndex handling in the process.

- [x] closes #14249 
- [x] tests added / passed
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry

~~I quickly looked at some other issues that this didn't originally aim to address and found that this PR closes #19981 and #18326 too, but I didn't add a tests or whats new entries for those (yet).~~

Edit: tests and whatsnew entries added for:

- [x] closes #19981   
- [x] closes #18326
- [x] closes #18669 

Labels:
name: Bug
name: IO LaTeX
description: to_latex

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

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

```diff
@@            Coverage Diff             @@
##           master   #20797      +/-   ##
==========================================
- Coverage   91.85%   91.77%   -0.08%     
==========================================
  Files         153      153              
  Lines       49310    49257      -53     
==========================================
- Hits        45292    45207      -85     
- Misses       4018     4050      +32
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `90.16% <100%> (-0.08%)` | :arrow_down: |
| #single | `41.87% <0%> (-0.02%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/20797?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/io/formats/latex.py](https://codecov.io/gh/pandas-dev/pandas/pull/20797/diff?src=pr&el=tree#diff-cGFuZGFzL2lvL2Zvcm1hdHMvbGF0ZXgucHk=) | `100% <100%> (ø)` | :arrow_up: |
| [pandas/plotting/\_converter.py](https://codecov.io/gh/pandas-dev/pandas/pull/20797/diff?src=pr&el=tree#diff-cGFuZGFzL3Bsb3R0aW5nL19jb252ZXJ0ZXIucHk=) | `63.2% <0%> (-3.61%)` | :arrow_down: |
| [pandas/core/nanops.py](https://codecov.io/gh/pandas-dev/pandas/pull/20797/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvbmFub3BzLnB5) | `95.13% <0%> (-1.17%)` | :arrow_down: |
| [pandas/core/resample.py](https://codecov.io/gh/pandas-dev/pandas/pull/20797/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvcmVzYW1wbGUucHk=) | `96.06% <0%> (-0.37%)` | :arrow_down: |
| [pandas/core/indexes/base.py](https://codecov.io/gh/pandas-dev/pandas/pull/20797/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9iYXNlLnB5) | `96.63% <0%> (-0.06%)` | :arrow_down: |
| [pandas/core/common.py](https://codecov.io/gh/pandas-dev/pandas/pull/20797/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvY29tbW9uLnB5) | `92.01% <0%> (-0.03%)` | :arrow_down: |
| [pandas/core/indexes/datetimelike.py](https://codecov.io/gh/pandas-dev/pandas/pull/20797/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9kYXRldGltZWxpa2UucHk=) | `96.7% <0%> (-0.02%)` | :arrow_down: |
| [pandas/core/arrays/base.py](https://codecov.io/gh/pandas-dev/pandas/pull/20797/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvYXJyYXlzL2Jhc2UucHk=) | `84.14% <0%> (ø)` | :arrow_up: |
| [pandas/core/frame.py](https://codecov.io/gh/pandas-dev/pandas/pull/20797/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZnJhbWUucHk=) | `97.17% <0%> (ø)` | :arrow_up: |
| [pandas/core/series.py](https://codecov.io/gh/pandas-dev/pandas/pull/20797/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvc2VyaWVzLnB5) | `93.9% <0%> (ø)` | :arrow_up: |
| ... and [5 more](https://codecov.io/gh/pandas-dev/pandas/pull/20797/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/20797?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/20797?src=pr&el=footer). Last update [31e77b0...2a05733](https://codecov.io/gh/pandas-dev/pandas/pull/20797?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

The patch looks good, and thanks for investigating the related issues. If you could also add tests for them too, it would be great.
lgtm. @toobaz merge when satisfied.
@tomneep I just found out this PR fixes #18669 too: the original testcase cannot be reproduced (because of #18882 ), but the following can:

``` python
In [2]: mi = pd.MultiIndex.from_product([[1, 2]])

In [3]: df = pd.DataFrame(-1, index=mi, columns=range(4))

In [4]: df.index.names = ['']

In [5]: print(df.to_latex())
\begin{tabular}{lrrrr}
\toprule
  &  0 &  1 &  2 &  3 \\
\midrule
1 &    &    &    &    \\
2 & -1 & -1 & -1 & -1 \\
\bottomrule
\end{tabular}
```

Would you mind adding a test/whatsnew mention for this? Then I think we're ready to go.
@toobaz Thanks for spotting that one too. I've added a test and whatsnew entry. The output is slightly different than your expected output in #18669 as there is an extra empty row for the index name (which is empty) but I've checked and this is consistent with what `to_string()` and `to_html()` do.
@tomneep thanks!