From e260f58efa30fbfc3271005e3adefe6868404921 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 29 Sep 2020 01:52:31 +0700 Subject: [PATCH 1/4] REF: arrange tests by classes --- pandas/tests/io/formats/test_to_latex.py | 1000 +++++++++++----------- 1 file changed, 507 insertions(+), 493 deletions(-) diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 7a0d305758802..afb1e19820343 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -27,65 +27,13 @@ def _dedent(string): return dedent(string).lstrip() -class TestToLatex: - @pytest.fixture - def df_short(self): - """Short dataframe for testing table/tabular/longtable LaTeX env.""" - return DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - - @pytest.fixture - def caption_table(self): - """Caption for table/tabular LaTeX environment.""" - return "a table in a \\texttt{table/tabular} environment" - - @pytest.fixture - def label_table(self): - """Label for table/tabular LaTeX environment.""" - return "tab:table_tabular" - - @pytest.fixture - def caption_longtable(self): - """Caption for longtable LaTeX environment.""" - return "a table in a \\texttt{longtable} environment" - - @pytest.fixture - def label_longtable(self): - """Label for longtable LaTeX environment.""" - return "tab:longtable" - - @pytest.fixture - def multiindex_frame(self): - """Multiindex dataframe for testing multirow LaTeX macros.""" - yield DataFrame.from_dict( - { - ("c1", 0): pd.Series({x: x for x in range(4)}), - ("c1", 1): pd.Series({x: x + 4 for x in range(4)}), - ("c2", 0): pd.Series({x: x for x in range(4)}), - ("c2", 1): pd.Series({x: x + 4 for x in range(4)}), - ("c3", 0): pd.Series({x: x for x in range(4)}), - } - ).T - - @pytest.fixture - def multicolumn_frame(self): - """Multicolumn dataframe for testing multicolumn LaTeX macros.""" - yield pd.DataFrame( - { - ("c1", 0): {x: x for x in range(5)}, - ("c1", 1): {x: x + 5 for x in range(5)}, - ("c2", 0): {x: x for x in range(5)}, - ("c2", 1): {x: x + 5 for x in range(5)}, - ("c3", 0): {x: x for x in range(5)}, - } - ) +@pytest.fixture +def df_short(): + """Short dataframe for testing table/tabular/longtable LaTeX env.""" + return DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - @pytest.fixture - def df_with_symbols(self): - """Dataframe with special characters for testing chars escaping.""" - a = "a" - b = "b" - yield DataFrame({"co$e^x$": {a: "a", b: "b"}, "co^l1": {a: "a", b: "b"}}) +class TestToLatex: def test_to_latex_to_file(self, float_frame): with tm.ensure_clean("test.tex") as path: float_frame.to_latex(path) @@ -188,334 +136,185 @@ def test_to_latex_empty_tabular(self): ) assert result == expected - def test_to_latex_empty_longtable(self): - df = DataFrame() - result = df.to_latex(longtable=True) + def test_to_latex_no_header_with_index(self): + # GH 7124 + df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) + result = df.to_latex(header=False) expected = _dedent( r""" - \begin{longtable}{l} + \begin{tabular}{lrl} \toprule - Empty DataFrame - Columns: Index([], dtype='object') - Index: Index([], dtype='object') \\ - \end{longtable} + 0 & 1 & b1 \\ + 1 & 2 & b2 \\ + \bottomrule + \end{tabular} """ ) assert result == expected - def test_to_latex_with_formatters(self): - df = DataFrame( - { - "datetime64": [ - datetime(2016, 1, 1), - datetime(2016, 2, 5), - datetime(2016, 3, 3), - ], - "float": [1.0, 2.0, 3.0], - "int": [1, 2, 3], - "object": [(1, 2), True, False], - } - ) - - formatters = { - "datetime64": lambda x: x.strftime("%Y-%m"), - "float": lambda x: f"[{x: 4.1f}]", - "int": lambda x: f"0x{x:x}", - "object": lambda x: f"-{x!s}-", - "__index__": lambda x: f"index: {x}", - } - result = df.to_latex(formatters=dict(formatters)) - + def test_to_latex_no_header_without_index(self): + # GH 7124 + df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) + result = df.to_latex(index=False, header=False) expected = _dedent( r""" - \begin{tabular}{llrrl} + \begin{tabular}{rl} \toprule - {} & datetime64 & float & int & object \\ - \midrule - index: 0 & 2016-01 & [ 1.0] & 0x1 & -(1, 2)- \\ - index: 1 & 2016-02 & [ 2.0] & 0x2 & -True- \\ - index: 2 & 2016-03 & [ 3.0] & 0x3 & -False- \\ + 1 & b1 \\ + 2 & b2 \\ \bottomrule \end{tabular} """ ) assert result == expected - def test_to_latex_multiindex_column_tabular(self): - df = DataFrame({("x", "y"): ["a"]}) - result = df.to_latex() + def test_to_latex_specified_header_with_index(self): + # GH 7124 + df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) + result = df.to_latex(header=["AA", "BB"]) expected = _dedent( r""" - \begin{tabular}{ll} + \begin{tabular}{lrl} \toprule - {} & x \\ - {} & y \\ + {} & AA & BB \\ \midrule - 0 & a \\ + 0 & 1 & b1 \\ + 1 & 2 & b2 \\ \bottomrule \end{tabular} """ ) assert result == expected - def test_to_latex_multiindex_small_tabular(self): - df = DataFrame({("x", "y"): ["a"]}) - result = df.T.to_latex() + def test_to_latex_specified_header_without_index(self): + # GH 7124 + df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) + result = df.to_latex(header=["AA", "BB"], index=False) expected = _dedent( r""" - \begin{tabular}{lll} + \begin{tabular}{rl} \toprule - & & 0 \\ + AA & BB \\ \midrule - x & y & a \\ + 1 & b1 \\ + 2 & b2 \\ \bottomrule \end{tabular} """ ) assert result == expected - def test_to_latex_multiindex_tabular(self, multiindex_frame): - result = multiindex_frame.to_latex() + def test_to_latex_number_of_items_in_header_missmatch_raises(self): + # GH 7124 + df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) + msg = "Writing 2 cols but got 1 aliases" + with pytest.raises(ValueError, match=msg): + df.to_latex(header=["A"]) + + def test_to_latex_decimal(self): + # GH 12031 + df = DataFrame({"a": [1.0, 2.1], "b": ["b1", "b2"]}) + result = df.to_latex(decimal=",") expected = _dedent( r""" - \begin{tabular}{llrrrr} + \begin{tabular}{lrl} \toprule - & & 0 & 1 & 2 & 3 \\ + {} & a & b \\ \midrule - c1 & 0 & 0 & 1 & 2 & 3 \\ - & 1 & 4 & 5 & 6 & 7 \\ - c2 & 0 & 0 & 1 & 2 & 3 \\ - & 1 & 4 & 5 & 6 & 7 \\ - c3 & 0 & 0 & 1 & 2 & 3 \\ + 0 & 1,0 & b1 \\ + 1 & 2,1 & b2 \\ \bottomrule \end{tabular} """ ) assert result == expected - def test_to_latex_multicolumn_tabular(self, multiindex_frame): - # GH 14184 - df = multiindex_frame.T - df.columns.names = ["a", "b"] - result = df.to_latex() + def test_to_latex_series(self): + s = Series(["a", "b", "c"]) + result = s.to_latex() expected = _dedent( r""" - \begin{tabular}{lrrrrr} + \begin{tabular}{ll} \toprule - a & \multicolumn{2}{l}{c1} & \multicolumn{2}{l}{c2} & c3 \\ - b & 0 & 1 & 0 & 1 & 0 \\ + {} & 0 \\ \midrule - 0 & 0 & 4 & 0 & 4 & 0 \\ - 1 & 1 & 5 & 1 & 5 & 1 \\ - 2 & 2 & 6 & 2 & 6 & 2 \\ - 3 & 3 & 7 & 3 & 7 & 3 \\ + 0 & a \\ + 1 & b \\ + 2 & c \\ \bottomrule \end{tabular} """ ) assert result == expected - def test_to_latex_index_has_name_tabular(self): - # GH 10660 - df = pd.DataFrame({"a": [0, 0, 1, 1], "b": list("abab"), "c": [1, 2, 3, 4]}) - result = df.set_index(["a", "b"]).to_latex() + def test_to_latex_bold_rows(self): + # GH 16707 + df = pd.DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) + result = df.to_latex(bold_rows=True) expected = _dedent( r""" - \begin{tabular}{llr} + \begin{tabular}{lrl} \toprule - & & c \\ - a & b & \\ + {} & a & b \\ \midrule - 0 & a & 1 \\ - & b & 2 \\ - 1 & a & 3 \\ - & b & 4 \\ + \textbf{0} & 1 & b1 \\ + \textbf{1} & 2 & b2 \\ \bottomrule \end{tabular} """ ) assert result == expected - def test_to_latex_groupby_tabular(self): - # GH 10660 - df = pd.DataFrame({"a": [0, 0, 1, 1], "b": list("abab"), "c": [1, 2, 3, 4]}) - result = df.groupby("a").describe().to_latex() + def test_to_latex_no_bold_rows(self): + # GH 16707 + df = pd.DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) + result = df.to_latex(bold_rows=False) expected = _dedent( r""" - \begin{tabular}{lrrrrrrrr} + \begin{tabular}{lrl} \toprule - {} & \multicolumn{8}{l}{c} \\ - {} & count & mean & std & min & 25\% & 50\% & 75\% & max \\ - a & & & & & & & & \\ + {} & a & b \\ \midrule - 0 & 2.0 & 1.5 & 0.707107 & 1.0 & 1.25 & 1.5 & 1.75 & 2.0 \\ - 1 & 2.0 & 3.5 & 0.707107 & 3.0 & 3.25 & 3.5 & 3.75 & 4.0 \\ + 0 & 1 & b1 \\ + 1 & 2 & b2 \\ \bottomrule \end{tabular} """ ) assert result == expected - def test_to_latex_multiindex_dupe_level(self): - # see gh-14484 - # - # If an index is repeated in subsequent rows, it should be - # replaced with a blank in the created table. This should - # ONLY happen if all higher order indices (to the left) are - # equal too. In this test, 'c' has to be printed both times - # because the higher order index 'A' != 'B'. - df = pd.DataFrame( - index=pd.MultiIndex.from_tuples([("A", "c"), ("B", "c")]), columns=["col"] - ) - result = df.to_latex() + def test_to_latex_midrule_location(self): + # GH 18326 + df = pd.DataFrame({"a": [1, 2]}) + df.index.name = "foo" + result = df.to_latex(index_names=False) expected = _dedent( r""" - \begin{tabular}{lll} + \begin{tabular}{lr} \toprule - & & col \\ + {} & a \\ \midrule - A & c & NaN \\ - B & c & NaN \\ + 0 & 1 \\ + 1 & 2 \\ \bottomrule \end{tabular} """ ) assert result == expected - def test_to_latex_multicolumn_default(self, multicolumn_frame): - result = multicolumn_frame.to_latex() + +class TestToLatexLongtable: + def test_to_latex_empty_longtable(self): + df = DataFrame() + result = df.to_latex(longtable=True) expected = _dedent( r""" - \begin{tabular}{lrrrrr} + \begin{longtable}{l} \toprule - {} & \multicolumn{2}{l}{c1} & \multicolumn{2}{l}{c2} & c3 \\ - {} & 0 & 1 & 0 & 1 & 0 \\ - \midrule - 0 & 0 & 5 & 0 & 5 & 0 \\ - 1 & 1 & 6 & 1 & 6 & 1 \\ - 2 & 2 & 7 & 2 & 7 & 2 \\ - 3 & 3 & 8 & 3 & 8 & 3 \\ - 4 & 4 & 9 & 4 & 9 & 4 \\ - \bottomrule - \end{tabular} - """ - ) - assert result == expected - - def test_to_latex_multicolumn_false(self, multicolumn_frame): - result = multicolumn_frame.to_latex(multicolumn=False) - expected = _dedent( - r""" - \begin{tabular}{lrrrrr} - \toprule - {} & c1 & & c2 & & c3 \\ - {} & 0 & 1 & 0 & 1 & 0 \\ - \midrule - 0 & 0 & 5 & 0 & 5 & 0 \\ - 1 & 1 & 6 & 1 & 6 & 1 \\ - 2 & 2 & 7 & 2 & 7 & 2 \\ - 3 & 3 & 8 & 3 & 8 & 3 \\ - 4 & 4 & 9 & 4 & 9 & 4 \\ - \bottomrule - \end{tabular} - """ - ) - assert result == expected - - def test_to_latex_multirow_true(self, multicolumn_frame): - result = multicolumn_frame.T.to_latex(multirow=True) - expected = _dedent( - r""" - \begin{tabular}{llrrrrr} - \toprule - & & 0 & 1 & 2 & 3 & 4 \\ - \midrule - \multirow{2}{*}{c1} & 0 & 0 & 1 & 2 & 3 & 4 \\ - & 1 & 5 & 6 & 7 & 8 & 9 \\ - \cline{1-7} - \multirow{2}{*}{c2} & 0 & 0 & 1 & 2 & 3 & 4 \\ - & 1 & 5 & 6 & 7 & 8 & 9 \\ - \cline{1-7} - c3 & 0 & 0 & 1 & 2 & 3 & 4 \\ - \bottomrule - \end{tabular} - """ - ) - assert result == expected - - def test_to_latex_multicolumnrow_with_multicol_format(self, multicolumn_frame): - multicolumn_frame.index = multicolumn_frame.T.index - result = multicolumn_frame.T.to_latex( - multirow=True, - multicolumn=True, - multicolumn_format="c", - ) - expected = _dedent( - r""" - \begin{tabular}{llrrrrr} - \toprule - & & \multicolumn{2}{c}{c1} & \multicolumn{2}{c}{c2} & c3 \\ - & & 0 & 1 & 0 & 1 & 0 \\ - \midrule - \multirow{2}{*}{c1} & 0 & 0 & 1 & 2 & 3 & 4 \\ - & 1 & 5 & 6 & 7 & 8 & 9 \\ - \cline{1-7} - \multirow{2}{*}{c2} & 0 & 0 & 1 & 2 & 3 & 4 \\ - & 1 & 5 & 6 & 7 & 8 & 9 \\ - \cline{1-7} - c3 & 0 & 0 & 1 & 2 & 3 & 4 \\ - \bottomrule - \end{tabular} - """ - ) - assert result == expected - - def test_to_latex_escape_false(self, df_with_symbols): - result = df_with_symbols.to_latex(escape=False) - expected = _dedent( - r""" - \begin{tabular}{lll} - \toprule - {} & co$e^x$ & co^l1 \\ - \midrule - a & a & a \\ - b & b & b \\ - \bottomrule - \end{tabular} - """ - ) - assert result == expected - - def test_to_latex_escape_default(self, df_with_symbols): - result = df_with_symbols.to_latex() # default: escape=True - expected = _dedent( - r""" - \begin{tabular}{lll} - \toprule - {} & co\$e\textasciicircum x\$ & co\textasciicircum l1 \\ - \midrule - a & a & a \\ - b & b & b \\ - \bottomrule - \end{tabular} - """ - ) - assert result == expected - - def test_to_latex_special_escape(self): - df = DataFrame([r"a\b\c", r"^a^b^c", r"~a~b~c"]) - result = df.to_latex() - expected = _dedent( - r""" - \begin{tabular}{ll} - \toprule - {} & 0 \\ - \midrule - 0 & a\textbackslash b\textbackslash c \\ - 1 & \textasciicircum a\textasciicircum b\textasciicircum c \\ - 2 & \textasciitilde a\textasciitilde b\textasciitilde c \\ - \bottomrule - \end{tabular} + Empty DataFrame + Columns: Index([], dtype='object') + Index: Index([], dtype='object') \\ + \end{longtable} """ ) assert result == expected @@ -590,6 +389,28 @@ def test_to_latex_longtable_continued_on_next_page(self, df, expected_number): result = df.to_latex(index=False, longtable=True) assert fr"\multicolumn{{{expected_number}}}" in result + +class TestToLatexCaptionLabel: + @pytest.fixture + def caption_table(self): + """Caption for table/tabular LaTeX environment.""" + return "a table in a \\texttt{table/tabular} environment" + + @pytest.fixture + def label_table(self): + """Label for table/tabular LaTeX environment.""" + return "tab:table_tabular" + + @pytest.fixture + def caption_longtable(self): + """Caption for longtable LaTeX environment.""" + return "a table in a \\texttt{longtable} environment" + + @pytest.fixture + def label_longtable(self): + """Label for longtable LaTeX environment.""" + return "tab:longtable" + def test_to_latex_caption_only(self, df_short, caption_table): # GH 25436 result = df_short.to_latex(caption=caption_table) @@ -756,53 +577,61 @@ def test_to_latex_longtable_caption_and_label( ) assert result == expected - def test_to_latex_position(self): - the_position = "h" - df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - result = df.to_latex(position=the_position) + +class TestToLatexEscape: + @pytest.fixture + def df_with_symbols(self): + """Dataframe with special characters for testing chars escaping.""" + a = "a" + b = "b" + yield DataFrame({"co$e^x$": {a: "a", b: "b"}, "co^l1": {a: "a", b: "b"}}) + + def test_to_latex_escape_false(self, df_with_symbols): + result = df_with_symbols.to_latex(escape=False) expected = _dedent( r""" - \begin{table}[h] - \centering - \begin{tabular}{lrl} + \begin{tabular}{lll} \toprule - {} & a & b \\ + {} & co$e^x$ & co^l1 \\ \midrule - 0 & 1 & b1 \\ - 1 & 2 & b2 \\ + a & a & a \\ + b & b & b \\ \bottomrule \end{tabular} - \end{table} """ ) assert result == expected - def test_to_latex_longtable_position(self): - the_position = "t" - df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - result = df.to_latex(longtable=True, position=the_position) + def test_to_latex_escape_default(self, df_with_symbols): + result = df_with_symbols.to_latex() # default: escape=True expected = _dedent( r""" - \begin{longtable}[t]{lrl} + \begin{tabular}{lll} \toprule - {} & a & b \\ + {} & co\$e\textasciicircum x\$ & co\textasciicircum l1 \\ \midrule - \endfirsthead + a & a & a \\ + b & b & b \\ + \bottomrule + \end{tabular} + """ + ) + assert result == expected + def test_to_latex_special_escape(self): + df = DataFrame([r"a\b\c", r"^a^b^c", r"~a~b~c"]) + result = df.to_latex() + expected = _dedent( + r""" + \begin{tabular}{ll} \toprule - {} & a & b \\ - \midrule - \endhead - \midrule - \multicolumn{3}{r}{{Continued on next page}} \\ + {} & 0 \\ \midrule - \endfoot - + 0 & a\textbackslash b\textbackslash c \\ + 1 & \textasciicircum a\textasciicircum b\textasciicircum c \\ + 2 & \textasciitilde a\textasciitilde b\textasciitilde c \\ \bottomrule - \endlastfoot - 0 & 1 & b1 \\ - 1 & 2 & b2 \\ - \end{longtable} + \end{tabular} """ ) assert result == expected @@ -833,165 +662,441 @@ def test_to_latex_escape_special_chars(self): ) assert result == expected - def test_to_latex_no_header_with_index(self): + def test_to_latex_specified_header_special_chars_without_escape(self): # GH 7124 df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - result = df.to_latex(header=False) + result = df.to_latex(header=["$A$", "$B$"], escape=False) expected = _dedent( r""" \begin{tabular}{lrl} \toprule - 0 & 1 & b1 \\ - 1 & 2 & b2 \\ + {} & $A$ & $B$ \\ + \midrule + 0 & 1 & b1 \\ + 1 & 2 & b2 \\ \bottomrule \end{tabular} """ ) assert result == expected - def test_to_latex_no_header_without_index(self): - # GH 7124 - df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - result = df.to_latex(index=False, header=False) - expected = _dedent( - r""" - \begin{tabular}{rl} - \toprule - 1 & b1 \\ - 2 & b2 \\ - \bottomrule - \end{tabular} - """ - ) - assert result == expected - def test_to_latex_specified_header_with_index(self): - # GH 7124 +class TestToLatexPosition: + def test_to_latex_position(self): + the_position = "h" df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - result = df.to_latex(header=["AA", "BB"]) + result = df.to_latex(position=the_position) expected = _dedent( r""" + \begin{table}[h] + \centering \begin{tabular}{lrl} \toprule - {} & AA & BB \\ + {} & a & b \\ \midrule 0 & 1 & b1 \\ 1 & 2 & b2 \\ \bottomrule \end{tabular} + \end{table} """ ) assert result == expected - def test_to_latex_specified_header_without_index(self): - # GH 7124 + def test_to_latex_longtable_position(self): + the_position = "t" df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - result = df.to_latex(header=["AA", "BB"], index=False) + result = df.to_latex(longtable=True, position=the_position) expected = _dedent( r""" - \begin{tabular}{rl} + \begin{longtable}[t]{lrl} \toprule - AA & BB \\ + {} & a & b \\ \midrule - 1 & b1 \\ - 2 & b2 \\ + \endfirsthead + + \toprule + {} & a & b \\ + \midrule + \endhead + \midrule + \multicolumn{3}{r}{{Continued on next page}} \\ + \midrule + \endfoot + \bottomrule - \end{tabular} + \endlastfoot + 0 & 1 & b1 \\ + 1 & 2 & b2 \\ + \end{longtable} """ ) assert result == expected - def test_to_latex_specified_header_special_chars_without_escape(self): - # GH 7124 - df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - result = df.to_latex(header=["$A$", "$B$"], escape=False) + +class TestToLatexFormatters: + def test_to_latex_with_formatters(self): + df = DataFrame( + { + "datetime64": [ + datetime(2016, 1, 1), + datetime(2016, 2, 5), + datetime(2016, 3, 3), + ], + "float": [1.0, 2.0, 3.0], + "int": [1, 2, 3], + "object": [(1, 2), True, False], + } + ) + + formatters = { + "datetime64": lambda x: x.strftime("%Y-%m"), + "float": lambda x: f"[{x: 4.1f}]", + "int": lambda x: f"0x{x:x}", + "object": lambda x: f"-{x!s}-", + "__index__": lambda x: f"index: {x}", + } + result = df.to_latex(formatters=dict(formatters)) + expected = _dedent( r""" - \begin{tabular}{lrl} + \begin{tabular}{llrrl} \toprule - {} & $A$ & $B$ \\ + {} & datetime64 & float & int & object \\ \midrule - 0 & 1 & b1 \\ - 1 & 2 & b2 \\ + index: 0 & 2016-01 & [ 1.0] & 0x1 & -(1, 2)- \\ + index: 1 & 2016-02 & [ 2.0] & 0x2 & -True- \\ + index: 2 & 2016-03 & [ 3.0] & 0x3 & -False- \\ \bottomrule \end{tabular} """ ) assert result == expected - def test_to_latex_number_of_items_in_header_missmatch_raises(self): - # GH 7124 - df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - msg = "Writing 2 cols but got 1 aliases" - with pytest.raises(ValueError, match=msg): - df.to_latex(header=["A"]) + def test_to_latex_float_format_no_fixed_width_3decimals(self): + # GH 21625 + df = DataFrame({"x": [0.19999]}) + result = df.to_latex(float_format="%.3f") + expected = _dedent( + r""" + \begin{tabular}{lr} + \toprule + {} & x \\ + \midrule + 0 & 0.200 \\ + \bottomrule + \end{tabular} + """ + ) + assert result == expected - def test_to_latex_decimal(self): - # GH 12031 - df = DataFrame({"a": [1.0, 2.1], "b": ["b1", "b2"]}) - result = df.to_latex(decimal=",") + def test_to_latex_float_format_no_fixed_width_integer(self): + # GH 22270 + df = DataFrame({"x": [100.0]}) + result = df.to_latex(float_format="%.0f") expected = _dedent( r""" - \begin{tabular}{lrl} + \begin{tabular}{lr} \toprule - {} & a & b \\ + {} & x \\ \midrule - 0 & 1,0 & b1 \\ - 1 & 2,1 & b2 \\ + 0 & 100 \\ \bottomrule \end{tabular} """ ) assert result == expected - def test_to_latex_series(self): - s = Series(["a", "b", "c"]) - result = s.to_latex() + +class TestToLatexMultiindex: + @pytest.fixture + def multiindex_frame(self): + """Multiindex dataframe for testing multirow LaTeX macros.""" + yield DataFrame.from_dict( + { + ("c1", 0): pd.Series({x: x for x in range(4)}), + ("c1", 1): pd.Series({x: x + 4 for x in range(4)}), + ("c2", 0): pd.Series({x: x for x in range(4)}), + ("c2", 1): pd.Series({x: x + 4 for x in range(4)}), + ("c3", 0): pd.Series({x: x for x in range(4)}), + } + ).T + + @pytest.fixture + def multicolumn_frame(self): + """Multicolumn dataframe for testing multicolumn LaTeX macros.""" + yield pd.DataFrame( + { + ("c1", 0): {x: x for x in range(5)}, + ("c1", 1): {x: x + 5 for x in range(5)}, + ("c2", 0): {x: x for x in range(5)}, + ("c2", 1): {x: x + 5 for x in range(5)}, + ("c3", 0): {x: x for x in range(5)}, + } + ) + + def test_to_latex_multindex_header(self): + # GH 16718 + df = pd.DataFrame({"a": [0], "b": [1], "c": [2], "d": [3]}) + df = df.set_index(["a", "b"]) + observed = df.to_latex(header=["r1", "r2"]) + expected = _dedent( + r""" + \begin{tabular}{llrr} + \toprule + & & r1 & r2 \\ + a & b & & \\ + \midrule + 0 & 1 & 2 & 3 \\ + \bottomrule + \end{tabular} + """ + ) + assert observed == expected + + def test_to_latex_multiindex_empty_name(self): + # GH 18669 + mi = pd.MultiIndex.from_product([[1, 2]], names=[""]) + df = pd.DataFrame(-1, index=mi, columns=range(4)) + observed = df.to_latex() + expected = _dedent( + r""" + \begin{tabular}{lrrrr} + \toprule + & 0 & 1 & 2 & 3 \\ + {} & & & & \\ + \midrule + 1 & -1 & -1 & -1 & -1 \\ + 2 & -1 & -1 & -1 & -1 \\ + \bottomrule + \end{tabular} + """ + ) + assert observed == expected + + def test_to_latex_multiindex_column_tabular(self): + df = DataFrame({("x", "y"): ["a"]}) + result = df.to_latex() expected = _dedent( r""" \begin{tabular}{ll} \toprule - {} & 0 \\ + {} & x \\ + {} & y \\ \midrule 0 & a \\ - 1 & b \\ - 2 & c \\ \bottomrule \end{tabular} """ ) assert result == expected - def test_to_latex_bold_rows(self): - # GH 16707 - df = pd.DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - result = df.to_latex(bold_rows=True) + def test_to_latex_multiindex_small_tabular(self): + df = DataFrame({("x", "y"): ["a"]}).T + result = df.to_latex() expected = _dedent( r""" - \begin{tabular}{lrl} + \begin{tabular}{lll} \toprule - {} & a & b \\ + & & 0 \\ \midrule - \textbf{0} & 1 & b1 \\ - \textbf{1} & 2 & b2 \\ + x & y & a \\ \bottomrule \end{tabular} """ ) assert result == expected - def test_to_latex_no_bold_rows(self): - # GH 16707 - df = pd.DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - result = df.to_latex(bold_rows=False) + def test_to_latex_multiindex_tabular(self, multiindex_frame): + result = multiindex_frame.to_latex() expected = _dedent( r""" - \begin{tabular}{lrl} + \begin{tabular}{llrrrr} \toprule - {} & a & b \\ + & & 0 & 1 & 2 & 3 \\ \midrule - 0 & 1 & b1 \\ - 1 & 2 & b2 \\ + c1 & 0 & 0 & 1 & 2 & 3 \\ + & 1 & 4 & 5 & 6 & 7 \\ + c2 & 0 & 0 & 1 & 2 & 3 \\ + & 1 & 4 & 5 & 6 & 7 \\ + c3 & 0 & 0 & 1 & 2 & 3 \\ + \bottomrule + \end{tabular} + """ + ) + assert result == expected + + def test_to_latex_multicolumn_tabular(self, multiindex_frame): + # GH 14184 + df = multiindex_frame.T + df.columns.names = ["a", "b"] + result = df.to_latex() + expected = _dedent( + r""" + \begin{tabular}{lrrrrr} + \toprule + a & \multicolumn{2}{l}{c1} & \multicolumn{2}{l}{c2} & c3 \\ + b & 0 & 1 & 0 & 1 & 0 \\ + \midrule + 0 & 0 & 4 & 0 & 4 & 0 \\ + 1 & 1 & 5 & 1 & 5 & 1 \\ + 2 & 2 & 6 & 2 & 6 & 2 \\ + 3 & 3 & 7 & 3 & 7 & 3 \\ + \bottomrule + \end{tabular} + """ + ) + assert result == expected + + def test_to_latex_index_has_name_tabular(self): + # GH 10660 + df = pd.DataFrame({"a": [0, 0, 1, 1], "b": list("abab"), "c": [1, 2, 3, 4]}) + result = df.set_index(["a", "b"]).to_latex() + expected = _dedent( + r""" + \begin{tabular}{llr} + \toprule + & & c \\ + a & b & \\ + \midrule + 0 & a & 1 \\ + & b & 2 \\ + 1 & a & 3 \\ + & b & 4 \\ + \bottomrule + \end{tabular} + """ + ) + assert result == expected + + def test_to_latex_groupby_tabular(self): + # GH 10660 + df = pd.DataFrame({"a": [0, 0, 1, 1], "b": list("abab"), "c": [1, 2, 3, 4]}) + result = df.groupby("a").describe().to_latex() + expected = _dedent( + r""" + \begin{tabular}{lrrrrrrrr} + \toprule + {} & \multicolumn{8}{l}{c} \\ + {} & count & mean & std & min & 25\% & 50\% & 75\% & max \\ + a & & & & & & & & \\ + \midrule + 0 & 2.0 & 1.5 & 0.707107 & 1.0 & 1.25 & 1.5 & 1.75 & 2.0 \\ + 1 & 2.0 & 3.5 & 0.707107 & 3.0 & 3.25 & 3.5 & 3.75 & 4.0 \\ + \bottomrule + \end{tabular} + """ + ) + assert result == expected + + def test_to_latex_multiindex_dupe_level(self): + # see gh-14484 + # + # If an index is repeated in subsequent rows, it should be + # replaced with a blank in the created table. This should + # ONLY happen if all higher order indices (to the left) are + # equal too. In this test, 'c' has to be printed both times + # because the higher order index 'A' != 'B'. + df = pd.DataFrame( + index=pd.MultiIndex.from_tuples([("A", "c"), ("B", "c")]), columns=["col"] + ) + result = df.to_latex() + expected = _dedent( + r""" + \begin{tabular}{lll} + \toprule + & & col \\ + \midrule + A & c & NaN \\ + B & c & NaN \\ + \bottomrule + \end{tabular} + """ + ) + assert result == expected + + def test_to_latex_multicolumn_default(self, multicolumn_frame): + result = multicolumn_frame.to_latex() + expected = _dedent( + r""" + \begin{tabular}{lrrrrr} + \toprule + {} & \multicolumn{2}{l}{c1} & \multicolumn{2}{l}{c2} & c3 \\ + {} & 0 & 1 & 0 & 1 & 0 \\ + \midrule + 0 & 0 & 5 & 0 & 5 & 0 \\ + 1 & 1 & 6 & 1 & 6 & 1 \\ + 2 & 2 & 7 & 2 & 7 & 2 \\ + 3 & 3 & 8 & 3 & 8 & 3 \\ + 4 & 4 & 9 & 4 & 9 & 4 \\ + \bottomrule + \end{tabular} + """ + ) + assert result == expected + + def test_to_latex_multicolumn_false(self, multicolumn_frame): + result = multicolumn_frame.to_latex(multicolumn=False) + expected = _dedent( + r""" + \begin{tabular}{lrrrrr} + \toprule + {} & c1 & & c2 & & c3 \\ + {} & 0 & 1 & 0 & 1 & 0 \\ + \midrule + 0 & 0 & 5 & 0 & 5 & 0 \\ + 1 & 1 & 6 & 1 & 6 & 1 \\ + 2 & 2 & 7 & 2 & 7 & 2 \\ + 3 & 3 & 8 & 3 & 8 & 3 \\ + 4 & 4 & 9 & 4 & 9 & 4 \\ + \bottomrule + \end{tabular} + """ + ) + assert result == expected + + def test_to_latex_multirow_true(self, multicolumn_frame): + result = multicolumn_frame.T.to_latex(multirow=True) + expected = _dedent( + r""" + \begin{tabular}{llrrrrr} + \toprule + & & 0 & 1 & 2 & 3 & 4 \\ + \midrule + \multirow{2}{*}{c1} & 0 & 0 & 1 & 2 & 3 & 4 \\ + & 1 & 5 & 6 & 7 & 8 & 9 \\ + \cline{1-7} + \multirow{2}{*}{c2} & 0 & 0 & 1 & 2 & 3 & 4 \\ + & 1 & 5 & 6 & 7 & 8 & 9 \\ + \cline{1-7} + c3 & 0 & 0 & 1 & 2 & 3 & 4 \\ + \bottomrule + \end{tabular} + """ + ) + assert result == expected + + def test_to_latex_multicolumnrow_with_multicol_format(self, multicolumn_frame): + multicolumn_frame.index = multicolumn_frame.T.index + result = multicolumn_frame.T.to_latex( + multirow=True, + multicolumn=True, + multicolumn_format="c", + ) + expected = _dedent( + r""" + \begin{tabular}{llrrrrr} + \toprule + & & \multicolumn{2}{c}{c1} & \multicolumn{2}{c}{c2} & c3 \\ + & & 0 & 1 & 0 & 1 & 0 \\ + \midrule + \multirow{2}{*}{c1} & 0 & 0 & 1 & 2 & 3 & 4 \\ + & 1 & 5 & 6 & 7 & 8 & 9 \\ + \cline{1-7} + \multirow{2}{*}{c2} & 0 & 0 & 1 & 2 & 3 & 4 \\ + & 1 & 5 & 6 & 7 & 8 & 9 \\ + \cline{1-7} + c3 & 0 & 0 & 1 & 2 & 3 & 4 \\ \bottomrule \end{tabular} """ @@ -1061,7 +1166,8 @@ def test_to_latex_multiindex_nans(self, one_row): def test_to_latex_non_string_index(self): # GH 19981 - observed = pd.DataFrame([[1, 2, 3]] * 2).set_index([0, 1]).to_latex() + df = pd.DataFrame([[1, 2, 3]] * 2).set_index([0, 1]) + result = df.to_latex() expected = _dedent( r""" \begin{tabular}{llr} @@ -1075,100 +1181,8 @@ def test_to_latex_non_string_index(self): \end{tabular} """ ) - assert observed == expected - - def test_to_latex_midrule_location(self): - # GH 18326 - df = pd.DataFrame({"a": [1, 2]}) - df.index.name = "foo" - result = df.to_latex(index_names=False) - expected = _dedent( - r""" - \begin{tabular}{lr} - \toprule - {} & a \\ - \midrule - 0 & 1 \\ - 1 & 2 \\ - \bottomrule - \end{tabular} - """ - ) - assert result == expected - - def test_to_latex_multiindex_empty_name(self): - # GH 18669 - mi = pd.MultiIndex.from_product([[1, 2]], names=[""]) - df = pd.DataFrame(-1, index=mi, columns=range(4)) - observed = df.to_latex() - expected = _dedent( - r""" - \begin{tabular}{lrrrr} - \toprule - & 0 & 1 & 2 & 3 \\ - {} & & & & \\ - \midrule - 1 & -1 & -1 & -1 & -1 \\ - 2 & -1 & -1 & -1 & -1 \\ - \bottomrule - \end{tabular} - """ - ) - assert observed == expected - - def test_to_latex_float_format_no_fixed_width_3decimals(self): - # GH 21625 - df = DataFrame({"x": [0.19999]}) - result = df.to_latex(float_format="%.3f") - expected = _dedent( - r""" - \begin{tabular}{lr} - \toprule - {} & x \\ - \midrule - 0 & 0.200 \\ - \bottomrule - \end{tabular} - """ - ) assert result == expected - def test_to_latex_float_format_no_fixed_width_integer(self): - # GH 22270 - df = DataFrame({"x": [100.0]}) - result = df.to_latex(float_format="%.0f") - expected = _dedent( - r""" - \begin{tabular}{lr} - \toprule - {} & x \\ - \midrule - 0 & 100 \\ - \bottomrule - \end{tabular} - """ - ) - assert result == expected - - def test_to_latex_multindex_header(self): - # GH 16718 - df = pd.DataFrame({"a": [0], "b": [1], "c": [2], "d": [3]}) - df = df.set_index(["a", "b"]) - observed = df.to_latex(header=["r1", "r2"]) - expected = _dedent( - r""" - \begin{tabular}{llrr} - \toprule - & & r1 & r2 \\ - a & b & & \\ - \midrule - 0 & 1 & 2 & 3 \\ - \bottomrule - \end{tabular} - """ - ) - assert observed == expected - class TestTableBuilder: @pytest.fixture From 2b66e2fc05c78303cc08cfca91484017f94c4638 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 29 Sep 2020 02:04:43 +0700 Subject: [PATCH 2/4] TST: parametrize number of header cols --- pandas/tests/io/formats/test_to_latex.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index afb1e19820343..388b02c1a68e2 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -204,12 +204,25 @@ def test_to_latex_specified_header_without_index(self): ) assert result == expected - def test_to_latex_number_of_items_in_header_missmatch_raises(self): + @pytest.mark.parametrize( + "header, num_aliases", + [ + (["A"], 1), + (("B",), 1), + (("Col1", "Col2", "Col3"), 3), + (("Col1", "Col2", "Col3", "Col4"), 4), + ], + ) + def test_to_latex_number_of_items_in_header_missmatch_raises( + self, + header, + num_aliases, + ): # GH 7124 df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - msg = "Writing 2 cols but got 1 aliases" + msg = f"Writing 2 cols but got {num_aliases} aliases" with pytest.raises(ValueError, match=msg): - df.to_latex(header=["A"]) + df.to_latex(header=header) def test_to_latex_decimal(self): # GH 12031 From cd92db9dbc787f7abda2002184a2a705eea608b3 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 29 Sep 2020 02:09:54 +0700 Subject: [PATCH 3/4] REF: split column_format tests --- pandas/tests/io/formats/test_to_latex.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 388b02c1a68e2..4bd654bd55207 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -100,10 +100,11 @@ def test_to_latex_bad_column_format(self, bad_column_format): with pytest.raises(ValueError, match=msg): df.to_latex(column_format=bad_column_format) - def test_to_latex_column_format(self, float_frame): + def test_to_latex_column_format_just_works(self, float_frame): # GH Bug #9402 float_frame.to_latex(column_format="lcr") + def test_to_latex_column_format(self): df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) result = df.to_latex(column_format="lcr") expected = _dedent( From 8815c7fdea5fc11ba34501f7b5757fbf9c550a37 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 29 Sep 2020 02:10:32 +0700 Subject: [PATCH 4/4] REF: extract classes for header and bold --- pandas/tests/io/formats/test_to_latex.py | 250 ++++++++++++----------- 1 file changed, 127 insertions(+), 123 deletions(-) diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 4bd654bd55207..d3d865158309c 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -137,6 +137,132 @@ def test_to_latex_empty_tabular(self): ) assert result == expected + def test_to_latex_series(self): + s = Series(["a", "b", "c"]) + result = s.to_latex() + expected = _dedent( + r""" + \begin{tabular}{ll} + \toprule + {} & 0 \\ + \midrule + 0 & a \\ + 1 & b \\ + 2 & c \\ + \bottomrule + \end{tabular} + """ + ) + assert result == expected + + def test_to_latex_midrule_location(self): + # GH 18326 + df = pd.DataFrame({"a": [1, 2]}) + df.index.name = "foo" + result = df.to_latex(index_names=False) + expected = _dedent( + r""" + \begin{tabular}{lr} + \toprule + {} & a \\ + \midrule + 0 & 1 \\ + 1 & 2 \\ + \bottomrule + \end{tabular} + """ + ) + assert result == expected + + +class TestToLatexLongtable: + def test_to_latex_empty_longtable(self): + df = DataFrame() + result = df.to_latex(longtable=True) + expected = _dedent( + r""" + \begin{longtable}{l} + \toprule + Empty DataFrame + Columns: Index([], dtype='object') + Index: Index([], dtype='object') \\ + \end{longtable} + """ + ) + assert result == expected + + def test_to_latex_longtable_with_index(self): + df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) + result = df.to_latex(longtable=True) + expected = _dedent( + r""" + \begin{longtable}{lrl} + \toprule + {} & a & b \\ + \midrule + \endfirsthead + + \toprule + {} & a & b \\ + \midrule + \endhead + \midrule + \multicolumn{3}{r}{{Continued on next page}} \\ + \midrule + \endfoot + + \bottomrule + \endlastfoot + 0 & 1 & b1 \\ + 1 & 2 & b2 \\ + \end{longtable} + """ + ) + assert result == expected + + def test_to_latex_longtable_without_index(self): + df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) + result = df.to_latex(index=False, longtable=True) + expected = _dedent( + r""" + \begin{longtable}{rl} + \toprule + a & b \\ + \midrule + \endfirsthead + + \toprule + a & b \\ + \midrule + \endhead + \midrule + \multicolumn{2}{r}{{Continued on next page}} \\ + \midrule + \endfoot + + \bottomrule + \endlastfoot + 1 & b1 \\ + 2 & b2 \\ + \end{longtable} + """ + ) + assert result == expected + + @pytest.mark.parametrize( + "df, expected_number", + [ + (DataFrame({"a": [1, 2]}), 1), + (DataFrame({"a": [1, 2], "b": [3, 4]}), 2), + (DataFrame({"a": [1, 2], "b": [3, 4], "c": [5, 6]}), 3), + ], + ) + def test_to_latex_longtable_continued_on_next_page(self, df, expected_number): + result = df.to_latex(index=False, longtable=True) + assert fr"\multicolumn{{{expected_number}}}" in result + + +class TestToLatexHeader: def test_to_latex_no_header_with_index(self): # GH 7124 df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) @@ -243,24 +369,8 @@ def test_to_latex_decimal(self): ) assert result == expected - def test_to_latex_series(self): - s = Series(["a", "b", "c"]) - result = s.to_latex() - expected = _dedent( - r""" - \begin{tabular}{ll} - \toprule - {} & 0 \\ - \midrule - 0 & a \\ - 1 & b \\ - 2 & c \\ - \bottomrule - \end{tabular} - """ - ) - assert result == expected +class TestToLatexBold: def test_to_latex_bold_rows(self): # GH 16707 df = pd.DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) @@ -297,112 +407,6 @@ def test_to_latex_no_bold_rows(self): ) assert result == expected - def test_to_latex_midrule_location(self): - # GH 18326 - df = pd.DataFrame({"a": [1, 2]}) - df.index.name = "foo" - result = df.to_latex(index_names=False) - expected = _dedent( - r""" - \begin{tabular}{lr} - \toprule - {} & a \\ - \midrule - 0 & 1 \\ - 1 & 2 \\ - \bottomrule - \end{tabular} - """ - ) - assert result == expected - - -class TestToLatexLongtable: - def test_to_latex_empty_longtable(self): - df = DataFrame() - result = df.to_latex(longtable=True) - expected = _dedent( - r""" - \begin{longtable}{l} - \toprule - Empty DataFrame - Columns: Index([], dtype='object') - Index: Index([], dtype='object') \\ - \end{longtable} - """ - ) - assert result == expected - - def test_to_latex_longtable_with_index(self): - df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - result = df.to_latex(longtable=True) - expected = _dedent( - r""" - \begin{longtable}{lrl} - \toprule - {} & a & b \\ - \midrule - \endfirsthead - - \toprule - {} & a & b \\ - \midrule - \endhead - \midrule - \multicolumn{3}{r}{{Continued on next page}} \\ - \midrule - \endfoot - - \bottomrule - \endlastfoot - 0 & 1 & b1 \\ - 1 & 2 & b2 \\ - \end{longtable} - """ - ) - assert result == expected - - def test_to_latex_longtable_without_index(self): - df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - result = df.to_latex(index=False, longtable=True) - expected = _dedent( - r""" - \begin{longtable}{rl} - \toprule - a & b \\ - \midrule - \endfirsthead - - \toprule - a & b \\ - \midrule - \endhead - \midrule - \multicolumn{2}{r}{{Continued on next page}} \\ - \midrule - \endfoot - - \bottomrule - \endlastfoot - 1 & b1 \\ - 2 & b2 \\ - \end{longtable} - """ - ) - assert result == expected - - @pytest.mark.parametrize( - "df, expected_number", - [ - (DataFrame({"a": [1, 2]}), 1), - (DataFrame({"a": [1, 2], "b": [3, 4]}), 2), - (DataFrame({"a": [1, 2], "b": [3, 4], "c": [5, 6]}), 3), - ], - ) - def test_to_latex_longtable_continued_on_next_page(self, df, expected_number): - result = df.to_latex(index=False, longtable=True) - assert fr"\multicolumn{{{expected_number}}}" in result - class TestToLatexCaptionLabel: @pytest.fixture