diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 690e6b8f725ad..d10ec18e0898a 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -503,6 +503,9 @@ I/O - Bug in :class:`HDFStore` was dropping timezone information when exporting :class:`Series` with ``datetime64[ns, tz]`` dtypes with a fixed HDF5 store (:issue:`20594`) - :func:`read_csv` was closing user-provided binary file handles when ``engine="c"`` and an ``encoding`` was requested (:issue:`36980`) - Bug in :meth:`DataFrame.to_hdf` was not dropping missing rows with ``dropna=True`` (:issue:`35719`) +- In :meth:`to_latex` ``formatters`` now handles ``fstring`` and ``callable`` paramaters (:issue:`26278`) + + Plotting ^^^^^^^^ diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 36ce2c4776bd0..dd373872e9ae3 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -3034,10 +3034,14 @@ def to_latex( Write row names (index). na_rep : str, default 'NaN' Missing data representation. - formatters : list of functions or dict of {{str: function}}, optional + formatters : list of functions/str or dict of {{str: function}}, optional Formatter functions to apply to columns' elements by position or name. The result of each function must be a unicode string. List must be of length equal to the number of columns. + + .. versionchanged:: 1.2 + optionally allow fstrings in place of functions + float_format : one-parameter function or str, optional, default None Formatter for floating point numbers. For example ``float_format="%.2f"`` and ``float_format="{{:0.2f}}".format`` will @@ -3088,7 +3092,7 @@ def to_latex( .. versionadded:: 1.0.0 .. versionchanged:: 1.2.0 - Optionally allow caption to be a tuple ``(full_caption, short_caption)``. + optionally allow caption to be a tuple ``(full_caption, short_caption)``. label : str, optional The LaTeX label to be placed inside ``\label{{}}`` in the output. @@ -3121,6 +3125,17 @@ def to_latex( Donatello & purple & bo staff \\ \bottomrule \end{{tabular}} + >>> df = pd.DataFrame([[1,2], [3,4]], columns=['a','b']) + >>> print(df.to_latex(formatters=["d", ".3f"], + ... index=False)) # doctest: +NORMALIZE_WHITESPACE + \begin{{tabular}}{{rr}} + \toprule + a & b \\ + \midrule + 1 & 2.000 \\ + 3 & 4.000 \\ + \bottomrule + \end{{tabular}} """ # Get defaults from the pandas config if self.ndim == 1: diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index 43e76d0aef490..bf4bd38e6291e 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -84,7 +84,11 @@ FormattersType = Union[ - List[Callable], Tuple[Callable, ...], Mapping[Union[str, int], Callable] + List[Callable], + Tuple[Callable, ...], + List[str], + List[Union[Callable, str]], + Mapping[Union[str, int], Callable], ] ColspaceType = Mapping[Label, Union[str, int]] ColspaceArgType = Union[ @@ -106,7 +110,7 @@ Whether to print index (row) labels. na_rep : str, optional, default 'NaN' String representation of ``NaN`` to use. - formatters : list, tuple or dict of one-param. functions, optional + formatters : list, tuple or dict of one-param. functions, str, optional Formatter functions to apply to columns' elements by position or name. The result of each function must be a unicode string. @@ -576,6 +580,24 @@ def _initialize_sparsify(self, sparsify: Optional[bool]) -> bool: def _initialize_formatters( self, formatters: Optional[FormattersType] ) -> FormattersType: + if is_list_like(formatters) and not isinstance(formatters, dict): + formatter_elems_type = all( + isinstance(elem, str) or callable(elem) for elem in formatters + ) + if formatter_elems_type: + # two fold lambda is required to bypass lambda replication + # issues in list comprehensions + formatters = [ + (lambda style: lambda x: "{0:{1}}".format(x, style))(style) + if isinstance(style, str) + else style + for style in formatters + ] + else: + raise ValueError( + "Formatters elements should be f-strings or callable functions" + ) + if formatters is None: return {} elif len(self.frame.columns) == len(formatters) or isinstance(formatters, dict): diff --git a/pandas/tests/io/formats/test_to_latex.py b/pandas/tests/io/formats/test_to_latex.py index 7cf7ed3f77609..b96b674b2f230 100644 --- a/pandas/tests/io/formats/test_to_latex.py +++ b/pandas/tests/io/formats/test_to_latex.py @@ -121,6 +121,27 @@ def test_to_latex_column_format(self): ) assert result == expected + def test_to_latex_with_float_format_list(self): + # GH: 26278 + data = [[1, 2, 3], [4, 5, 6], [7, 8, 9.001]] + df = DataFrame(data, columns=["a", "b", "c"], index=["foo", "bar", "foobar"]) + + result = df.to_latex(formatters=["d", ".2f", lambda x: f"{x:.3f}"]) + expected = _dedent( + r""" + \begin{tabular}{lrrr} + \toprule + {} & a & b & c \\ + \midrule + foo & 1 & 2.00 & 3.000 \\ + bar & 4 & 5.00 & 6.000 \\ + foobar & 7 & 8.00 & 9.001 \\ + \bottomrule + \end{tabular} + """ + ) + assert result == expected + def test_to_latex_empty_tabular(self): df = DataFrame() result = df.to_latex()