diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index ae67b05047a98..24a396eb9491e 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -1022,38 +1022,6 @@ class DataFrameRenderer: def __init__(self, fmt: DataFrameFormatter) -> None: self.fmt = fmt - def to_latex( - self, - buf: FilePath | WriteBuffer[str] | None = None, - column_format: str | None = None, - longtable: bool = False, - encoding: str | None = None, - multicolumn: bool = False, - multicolumn_format: str | None = None, - multirow: bool = False, - caption: str | tuple[str, str] | None = None, - label: str | None = None, - position: str | None = None, - ) -> str | None: - """ - Render a DataFrame to a LaTeX tabular/longtable environment output. - """ - from pandas.io.formats.latex import LatexFormatter - - latex_formatter = LatexFormatter( - self.fmt, - longtable=longtable, - column_format=column_format, - multicolumn=multicolumn, - multicolumn_format=multicolumn_format, - multirow=multirow, - caption=caption, - label=label, - position=position, - ) - string = latex_formatter.to_string() - return save_to_buffer(string, buf=buf, encoding=encoding) - def to_html( self, buf: FilePath | WriteBuffer[str] | None = None, diff --git a/pandas/io/formats/latex.py b/pandas/io/formats/latex.py deleted file mode 100644 index a97f3d4ef541e..0000000000000 --- a/pandas/io/formats/latex.py +++ /dev/null @@ -1,831 +0,0 @@ -""" -Module for formatting output data in Latex. -""" -from __future__ import annotations - -from abc import ( - ABC, - abstractmethod, -) -from typing import ( - TYPE_CHECKING, - Iterator, - Sequence, -) - -import numpy as np - -from pandas.core.dtypes.generic import ABCMultiIndex - -if TYPE_CHECKING: - from pandas.io.formats.format import DataFrameFormatter - - -def _split_into_full_short_caption( - caption: str | tuple[str, str] | None -) -> tuple[str, str]: - """Extract full and short captions from caption string/tuple. - - Parameters - ---------- - caption : str or tuple, optional - Either table caption string or tuple (full_caption, short_caption). - If string is provided, then it is treated as table full caption, - while short_caption is considered an empty string. - - Returns - ------- - full_caption, short_caption : tuple - Tuple of full_caption, short_caption strings. - """ - if caption: - if isinstance(caption, str): - full_caption = caption - short_caption = "" - else: - try: - full_caption, short_caption = caption - except ValueError as err: - msg = "caption must be either a string or a tuple of two strings" - raise ValueError(msg) from err - else: - full_caption = "" - short_caption = "" - return full_caption, short_caption - - -class RowStringConverter: - r"""Converter for dataframe rows into LaTeX strings. - - Parameters - ---------- - formatter : `DataFrameFormatter` - Instance of `DataFrameFormatter`. - multicolumn: bool, optional - Whether to use \multicolumn macro. - multicolumn_format: str, optional - Multicolumn format. - multirow: bool, optional - Whether to use \multirow macro. - - """ - - def __init__( - self, - formatter: DataFrameFormatter, - multicolumn: bool = False, - multicolumn_format: str | None = None, - multirow: bool = False, - ) -> None: - self.fmt = formatter - self.frame = self.fmt.frame - self.multicolumn = multicolumn - self.multicolumn_format = multicolumn_format - self.multirow = multirow - self.clinebuf: list[list[int]] = [] - self.strcols = self._get_strcols() - self.strrows = list(zip(*self.strcols)) - - def get_strrow(self, row_num: int) -> str: - """Get string representation of the row.""" - row = self.strrows[row_num] - - is_multicol = ( - row_num < self.column_levels and self.fmt.header and self.multicolumn - ) - - is_multirow = ( - row_num >= self.header_levels - and self.fmt.index - and self.multirow - and self.index_levels > 1 - ) - - is_cline_maybe_required = is_multirow and row_num < len(self.strrows) - 1 - - crow = self._preprocess_row(row) - - if is_multicol: - crow = self._format_multicolumn(crow) - if is_multirow: - crow = self._format_multirow(crow, row_num) - - lst = [] - lst.append(" & ".join(crow)) - lst.append(" \\\\") - if is_cline_maybe_required: - cline = self._compose_cline(row_num, len(self.strcols)) - lst.append(cline) - return "".join(lst) - - @property - def _header_row_num(self) -> int: - """Number of rows in header.""" - return self.header_levels if self.fmt.header else 0 - - @property - def index_levels(self) -> int: - """Integer number of levels in index.""" - return self.frame.index.nlevels - - @property - def column_levels(self) -> int: - return self.frame.columns.nlevels - - @property - def header_levels(self) -> int: - nlevels = self.column_levels - if self.fmt.has_index_names and self.fmt.show_index_names: - nlevels += 1 - return nlevels - - def _get_strcols(self) -> list[list[str]]: - """String representation of the columns.""" - if self.fmt.frame.empty: - strcols = [[self._empty_info_line]] - else: - strcols = self.fmt.get_strcols() - - # reestablish the MultiIndex that has been joined by get_strcols() - if self.fmt.index and isinstance(self.frame.index, ABCMultiIndex): - out = self.frame.index.format( - adjoin=False, - sparsify=self.fmt.sparsify, - names=self.fmt.has_index_names, - na_rep=self.fmt.na_rep, - ) - - # index.format will sparsify repeated entries with empty strings - # so pad these with some empty space - def pad_empties(x): - for pad in reversed(x): - if pad: - return [x[0]] + [i if i else " " * len(pad) for i in x[1:]] - - gen = (pad_empties(i) for i in out) - - # Add empty spaces for each column level - clevels = self.frame.columns.nlevels - out = [[" " * len(i[-1])] * clevels + i for i in gen] - - # Add the column names to the last index column - cnames = self.frame.columns.names - if any(cnames): - new_names = [i if i else "{}" for i in cnames] - out[self.frame.index.nlevels - 1][:clevels] = new_names - - # Get rid of old multiindex column and add new ones - strcols = out + strcols[1:] - return strcols - - @property - def _empty_info_line(self) -> str: - return ( - f"Empty {type(self.frame).__name__}\n" - f"Columns: {self.frame.columns}\n" - f"Index: {self.frame.index}" - ) - - def _preprocess_row(self, row: Sequence[str]) -> list[str]: - """Preprocess elements of the row.""" - if self.fmt.escape: - crow = _escape_symbols(row) - else: - crow = [x if x else "{}" for x in row] - if self.fmt.bold_rows and self.fmt.index: - crow = _convert_to_bold(crow, self.index_levels) - return crow - - def _format_multicolumn(self, row: list[str]) -> list[str]: - r""" - Combine columns belonging to a group to a single multicolumn entry - according to self.multicolumn_format - - e.g.: - a & & & b & c & - will become - \multicolumn{3}{l}{a} & b & \multicolumn{2}{l}{c} - """ - row2 = row[: self.index_levels] - ncol = 1 - coltext = "" - - def append_col() -> None: - # write multicolumn if needed - if ncol > 1: - row2.append( - f"\\multicolumn{{{ncol:d}}}{{{self.multicolumn_format}}}" - f"{{{coltext.strip()}}}" - ) - # don't modify where not needed - else: - row2.append(coltext) - - for c in row[self.index_levels :]: - # if next col has text, write the previous - if c.strip(): - if coltext: - append_col() - coltext = c - ncol = 1 - # if not, add it to the previous multicolumn - else: - ncol += 1 - # write last column name - if coltext: - append_col() - return row2 - - def _format_multirow(self, row: list[str], i: int) -> list[str]: - r""" - Check following rows, whether row should be a multirow - - e.g.: becomes: - a & 0 & \multirow{2}{*}{a} & 0 & - & 1 & & 1 & - b & 0 & \cline{1-2} - b & 0 & - """ - for j in range(self.index_levels): - if row[j].strip(): - nrow = 1 - for r in self.strrows[i + 1 :]: - if not r[j].strip(): - nrow += 1 - else: - break - if nrow > 1: - # overwrite non-multirow entry - row[j] = f"\\multirow{{{nrow:d}}}{{*}}{{{row[j].strip()}}}" - # save when to end the current block with \cline - self.clinebuf.append([i + nrow - 1, j + 1]) - return row - - def _compose_cline(self, i: int, icol: int) -> str: - """ - Create clines after multirow-blocks are finished. - """ - lst = [] - for cl in self.clinebuf: - if cl[0] == i: - lst.append(f"\n\\cline{{{cl[1]:d}-{icol:d}}}") - # remove entries that have been written to buffer - self.clinebuf = [x for x in self.clinebuf if x[0] != i] - return "".join(lst) - - -class RowStringIterator(RowStringConverter): - """Iterator over rows of the header or the body of the table.""" - - @abstractmethod - def __iter__(self) -> Iterator[str]: - """Iterate over LaTeX string representations of rows.""" - - -class RowHeaderIterator(RowStringIterator): - """Iterator for the table header rows.""" - - def __iter__(self) -> Iterator[str]: - for row_num in range(len(self.strrows)): - if row_num < self._header_row_num: - yield self.get_strrow(row_num) - - -class RowBodyIterator(RowStringIterator): - """Iterator for the table body rows.""" - - def __iter__(self) -> Iterator[str]: - for row_num in range(len(self.strrows)): - if row_num >= self._header_row_num: - yield self.get_strrow(row_num) - - -class TableBuilderAbstract(ABC): - """ - Abstract table builder producing string representation of LaTeX table. - - Parameters - ---------- - formatter : `DataFrameFormatter` - Instance of `DataFrameFormatter`. - column_format: str, optional - Column format, for example, 'rcl' for three columns. - multicolumn: bool, optional - Use multicolumn to enhance MultiIndex columns. - multicolumn_format: str, optional - The alignment for multicolumns, similar to column_format. - multirow: bool, optional - Use multirow to enhance MultiIndex rows. - caption: str, optional - Table caption. - short_caption: str, optional - Table short caption. - label: str, optional - LaTeX label. - position: str, optional - Float placement specifier, for example, 'htb'. - """ - - def __init__( - self, - formatter: DataFrameFormatter, - column_format: str | None = None, - multicolumn: bool = False, - multicolumn_format: str | None = None, - multirow: bool = False, - caption: str | None = None, - short_caption: str | None = None, - label: str | None = None, - position: str | None = None, - ) -> None: - self.fmt = formatter - self.column_format = column_format - self.multicolumn = multicolumn - self.multicolumn_format = multicolumn_format - self.multirow = multirow - self.caption = caption - self.short_caption = short_caption - self.label = label - self.position = position - - def get_result(self) -> str: - """String representation of LaTeX table.""" - elements = [ - self.env_begin, - self.top_separator, - self.header, - self.middle_separator, - self.env_body, - self.bottom_separator, - self.env_end, - ] - result = "\n".join([item for item in elements if item]) - trailing_newline = "\n" - result += trailing_newline - return result - - @property - @abstractmethod - def env_begin(self) -> str: - """Beginning of the environment.""" - - @property - @abstractmethod - def top_separator(self) -> str: - """Top level separator.""" - - @property - @abstractmethod - def header(self) -> str: - """Header lines.""" - - @property - @abstractmethod - def middle_separator(self) -> str: - """Middle level separator.""" - - @property - @abstractmethod - def env_body(self) -> str: - """Environment body.""" - - @property - @abstractmethod - def bottom_separator(self) -> str: - """Bottom level separator.""" - - @property - @abstractmethod - def env_end(self) -> str: - """End of the environment.""" - - -class GenericTableBuilder(TableBuilderAbstract): - """Table builder producing string representation of LaTeX table.""" - - @property - def header(self) -> str: - iterator = self._create_row_iterator(over="header") - return "\n".join(list(iterator)) - - @property - def top_separator(self) -> str: - return "\\toprule" - - @property - def middle_separator(self) -> str: - return "\\midrule" if self._is_separator_required() else "" - - @property - def env_body(self) -> str: - iterator = self._create_row_iterator(over="body") - return "\n".join(list(iterator)) - - def _is_separator_required(self) -> bool: - return bool(self.header and self.env_body) - - @property - def _position_macro(self) -> str: - r"""Position macro, extracted from self.position, like [h].""" - return f"[{self.position}]" if self.position else "" - - @property - def _caption_macro(self) -> str: - r"""Caption macro, extracted from self.caption. - - With short caption: - \caption[short_caption]{caption_string}. - - Without short caption: - \caption{caption_string}. - """ - if self.caption: - return "".join( - [ - r"\caption", - f"[{self.short_caption}]" if self.short_caption else "", - f"{{{self.caption}}}", - ] - ) - return "" - - @property - def _label_macro(self) -> str: - r"""Label macro, extracted from self.label, like \label{ref}.""" - return f"\\label{{{self.label}}}" if self.label else "" - - def _create_row_iterator(self, over: str) -> RowStringIterator: - """Create iterator over header or body of the table. - - Parameters - ---------- - over : {'body', 'header'} - Over what to iterate. - - Returns - ------- - RowStringIterator - Iterator over body or header. - """ - iterator_kind = self._select_iterator(over) - return iterator_kind( - formatter=self.fmt, - multicolumn=self.multicolumn, - multicolumn_format=self.multicolumn_format, - multirow=self.multirow, - ) - - def _select_iterator(self, over: str) -> type[RowStringIterator]: - """Select proper iterator over table rows.""" - if over == "header": - return RowHeaderIterator - elif over == "body": - return RowBodyIterator - else: - msg = f"'over' must be either 'header' or 'body', but {over} was provided" - raise ValueError(msg) - - -class LongTableBuilder(GenericTableBuilder): - """Concrete table builder for longtable. - - >>> from pandas.io.formats import format as fmt - >>> df = pd.DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - >>> formatter = fmt.DataFrameFormatter(df) - >>> builder = LongTableBuilder(formatter, caption='a long table', - ... label='tab:long', column_format='lrl') - >>> table = builder.get_result() - >>> print(table) - \\begin{longtable}{lrl} - \\caption{a long table} - \\label{tab:long}\\\\ - \\toprule - {} & a & b \\\\ - \\midrule - \\endfirsthead - \\caption[]{a long table} \\\\ - \\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} - - """ - - @property - def env_begin(self) -> str: - first_row = ( - f"\\begin{{longtable}}{self._position_macro}{{{self.column_format}}}" - ) - elements = [first_row, f"{self._caption_and_label()}"] - return "\n".join([item for item in elements if item]) - - def _caption_and_label(self) -> str: - if self.caption or self.label: - double_backslash = "\\\\" - elements = [f"{self._caption_macro}", f"{self._label_macro}"] - caption_and_label = "\n".join([item for item in elements if item]) - caption_and_label += double_backslash - return caption_and_label - else: - return "" - - @property - def middle_separator(self) -> str: - iterator = self._create_row_iterator(over="header") - - # the content between \endfirsthead and \endhead commands - # mitigates repeated List of Tables entries in the final LaTeX - # document when dealing with longtable environments; GH #34360 - elements = [ - "\\midrule", - "\\endfirsthead", - f"\\caption[]{{{self.caption}}} \\\\" if self.caption else "", - self.top_separator, - self.header, - "\\midrule", - "\\endhead", - "\\midrule", - f"\\multicolumn{{{len(iterator.strcols)}}}{{r}}" - "{{Continued on next page}} \\\\", - "\\midrule", - "\\endfoot\n", - "\\bottomrule", - "\\endlastfoot", - ] - if self._is_separator_required(): - return "\n".join(elements) - return "" - - @property - def bottom_separator(self) -> str: - return "" - - @property - def env_end(self) -> str: - return "\\end{longtable}" - - -class RegularTableBuilder(GenericTableBuilder): - """Concrete table builder for regular table. - - >>> from pandas.io.formats import format as fmt - >>> df = pd.DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - >>> formatter = fmt.DataFrameFormatter(df) - >>> builder = RegularTableBuilder(formatter, caption='caption', label='lab', - ... column_format='lrc') - >>> table = builder.get_result() - >>> print(table) - \\begin{table} - \\centering - \\caption{caption} - \\label{lab} - \\begin{tabular}{lrc} - \\toprule - {} & a & b \\\\ - \\midrule - 0 & 1 & b1 \\\\ - 1 & 2 & b2 \\\\ - \\bottomrule - \\end{tabular} - \\end{table} - - """ - - @property - def env_begin(self) -> str: - elements = [ - f"\\begin{{table}}{self._position_macro}", - "\\centering", - f"{self._caption_macro}", - f"{self._label_macro}", - f"\\begin{{tabular}}{{{self.column_format}}}", - ] - return "\n".join([item for item in elements if item]) - - @property - def bottom_separator(self) -> str: - return "\\bottomrule" - - @property - def env_end(self) -> str: - return "\n".join(["\\end{tabular}", "\\end{table}"]) - - -class TabularBuilder(GenericTableBuilder): - """Concrete table builder for tabular environment. - - >>> from pandas.io.formats import format as fmt - >>> df = pd.DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - >>> formatter = fmt.DataFrameFormatter(df) - >>> builder = TabularBuilder(formatter, column_format='lrc') - >>> table = builder.get_result() - >>> print(table) - \\begin{tabular}{lrc} - \\toprule - {} & a & b \\\\ - \\midrule - 0 & 1 & b1 \\\\ - 1 & 2 & b2 \\\\ - \\bottomrule - \\end{tabular} - - """ - - @property - def env_begin(self) -> str: - return f"\\begin{{tabular}}{{{self.column_format}}}" - - @property - def bottom_separator(self) -> str: - return "\\bottomrule" - - @property - def env_end(self) -> str: - return "\\end{tabular}" - - -class LatexFormatter: - r""" - Used to render a DataFrame to a LaTeX tabular/longtable environment output. - - Parameters - ---------- - formatter : `DataFrameFormatter` - longtable : bool, default False - Use longtable environment. - column_format : str, default None - The columns format as specified in `LaTeX table format - `__ e.g 'rcl' for 3 columns - multicolumn : bool, default False - Use \multicolumn to enhance MultiIndex columns. - multicolumn_format : str, default 'l' - The alignment for multicolumns, similar to `column_format` - multirow : bool, default False - Use \multirow to enhance MultiIndex rows. - caption : str or tuple, optional - Tuple (full_caption, short_caption), - which results in \caption[short_caption]{full_caption}; - if a single string is passed, no short caption will be set. - label : str, optional - The LaTeX label to be placed inside ``\label{}`` in the output. - position : str, optional - The LaTeX positional argument for tables, to be placed after - ``\begin{}`` in the output. - - See Also - -------- - HTMLFormatter - """ - - def __init__( - self, - formatter: DataFrameFormatter, - longtable: bool = False, - column_format: str | None = None, - multicolumn: bool = False, - multicolumn_format: str | None = None, - multirow: bool = False, - caption: str | tuple[str, str] | None = None, - label: str | None = None, - position: str | None = None, - ) -> None: - self.fmt = formatter - self.frame = self.fmt.frame - self.longtable = longtable - self.column_format = column_format - self.multicolumn = multicolumn - self.multicolumn_format = multicolumn_format - self.multirow = multirow - self.caption, self.short_caption = _split_into_full_short_caption(caption) - self.label = label - self.position = position - - def to_string(self) -> str: - """ - Render a DataFrame to a LaTeX tabular, longtable, or table/tabular - environment output. - """ - return self.builder.get_result() - - @property - def builder(self) -> TableBuilderAbstract: - """Concrete table builder. - - Returns - ------- - TableBuilder - """ - builder = self._select_builder() - return builder( - formatter=self.fmt, - column_format=self.column_format, - multicolumn=self.multicolumn, - multicolumn_format=self.multicolumn_format, - multirow=self.multirow, - caption=self.caption, - short_caption=self.short_caption, - label=self.label, - position=self.position, - ) - - def _select_builder(self) -> type[TableBuilderAbstract]: - """Select proper table builder.""" - if self.longtable: - return LongTableBuilder - if any([self.caption, self.label, self.position]): - return RegularTableBuilder - return TabularBuilder - - @property - def column_format(self) -> str | None: - """Column format.""" - return self._column_format - - @column_format.setter - def column_format(self, input_column_format: str | None) -> None: - """Setter for column format.""" - if input_column_format is None: - self._column_format = ( - self._get_index_format() + self._get_column_format_based_on_dtypes() - ) - elif not isinstance(input_column_format, str): - raise ValueError( - f"column_format must be str or unicode, " - f"not {type(input_column_format)}" - ) - else: - self._column_format = input_column_format - - def _get_column_format_based_on_dtypes(self) -> str: - """Get column format based on data type. - - Right alignment for numbers and left - for strings. - """ - - def get_col_type(dtype) -> str: - if issubclass(dtype.type, np.number): - return "r" - return "l" - - dtypes = self.frame.dtypes._values - return "".join(map(get_col_type, dtypes)) - - def _get_index_format(self) -> str: - """Get index column format.""" - return "l" * self.frame.index.nlevels if self.fmt.index else "" - - -def _escape_symbols(row: Sequence[str]) -> list[str]: - """Carry out string replacements for special symbols. - - Parameters - ---------- - row : list - List of string, that may contain special symbols. - - Returns - ------- - list - list of strings with the special symbols replaced. - """ - return [ - ( - x.replace("\\", "\\textbackslash ") - .replace("_", "\\_") - .replace("%", "\\%") - .replace("$", "\\$") - .replace("#", "\\#") - .replace("{", "\\{") - .replace("}", "\\}") - .replace("~", "\\textasciitilde ") - .replace("^", "\\textasciicircum ") - .replace("&", "\\&") - if (x and x != "{}") - else "{}" - ) - for x in row - ] - - -def _convert_to_bold(crow: Sequence[str], ilevels: int) -> list[str]: - """Convert elements in ``crow`` to bold.""" - return [ - f"\\textbf{{{x}}}" if j < ilevels and x.strip() not in ["", "{}"] else x - for j, x in enumerate(crow) - ] - - -if __name__ == "__main__": - import doctest - - doctest.testmod() diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index c0bbc22fc1746..64c064172a646 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -11,14 +11,6 @@ ) import pandas._testing as tm -from pandas.io.formats.format import DataFrameFormatter -from pandas.io.formats.latex import ( - RegularTableBuilder, - RowBodyIterator, - RowHeaderIterator, - RowStringConverter, -) - pytest.importorskip("jinja2") @@ -1417,97 +1409,14 @@ def test_to_latex_multiindex_multirow(self): assert result == expected -class TestTableBuilder: - @pytest.fixture - def dataframe(self): - return DataFrame({"a": [1, 2], "b": ["b1", "b2"]}) - - @pytest.fixture - def table_builder(self, dataframe): - return RegularTableBuilder(formatter=DataFrameFormatter(dataframe)) - - def test_create_row_iterator(self, table_builder): - iterator = table_builder._create_row_iterator(over="header") - assert isinstance(iterator, RowHeaderIterator) - - def test_create_body_iterator(self, table_builder): - iterator = table_builder._create_row_iterator(over="body") - assert isinstance(iterator, RowBodyIterator) - - def test_create_body_wrong_kwarg_raises(self, table_builder): - with pytest.raises(ValueError, match="must be either 'header' or 'body'"): - table_builder._create_row_iterator(over="SOMETHING BAD") - - -class TestRowStringConverter: - @pytest.mark.parametrize( - "row_num, expected", - [ - (0, r"{} & Design & ratio & xy \\"), - (1, r"0 & 1 & 4 & 10 \\"), - (2, r"1 & 2 & 5 & 11 \\"), - ], - ) - def test_get_strrow_normal_without_escape(self, row_num, expected): - df = DataFrame({r"Design": [1, 2, 3], r"ratio": [4, 5, 6], r"xy": [10, 11, 12]}) - row_string_converter = RowStringConverter( - formatter=DataFrameFormatter(df, escape=True), - ) - assert row_string_converter.get_strrow(row_num=row_num) == expected - - @pytest.mark.parametrize( - "row_num, expected", - [ - (0, r"{} & Design \# & ratio, \% & x\&y \\"), - (1, r"0 & 1 & 4 & 10 \\"), - (2, r"1 & 2 & 5 & 11 \\"), - ], - ) - def test_get_strrow_normal_with_escape(self, row_num, expected): - df = DataFrame( - {r"Design #": [1, 2, 3], r"ratio, %": [4, 5, 6], r"x&y": [10, 11, 12]} - ) - row_string_converter = RowStringConverter( - formatter=DataFrameFormatter(df, escape=True), - ) - assert row_string_converter.get_strrow(row_num=row_num) == expected - - @pytest.mark.parametrize( - "row_num, expected", - [ - (0, r"{} & \multicolumn{2}{r}{c1} & \multicolumn{2}{r}{c2} & c3 \\"), - (1, r"{} & 0 & 1 & 0 & 1 & 0 \\"), - (2, r"0 & 0 & 5 & 0 & 5 & 0 \\"), - ], +def test_to_latex_exceeding_float_point_double(): + df = DataFrame(data=[[1234567890123456789]], columns=["test"]) + expected = _dedent( + r""" + \begin{tabular}{lr} + & test \\ + 0 & 1234567890123456789 \\ + \end{tabular} + """ ) - def test_get_strrow_multindex_multicolumn(self, row_num, expected): - df = 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)}, - } - ) - - row_string_converter = RowStringConverter( - formatter=DataFrameFormatter(df), - multicolumn=True, - multicolumn_format="r", - multirow=True, - ) - - assert row_string_converter.get_strrow(row_num=row_num) == expected - - def test_to_latex_exceeding_float_point_double(self): - df = DataFrame(data=[[1234567890123456789]], columns=["test"]) - expected = _dedent( - r""" - \begin{tabular}{lr} - & test \\ - 0 & 1234567890123456789 \\ - \end{tabular} - """ - ) - assert df.style.to_latex() == expected + assert df.style.to_latex() == expected