diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index bc5d66e8e7955..737d4bf22f944 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -877,7 +877,8 @@ I/O - Bug in :func:`read_sql_query` ignoring ``dtype`` argument when ``chunksize`` is specified and result is empty (:issue:`50245`) - Bug in :func:`read_csv` for a single-line csv with fewer columns than ``names`` raised :class:`.errors.ParserError` with ``engine="c"`` (:issue:`47566`) - Bug in displaying ``string`` dtypes not showing storage option (:issue:`50099`) -- Bug in :func:`DataFrame.to_string` with ``header=False`` that printed the index name on the same line as the first row of the data (:issue:`49230`) +- Bug in :meth:`DataFrame.to_string` with ``header=False`` that printed the index name on the same line as the first row of the data (:issue:`49230`) +- Bug in :meth:`DataFrame.to_string` ignoring float formatter for extension arrays (:issue:`39336`) - Fixed memory leak which stemmed from the initialization of the internal JSON module (:issue:`49222`) - Fixed issue where :func:`json_normalize` would incorrectly remove leading characters from column names that matched the ``sep`` argument (:issue:`49861`) - diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index 1328e77219153..c05bd437d6c3e 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -1259,6 +1259,7 @@ def format_array( decimal: str = ".", leading_space: bool | None = True, quoting: int | None = None, + fallback_formatter: Callable | None = None, ) -> list[str]: """ Format an array for printing. @@ -1281,6 +1282,7 @@ def format_array( When formatting an Index subclass (e.g. IntervalIndex._format_native_types), we don't want the leading space since it should be left-aligned. + fallback_formatter Returns ------- @@ -1322,6 +1324,7 @@ def format_array( decimal=decimal, leading_space=leading_space, quoting=quoting, + fallback_formatter=fallback_formatter, ) return fmt_obj.get_result() @@ -1341,6 +1344,7 @@ def __init__( quoting: int | None = None, fixed_width: bool = True, leading_space: bool | None = True, + fallback_formatter: Callable | None = None, ) -> None: self.values = values self.digits = digits @@ -1353,6 +1357,7 @@ def __init__( self.quoting = quoting self.fixed_width = fixed_width self.leading_space = leading_space + self.fallback_formatter = fallback_formatter def get_result(self) -> list[str]: fmt_values = self._format_strings() @@ -1371,6 +1376,8 @@ def _format_strings(self) -> list[str]: if self.formatter is not None: formatter = self.formatter + elif self.fallback_formatter is not None: + formatter = self.fallback_formatter else: quote_strings = self.quoting is not None and self.quoting != QUOTE_NONE formatter = partial( @@ -1419,7 +1426,7 @@ def _format(x): fmt_values = [] for i, v in enumerate(vals): - if not is_float_type[i] and leading_space: + if not is_float_type[i] and leading_space or self.formatter is not None: fmt_values.append(f" {_format(v)}") elif is_float_type[i]: fmt_values.append(float_format(v)) @@ -1651,8 +1658,9 @@ def _format_strings(self) -> list[str]: values = extract_array(self.values, extract_numpy=True) formatter = self.formatter + fallback_formatter = None if formatter is None: - formatter = values._formatter(boxed=True) + fallback_formatter = values._formatter(boxed=True) if isinstance(values, Categorical): # Categorical is special for now, so that we can preserve tzinfo @@ -1671,6 +1679,7 @@ def _format_strings(self) -> list[str]: decimal=self.decimal, leading_space=self.leading_space, quoting=self.quoting, + fallback_formatter=fallback_formatter, ) return fmt_values diff --git a/pandas/tests/frame/test_repr_info.py b/pandas/tests/frame/test_repr_info.py index 4664052196797..fbe5500398f74 100644 --- a/pandas/tests/frame/test_repr_info.py +++ b/pandas/tests/frame/test_repr_info.py @@ -365,3 +365,17 @@ def test_datetime64tz_slice_non_truncate(self): df = df.iloc[:, :5] result = repr(df) assert result == expected + + def test_masked_ea_with_formatter(self): + # GH#39336 + df = DataFrame( + { + "a": Series([0.123456789, 1.123456789], dtype="Float64"), + "b": Series([1, 2], dtype="Int64"), + } + ) + result = df.to_string(formatters=["{:.2f}".format, "{:.2f}".format]) + expected = """ a b +0 0.12 1.00 +1 1.12 2.00""" + assert result == expected