Skip to content

Commit 5dbe1af

Browse files
committed
BUG: fix to_latex() when using MultiIndex with NaN in (#14249)
1 parent 1e4c50a commit 5dbe1af

File tree

3 files changed

+31
-6
lines changed

3 files changed

+31
-6
lines changed

doc/source/whatsnew/v0.23.0.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,7 @@ I/O
864864
- Bug in :func:`read_csv` where missing values were not being handled properly when ``keep_default_na=False`` with dictionary ``na_values`` (:issue:`19227`)
865865
- Bug in :func:`read_sas` where a file with 0 variables gave an ``AttributeError`` incorrectly. Now it gives an ``EmptyDataError`` (:issue:`18184`)
866866
- Bug in :func:`DataFrame.to_latex()` where pairs of braces meant to serve as invisible placeholders were escaped (:issue:`18667`)
867+
- Bug in :func:`DataFrame.to_latex()` where a ``NaN`` in a ``MultiIndex`` would cause an ``IndexError`` or incorrect output (:issue:`14249`)
867868
- Bug in :func:`read_json` where large numeric values were causing an ``OverflowError`` (:issue:`18842`)
868869
- Bug in :func:`DataFrame.to_parquet` where an exception was raised if the write destination is S3 (:issue:`19134`)
869870
- :class:`Interval` now supported in :func:`DataFrame.to_excel` for all Excel file types (:issue:`19242`)

pandas/io/formats/format.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,8 @@ def get_col_type(dtype):
912912
previous_lev3 = None
913913
for i, lev in enumerate(self.frame.index.levels):
914914
lev2 = lev.format()
915-
blank = ' ' * len(lev2[0])
915+
blank = (' ' * len(lev2[0]) if lev2 else
916+
' ' * len(self.fmt.na_rep))
916917
# display column names in last index-column
917918
if cname and i == lastcol:
918919
lev3 = [x if x else '{}' for x in self.frame.columns.names]
@@ -922,15 +923,16 @@ def get_col_type(dtype):
922923
lev3.append(lev.name)
923924
current_idx_val = None
924925
for level_idx in self.frame.index.labels[i]:
926+
idx_val = (lev2[level_idx] if level_idx >= 0 else
927+
self.fmt.na_rep)
925928
if ((previous_lev3 is None or
926-
previous_lev3[len(lev3)].isspace()) and
927-
lev2[level_idx] == current_idx_val):
929+
previous_lev3[len(lev3)].isspace()) and
930+
idx_val == current_idx_val):
928931
# same index as above row and left index was the same
929932
lev3.append(blank)
930933
else:
931-
# different value than above or left index different
932-
lev3.append(lev2[level_idx])
933-
current_idx_val = lev2[level_idx]
934+
lev3.append(idx_val)
935+
current_idx_val = idx_val
934936
strcols.insert(i, lev3)
935937
previous_lev3 = lev3
936938

pandas/tests/io/formats/test_to_latex.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,3 +621,25 @@ def test_to_latex_multiindex_names(self, name0, name1, axes):
621621
\end{tabular}
622622
""" % tuple(list(col_names) + [idx_names_row])
623623
assert observed == expected
624+
625+
@pytest.mark.parametrize('one_row', [True, False])
626+
def test_to_latex_multiindex_nans(self, one_row):
627+
# GH 14249
628+
df = pd.DataFrame({'a': [None, 1], 'b': [2, 3], 'c': [4, 5]})
629+
if one_row:
630+
df = df.iloc[[0]]
631+
observed = df.set_index(['a', 'b']).to_latex()
632+
expected = r"""\begin{tabular}{llr}
633+
\toprule
634+
& & c \\
635+
a & b & \\
636+
\midrule
637+
NaN & 2 & 4 \\
638+
"""
639+
if not one_row:
640+
expected += r"""1.0 & 3 & 5 \\
641+
"""
642+
expected += r"""\bottomrule
643+
\end{tabular}
644+
"""
645+
assert observed == expected

0 commit comments

Comments
 (0)