diff --git a/py-polars/docs/source/reference/dataframe/descriptive.rst b/py-polars/docs/source/reference/dataframe/descriptive.rst index 00fba0d0cad1..ad166c830253 100644 --- a/py-polars/docs/source/reference/dataframe/descriptive.rst +++ b/py-polars/docs/source/reference/dataframe/descriptive.rst @@ -16,3 +16,4 @@ Descriptive DataFrame.n_chunks DataFrame.n_unique DataFrame.null_count + DataFrame.show diff --git a/py-polars/docs/source/reference/lazyframe/descriptive.rst b/py-polars/docs/source/reference/lazyframe/descriptive.rst index 0f05afae8960..0d499ea37e3f 100644 --- a/py-polars/docs/source/reference/lazyframe/descriptive.rst +++ b/py-polars/docs/source/reference/lazyframe/descriptive.rst @@ -9,3 +9,4 @@ Descriptive LazyFrame.describe LazyFrame.explain LazyFrame.show_graph + LazyFrame.show diff --git a/py-polars/polars/dataframe/frame.py b/py-polars/polars/dataframe/frame.py index 3403f8c12dac..b073e7b61a35 100644 --- a/py-polars/polars/dataframe/frame.py +++ b/py-polars/polars/dataframe/frame.py @@ -55,6 +55,7 @@ from polars._utils.serde import serialize_polars_object from polars._utils.unstable import issue_unstable_warning, unstable from polars._utils.various import ( + _in_notebook, is_bool_sequence, no_default, normalize_filepath, @@ -63,6 +64,7 @@ warn_null_comparison, ) from polars._utils.wrap import wrap_expr, wrap_ldf, wrap_s +from polars.config import Config from polars.dataframe._html import NotebookFormatter from polars.dataframe.group_by import DynamicGroupBy, GroupBy, RollingGroupBy from polars.dataframe.plotting import DataFramePlot @@ -147,6 +149,7 @@ CsvQuoteStyle, DbWriteEngine, FillNullStrategy, + FloatFmt, FrameInitTypes, IndexOrder, IntoExpr, @@ -176,6 +179,7 @@ UnstackDirection, ) from polars._utils.various import NoDefault + from polars.config import TableFormatNames from polars.interchange.dataframe import PolarsDataFrame from polars.ml.torch import PolarsDataset @@ -11263,6 +11267,216 @@ def melt( value_name=value_name, ) + def show( + self, + limit: int | None = 5, + *, + ascii_tables: bool | None = None, + decimal_separator: str | None = None, + thousands_separator: str | bool | None = None, + float_precision: int | None = None, + fmt_float: FloatFmt | None = None, + fmt_str_lengths: int | None = None, + fmt_table_cell_list_len: int | None = None, + tbl_cell_alignment: Literal["LEFT", "CENTER", "RIGHT"] | None = None, + tbl_cell_numeric_alignment: Literal["LEFT", "CENTER", "RIGHT"] | None = None, + tbl_cols: int | None = None, + tbl_column_data_type_inline: bool | None = None, + tbl_dataframe_shape_below: bool | None = None, + tbl_formatting: TableFormatNames | None = None, + tbl_hide_column_data_types: bool | None = None, + tbl_hide_column_names: bool | None = None, + tbl_hide_dtype_separator: bool | None = None, + tbl_hide_dataframe_shape: bool | None = None, + tbl_width_chars: int | None = None, + trim_decimal_zeros: bool | None = None, + ) -> None: + """ + Show the first `n` rows. + + Parameters + ---------- + limit : int + Numbers of rows to show. If a negative value is passed, return all rows + except the last `abs(n)`. If None is passed, return all rows. + ascii_tables : bool + Use ASCII characters to display table outlines. Set False to revert to the + default UTF8_FULL_CONDENSED formatting style. See + :func:`Config.set_ascii_tables` for more information. + decimal_separator : str + Set the decimal separator character. See + :func:`Config.set_decimal_separator` for more information. + thousands_separator : str + Set the thousands grouping separator character. See + :func:`Config.set_thousands_separator` for more information. + float_precision : int + Number of decimal places to display for floating point values. See + :func:`Config.set_float_precision` for more information. + fmt_float : {"mixed", "full"} + Control how floating point values are displayed. See + :func:`Config.set_fmt_float` for more information. Supported options are: + + * "mixed": Limit the number of decimal places and use scientific notation + for large/small values. + * "full": Print the full precision of the floating point number. + + fmt_str_lengths : int + Number of characters to display for string values. See + :func:`Config.set_fmt_str_lengths` for more information. + fmt_table_cell_list_len : int + Number of elements to display for List values. See + :func:`Config.set_fmt_table_cell_list_len` for more information. + tbl_cell_alignment : str + Set table cell alignment. See :func:`Config.set_tbl_cell_alignment` for more + information. Supported options are: + + * "LEFT": left aligned + * "CENTER": center aligned + * "RIGHT": right aligned + + tbl_cell_numeric_alignment : str + Set table cell alignment for numeric columns. See + :func:`Config.set_tbl_cell_numeric_alignment` for more information. + Supported options are: + + * "LEFT": left aligned + * "CENTER": center aligned + * "RIGHT": right aligned + + tbl_cols : int + Number of columns to display. See :func:`Config.set_tbl_cols` for more + information. + tbl_column_data_type_inline : bool + Moves the data type inline with the column name (to the right, in + parentheses). See :func:`Config.set_tbl_column_data_type_inline` for more + information. + tbl_dataframe_shape_below : bool + Print the DataFrame shape information below the data when displaying tables. + See :func:`Config.set_tbl_dataframe_shape_below` for more information. + tbl_formatting : str + Set table formatting style. See :func:`Config.set_tbl_formatting` for more + information. Supported options are: + + * "ASCII_FULL": ASCII, with all borders and lines, including row dividers. + * "ASCII_FULL_CONDENSED": Same as ASCII_FULL, but with dense row spacing. + * "ASCII_NO_BORDERS": ASCII, no borders. + * "ASCII_BORDERS_ONLY": ASCII, borders only. + * "ASCII_BORDERS_ONLY_CONDENSED": ASCII, borders only, dense row spacing. + * "ASCII_HORIZONTAL_ONLY": ASCII, horizontal lines only. + * "ASCII_MARKDOWN": Markdown format (ascii ellipses for truncated values). + * "MARKDOWN": Markdown format (utf8 ellipses for truncated values). + * "UTF8_FULL": UTF8, with all borders and lines, including row dividers. + * "UTF8_FULL_CONDENSED": Same as UTF8_FULL, but with dense row spacing. + * "UTF8_NO_BORDERS": UTF8, no borders. + * "UTF8_BORDERS_ONLY": UTF8, borders only. + * "UTF8_HORIZONTAL_ONLY": UTF8, horizontal lines only. + * "NOTHING": No borders or other lines. + + tbl_hide_column_data_types : bool + Hide table column data types (i64, f64, str etc.). See + :func:`Config.set_tbl_hide_column_data_types` for more information. + tbl_hide_column_names : bool + Hide table column names. See :func:`Config.set_tbl_hide_column_names` for + more information. + tbl_hide_dtype_separator : bool + Hide the '---' separator between the column names and column types. See + :func:`Config.set_tbl_hide_dtype_separator` for more information. + tbl_hide_dataframe_shape : bool + Hide the DataFrame shape information when displaying tables. See + :func:`Config.set_tbl_hide_dataframe_shape` for more information. + tbl_width_chars : int + Set the maximum width of a table in characters. See + :func:`Config.set_tbl_width_chars` for more information. + trim_decimal_zeros : bool + Strip trailing zeros from Decimal data type values. See + :func:`Config.set_trim_decimal_zeros` for more information. + + See Also + -------- + head + + Examples + -------- + >>> df = pl.DataFrame( + ... { + ... "foo": [1, 2, 3, 4, 5], + ... "bar": [6, 7, 8, 9, 10], + ... "ham": ["a", "b", "c", "d", "e"], + ... } + ... ) + >>> df.show(3) + shape: (3, 3) + ┌─────┬─────┬─────┐ + │ foo ┆ bar ┆ ham │ + │ --- ┆ --- ┆ --- │ + │ i64 ┆ i64 ┆ str │ + ╞═════╪═════╪═════╡ + │ 1 ┆ 6 ┆ a │ + │ 2 ┆ 7 ┆ b │ + │ 3 ┆ 8 ┆ c │ + └─────┴─────┴─────┘ + + Pass a negative value to get all rows `except` the last `abs(n)`. + + >>> df.show(-3) + shape: (2, 3) + ┌─────┬─────┬─────┐ + │ foo ┆ bar ┆ ham │ + │ --- ┆ --- ┆ --- │ + │ i64 ┆ i64 ┆ str │ + ╞═════╪═════╪═════╡ + │ 1 ┆ 6 ┆ a │ + │ 2 ┆ 7 ┆ b │ + └─────┴─────┴─────┘ + """ + if limit is None: + df = self + tbl_rows = -1 + else: + df = self.head(limit) + if limit < 0: + tbl_rows = self.height - abs(limit) + else: + tbl_rows = limit + + if ascii_tables is not None and tbl_formatting is not None: + msg = "Can not set `ascii_tables` and `tbl_formatting` at the same time." + raise ValueError(msg) + if ascii_tables is not None: + if ascii_tables: + tbl_formatting = "ASCII_FULL_CONDENSED" + else: + tbl_formatting = "UTF8_FULL_CONDENSED" + + with Config( + ascii_tables=ascii_tables, + decimal_separator=decimal_separator, + thousands_separator=thousands_separator, + float_precision=float_precision, + fmt_float=fmt_float, + fmt_str_lengths=fmt_str_lengths, + fmt_table_cell_list_len=fmt_table_cell_list_len, + tbl_cell_alignment=tbl_cell_alignment, + tbl_cell_numeric_alignment=tbl_cell_numeric_alignment, + tbl_cols=tbl_cols, + tbl_column_data_type_inline=tbl_column_data_type_inline, + tbl_dataframe_shape_below=tbl_dataframe_shape_below, + tbl_formatting=tbl_formatting, + tbl_hide_column_data_types=tbl_hide_column_data_types, + tbl_hide_column_names=tbl_hide_column_names, + tbl_hide_dtype_separator=tbl_hide_dtype_separator, + tbl_hide_dataframe_shape=tbl_hide_dataframe_shape, + tbl_rows=tbl_rows, + tbl_width_chars=tbl_width_chars, + trim_decimal_zeros=trim_decimal_zeros, + ): + if _in_notebook(): + from IPython.display import display_html + + display_html(df) + else: + print(df) + def _to_metadata( self, columns: None | str | list[str] = None, diff --git a/py-polars/polars/lazyframe/frame.py b/py-polars/polars/lazyframe/frame.py index 9b438ae8dbfa..9448d4bffad1 100644 --- a/py-polars/polars/lazyframe/frame.py +++ b/py-polars/polars/lazyframe/frame.py @@ -103,6 +103,7 @@ EngineType, ExplainFormat, FillNullStrategy, + FloatFmt, FrameInitTypes, IntoExpr, IntoExprColumn, @@ -118,6 +119,7 @@ StartBy, UniqueKeepStrategy, ) + from polars.config import TableFormatNames from polars.dependencies import numpy as np if sys.version_info >= (3, 10): @@ -6907,6 +6909,200 @@ def melt( streamable=streamable, ) + def show( + self, + limit: int | None = 5, + *, + ascii_tables: bool | None = None, + decimal_separator: str | None = None, + thousands_separator: str | bool | None = None, + float_precision: int | None = None, + fmt_float: FloatFmt | None = None, + fmt_str_lengths: int | None = None, + fmt_table_cell_list_len: int | None = None, + tbl_cell_alignment: Literal["LEFT", "CENTER", "RIGHT"] | None = None, + tbl_cell_numeric_alignment: Literal["LEFT", "CENTER", "RIGHT"] | None = None, + tbl_cols: int | None = None, + tbl_column_data_type_inline: bool | None = None, + tbl_dataframe_shape_below: bool | None = None, + tbl_formatting: TableFormatNames | None = None, + tbl_hide_column_data_types: bool | None = None, + tbl_hide_column_names: bool | None = None, + tbl_hide_dtype_separator: bool | None = None, + tbl_hide_dataframe_shape: bool | None = None, + tbl_width_chars: int | None = None, + trim_decimal_zeros: bool | None = None, + ) -> None: + """ + Show the first `n` rows. + + Parameters + ---------- + limit : int + Number of rows to show. If None is passed, return all rows. + ascii_tables : bool + Use ASCII characters to display table outlines. Set False to revert to the + default UTF8_FULL_CONDENSED formatting style. See + :func:`Config.set_ascii_tables` for more information. + decimal_separator : str + Set the decimal separator character. See + :func:`Config.set_decimal_separator` for more information. + thousands_separator : str + Set the thousands grouping separator character. See + :func:`Config.set_thousands_separator` for more information. + float_precision : int + Number of decimal places to display for floating point values. See + :func:`Config.set_float_precision` for more information. + fmt_float : {"mixed", "full"} + Control how floating point values are displayed. See + :func:`Config.set_fmt_float` for more information. Supported options are: + + * "mixed": Limit the number of decimal places and use scientific notation + for large/small values. + * "full": Print the full precision of the floating point number. + + fmt_str_lengths : int + Number of characters to display for string values. See + :func:`Config.set_fmt_str_lengths` for more information. + fmt_table_cell_list_len : int + Number of elements to display for List values. See + :func:`Config.set_fmt_table_cell_list_len` for more information. + tbl_cell_alignment : str + Set table cell alignment. See :func:`Config.set_tbl_cell_alignment` for more + information. Supported options are: + + * "LEFT": left aligned + * "CENTER": center aligned + * "RIGHT": right aligned + + tbl_cell_numeric_alignment : str + Set table cell alignment for numeric columns. See + :func:`Config.set_tbl_cell_numeric_alignment` for more information. + Supported options are: + + * "LEFT": left aligned + * "CENTER": center aligned + * "RIGHT": right aligned + + tbl_cols : int + Number of columns to display. See :func:`Config.set_tbl_cols` for more + information. + tbl_column_data_type_inline : bool + Moves the data type inline with the column name (to the right, in + parentheses). See :func:`Config.set_tbl_column_data_type_inline` for more + information. + tbl_dataframe_shape_below : bool + Print the DataFrame shape information below the data when displaying tables. + See :func:`Config.set_tbl_dataframe_shape_below` for more information. + tbl_formatting : str + Set table formatting style. See :func:`Config.set_tbl_formatting` for more + information. Supported options are: + + * "ASCII_FULL": ASCII, with all borders and lines, including row dividers. + * "ASCII_FULL_CONDENSED": Same as ASCII_FULL, but with dense row spacing. + * "ASCII_NO_BORDERS": ASCII, no borders. + * "ASCII_BORDERS_ONLY": ASCII, borders only. + * "ASCII_BORDERS_ONLY_CONDENSED": ASCII, borders only, dense row spacing. + * "ASCII_HORIZONTAL_ONLY": ASCII, horizontal lines only. + * "ASCII_MARKDOWN": Markdown format (ascii ellipses for truncated values). + * "MARKDOWN": Markdown format (utf8 ellipses for truncated values). + * "UTF8_FULL": UTF8, with all borders and lines, including row dividers. + * "UTF8_FULL_CONDENSED": Same as UTF8_FULL, but with dense row spacing. + * "UTF8_NO_BORDERS": UTF8, no borders. + * "UTF8_BORDERS_ONLY": UTF8, borders only. + * "UTF8_HORIZONTAL_ONLY": UTF8, horizontal lines only. + * "NOTHING": No borders or other lines. + + tbl_hide_column_data_types : bool + Hide table column data types (i64, f64, str etc.). See + :func:`Config.set_tbl_hide_column_data_types` for more information. + tbl_hide_column_names : bool + Hide table column names. See :func:`Config.set_tbl_hide_column_names` for + more information. + tbl_hide_dtype_separator : bool + Hide the '---' separator between the column names and column types. See + :func:`Config.set_tbl_hide_dtype_separator` for more information. + tbl_hide_dataframe_shape : bool + Hide the DataFrame shape information when displaying tables. See + :func:`Config.set_tbl_hide_dataframe_shape` for more information. + tbl_width_chars : int + Set the maximum width of a table in characters. See + :func:`Config.set_tbl_width_chars` for more information. + trim_decimal_zeros : bool + Strip trailing zeros from Decimal data type values. See + :func:`Config.set_trim_decimal_zeros` for more information. + + Warnings + -------- + * This method does *not* maintain the laziness of the frame, and will `collect` + the final result. This could potentially be an expensive operation. + + Examples + -------- + >>> lf = pl.LazyFrame( + ... { + ... "a": [1, 2, 3, 4, 5, 6], + ... "b": [7, 8, 9, 10, 11, 12], + ... } + ... ) + >>> lf.show() + shape: (5, 2) + ┌─────┬─────┐ + │ a ┆ b │ + │ --- ┆ --- │ + │ i64 ┆ i64 │ + ╞═════╪═════╡ + │ 1 ┆ 7 │ + │ 2 ┆ 8 │ + │ 3 ┆ 9 │ + │ 4 ┆ 10 │ + │ 5 ┆ 11 │ + └─────┴─────┘ + >>> lf.show(2) + shape: (2, 2) + ┌─────┬─────┐ + │ a ┆ b │ + │ --- ┆ --- │ + │ i64 ┆ i64 │ + ╞═════╪═════╡ + │ 1 ┆ 7 │ + │ 2 ┆ 8 │ + └─────┴─────┘ + """ + if limit is None: + lf = self + issue_warning( + "Showing a LazyFrame without a limit requires collecting the whole " + "LazyFrame, which could be an expensive operation. Set a limit to " + "show the LazyFrame without this warning.", + category=PerformanceWarning, + ) + else: + lf = self.head(limit) + + lf.collect().show( + limit, + ascii_tables=ascii_tables, + decimal_separator=decimal_separator, + thousands_separator=thousands_separator, + float_precision=float_precision, + fmt_float=fmt_float, + fmt_str_lengths=fmt_str_lengths, + fmt_table_cell_list_len=fmt_table_cell_list_len, + tbl_cell_alignment=tbl_cell_alignment, + tbl_cell_numeric_alignment=tbl_cell_numeric_alignment, + tbl_cols=tbl_cols, + tbl_column_data_type_inline=tbl_column_data_type_inline, + tbl_dataframe_shape_below=tbl_dataframe_shape_below, + tbl_formatting=tbl_formatting, + tbl_hide_column_data_types=tbl_hide_column_data_types, + tbl_hide_column_names=tbl_hide_column_names, + tbl_hide_dtype_separator=tbl_hide_dtype_separator, + tbl_hide_dataframe_shape=tbl_hide_dataframe_shape, + tbl_width_chars=tbl_width_chars, + trim_decimal_zeros=trim_decimal_zeros, + ) + def _to_metadata( self, columns: None | str | list[str] = None, diff --git a/py-polars/tests/unit/dataframe/test_show.py b/py-polars/tests/unit/dataframe/test_show.py new file mode 100644 index 000000000000..7e5e5d593da0 --- /dev/null +++ b/py-polars/tests/unit/dataframe/test_show.py @@ -0,0 +1,645 @@ +import pytest + +import polars as pl +from polars.config import TableFormatNames + + +def test_df_show_default(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame( + { + "foo": [1, 2, 3, 4, 5, 6, 7], + "bar": ["a", "b", "c", "d", "e", "f", "g"], + } + ) + + df.show() + out, _ = capsys.readouterr() + assert ( + out + == """shape: (5, 2) +┌─────┬─────┐ +│ foo ┆ bar │ +│ --- ┆ --- │ +│ i64 ┆ str │ +╞═════╪═════╡ +│ 1 ┆ a │ +│ 2 ┆ b │ +│ 3 ┆ c │ +│ 4 ┆ d │ +│ 5 ┆ e │ +└─────┴─────┘ +""" + ) + + +def test_df_show_positive_limit(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame( + { + "foo": [1, 2, 3, 4, 5, 6, 7], + "bar": ["a", "b", "c", "d", "e", "f", "g"], + } + ) + + df.show(3) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (3, 2) +┌─────┬─────┐ +│ foo ┆ bar │ +│ --- ┆ --- │ +│ i64 ┆ str │ +╞═════╪═════╡ +│ 1 ┆ a │ +│ 2 ┆ b │ +│ 3 ┆ c │ +└─────┴─────┘ +""" + ) + + +def test_df_show_negative_limit(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame( + { + "foo": [1, 2, 3, 4, 5, 6, 7], + "bar": ["a", "b", "c", "d", "e", "f", "g"], + } + ) + + df.show(-5) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (2, 2) +┌─────┬─────┐ +│ foo ┆ bar │ +│ --- ┆ --- │ +│ i64 ┆ str │ +╞═════╪═════╡ +│ 1 ┆ a │ +│ 2 ┆ b │ +└─────┴─────┘ +""" + ) + + +def test_df_show_no_limit(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame( + { + "foo": [1, 2, 3, 4, 5, 6, 7], + "bar": ["a", "b", "c", "d", "e", "f", "g"], + } + ) + + df.show(limit=None) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (7, 2) +┌─────┬─────┐ +│ foo ┆ bar │ +│ --- ┆ --- │ +│ i64 ┆ str │ +╞═════╪═════╡ +│ 1 ┆ a │ +│ 2 ┆ b │ +│ 3 ┆ c │ +│ 4 ┆ d │ +│ 5 ┆ e │ +│ 6 ┆ f │ +│ 7 ┆ g │ +└─────┴─────┘ +""" + ) + + +@pl.Config(ascii_tables=False) +def test_df_show_ascii_tables(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame({"abc": [1.0, 2.5, 5.0], "xyz": [True, False, True]}) + + df.show(ascii_tables=True) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (3, 2) ++-----+-------+ +| abc | xyz | +| --- | --- | +| f64 | bool | ++=============+ +| 1.0 | true | +| 2.5 | false | +| 5.0 | true | ++-----+-------+ +""" + ) + + +@pytest.mark.parametrize( + ("ascii_tables", "tbl_formatting"), + [ + (True, "ASCII_FULL_CONDENSED"), + (True, "UTF8_FULL_CONDENSED"), + (False, "ASCII_FULL_CONDENSED"), + (False, "UTF8_FULL_CONDENSED"), + ], +) +def test_df_show_cannot_set_ascii_tables_and_tbl_formatting( + ascii_tables: bool, tbl_formatting: TableFormatNames +) -> None: + df = pl.DataFrame() + + with pytest.raises(ValueError): + df.show(ascii_tables=ascii_tables, tbl_formatting=tbl_formatting) + + +@pl.Config(decimal_separator=",") +def test_df_show_decimal_separator(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame({"v": [9876.54321, 1010101.0, -123456.78]}) + + df.show(decimal_separator=",") + out, _ = capsys.readouterr() + assert ( + out + == """shape: (3, 1) +┌────────────┐ +│ v │ +│ --- │ +│ f64 │ +╞════════════╡ +│ 9876,54321 │ +│ 1,010101e6 │ +│ -123456,78 │ +└────────────┘ +""" + ) + + +@pl.Config(thousands_separator=".") +def test_df_show_thousands_separator(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame({"v": [9876.54321, 1010101.0, -123456.78]}) + + df.show(thousands_separator=" ") + out, _ = capsys.readouterr() + assert ( + out + == """shape: (3, 1) +┌─────────────┐ +│ v │ +│ --- │ +│ f64 │ +╞═════════════╡ +│ 9 876.54321 │ +│ 1.010101e6 │ +│ -123 456.78 │ +└─────────────┘ +""" + ) + + +@pl.Config(float_precision=8) +def test_df_show_float_precision(capsys: pytest.CaptureFixture[str]) -> None: + from math import e, pi + + df = pl.DataFrame({"const": ["pi", "e"], "value": [pi, e]}) + + df.show(float_precision=15) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (2, 2) +┌───────┬───────────────────┐ +│ const ┆ value │ +│ --- ┆ --- │ +│ str ┆ f64 │ +╞═══════╪═══════════════════╡ +│ pi ┆ 3.141592653589793 │ +│ e ┆ 2.718281828459045 │ +└───────┴───────────────────┘ +""" + ) + + df.show(float_precision=3) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (2, 2) +┌───────┬───────┐ +│ const ┆ value │ +│ --- ┆ --- │ +│ str ┆ f64 │ +╞═══════╪═══════╡ +│ pi ┆ 3.142 │ +│ e ┆ 2.718 │ +└───────┴───────┘ +""" + ) + + +@pl.Config(fmt_float="full") +def test_df_show_fmt_float(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame({"num": [1.2304980958725870923, 1e6, 1e-8]}) + + df.show(fmt_float="mixed") + out, _ = capsys.readouterr() + assert ( + out + == """shape: (3, 1) +┌───────────┐ +│ num │ +│ --- │ +│ f64 │ +╞═══════════╡ +│ 1.230498 │ +│ 1e6 │ +│ 1.0000e-8 │ +└───────────┘ +""" + ) + + +@pl.Config(fmt_str_lengths=20) +def test_df_show_fmt_str_lengths(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame( + { + "txt": [ + "Play it, Sam. Play 'As Time Goes By'.", + "This is the beginning of a beautiful friendship.", + ] + } + ) + + df.show(fmt_str_lengths=10) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (2, 1) +┌─────────────┐ +│ txt │ +│ --- │ +│ str │ +╞═════════════╡ +│ Play it, S… │ +│ This is th… │ +└─────────────┘ +""" + ) + + df.show(fmt_str_lengths=50) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (2, 1) +┌──────────────────────────────────────────────────┐ +│ txt │ +│ --- │ +│ str │ +╞══════════════════════════════════════════════════╡ +│ Play it, Sam. Play 'As Time Goes By'. │ +│ This is the beginning of a beautiful friendship. │ +└──────────────────────────────────────────────────┘ +""" + ) + + +@pl.Config(fmt_table_cell_list_len=5) +def test_df_show_fmt_table_cell_list_len(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame({"nums": [list(range(10))]}) + + df.show(fmt_table_cell_list_len=2) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (1, 1) +┌───────────┐ +│ nums │ +│ --- │ +│ list[i64] │ +╞═══════════╡ +│ [0, … 9] │ +└───────────┘ +""" + ) + + df.show(fmt_table_cell_list_len=8) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (1, 1) +┌────────────────────────────┐ +│ nums │ +│ --- │ +│ list[i64] │ +╞════════════════════════════╡ +│ [0, 1, 2, 3, 4, 5, 6, … 9] │ +└────────────────────────────┘ +""" + ) + + +@pl.Config(tbl_cell_alignment="LEFT") +def test_df_show_tbl_cell_alignment(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame( + {"column_abc": [1.0, 2.5, 5.0], "column_xyz": [True, False, True]} + ) + + df.show(tbl_cell_alignment="RIGHT") + out, _ = capsys.readouterr() + assert ( + out + == """shape: (3, 2) +┌────────────┬────────────┐ +│ column_abc ┆ column_xyz │ +│ --- ┆ --- │ +│ f64 ┆ bool │ +╞════════════╪════════════╡ +│ 1.0 ┆ true │ +│ 2.5 ┆ false │ +│ 5.0 ┆ true │ +└────────────┴────────────┘ +""" + ) + + +@pl.Config(tbl_cell_numeric_alignment="LEFT") +def test_df_show_tbl_cell_numeric_alignment(capsys: pytest.CaptureFixture[str]) -> None: + from datetime import date + + df = pl.DataFrame( + { + "abc": [11, 2, 333], + "mno": [date(2023, 10, 29), None, date(2001, 7, 5)], + "xyz": [True, False, None], + } + ) + + df.show(tbl_cell_numeric_alignment="RIGHT") + out, _ = capsys.readouterr() + assert ( + out + == """shape: (3, 3) +┌─────┬────────────┬───────┐ +│ abc ┆ mno ┆ xyz │ +│ --- ┆ --- ┆ --- │ +│ i64 ┆ date ┆ bool │ +╞═════╪════════════╪═══════╡ +│ 11 ┆ 2023-10-29 ┆ true │ +│ 2 ┆ null ┆ false │ +│ 333 ┆ 2001-07-05 ┆ null │ +└─────┴────────────┴───────┘ +""" + ) + + +@pl.Config(tbl_cols=2) +def test_df_show_tbl_cols(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame({str(i): [i] for i in range(10)}) + + df.show(tbl_cols=3) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (1, 10) +┌─────┬─────┬───┬─────┐ +│ 0 ┆ 1 ┆ … ┆ 9 │ +│ --- ┆ --- ┆ ┆ --- │ +│ i64 ┆ i64 ┆ ┆ i64 │ +╞═════╪═════╪═══╪═════╡ +│ 0 ┆ 1 ┆ … ┆ 9 │ +└─────┴─────┴───┴─────┘ +""" + ) + + df.show(tbl_cols=7) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (1, 10) +┌─────┬─────┬─────┬─────┬───┬─────┬─────┬─────┐ +│ 0 ┆ 1 ┆ 2 ┆ 3 ┆ … ┆ 7 ┆ 8 ┆ 9 │ +│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- │ +│ i64 ┆ i64 ┆ i64 ┆ i64 ┆ ┆ i64 ┆ i64 ┆ i64 │ +╞═════╪═════╪═════╪═════╪═══╪═════╪═════╪═════╡ +│ 0 ┆ 1 ┆ 2 ┆ 3 ┆ … ┆ 7 ┆ 8 ┆ 9 │ +└─────┴─────┴─────┴─────┴───┴─────┴─────┴─────┘ +""" + ) + + +@pl.Config(tbl_column_data_type_inline=False) +def test_df_show_tbl_column_data_type_inline( + capsys: pytest.CaptureFixture[str], +) -> None: + df = pl.DataFrame({"abc": [1.0, 2.5, 5.0], "xyz": [True, False, True]}) + + df.show(tbl_column_data_type_inline=True) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (3, 2) +┌───────────┬────────────┐ +│ abc (f64) ┆ xyz (bool) │ +╞═══════════╪════════════╡ +│ 1.0 ┆ true │ +│ 2.5 ┆ false │ +│ 5.0 ┆ true │ +└───────────┴────────────┘ +""" + ) + + +@pl.Config(tbl_dataframe_shape_below=False) +def test_df_show_tbl_dataframe_shape_below(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame({"abc": [1.0, 2.5, 5.0], "xyz": [True, False, True]}) + + df.show(tbl_dataframe_shape_below=True) + out, _ = capsys.readouterr() + assert out == ( + "┌─────┬───────┐\n" + "│ abc ┆ xyz │\n" + "│ --- ┆ --- │\n" + "│ f64 ┆ bool │\n" + "╞═════╪═══════╡\n" + "│ 1.0 ┆ true │\n" + "│ 2.5 ┆ false │\n" + "│ 5.0 ┆ true │\n" + "└─────┴───────┘\n" + "shape: (3, 2)\n" + ) + + +@pl.Config(tbl_formatting="UTF8_FULL") +def test_df_show_tbl_formatting(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]}) + + df.show(tbl_formatting="ASCII_FULL") + out, _ = capsys.readouterr() + assert ( + out + == """shape: (3, 3) ++-----+-----+-----+ +| a | b | c | +| --- | --- | --- | +| i64 | i64 | i64 | ++=================+ +| 1 | 4 | 7 | +|-----+-----+-----| +| 2 | 5 | 8 | +|-----+-----+-----| +| 3 | 6 | 9 | ++-----+-----+-----+ +""" + ) + + df.show(tbl_formatting="MARKDOWN") + out, _ = capsys.readouterr() + assert ( + out + == """shape: (3, 3) +| a | b | c | +| --- | --- | --- | +| i64 | i64 | i64 | +|-----|-----|-----| +| 1 | 4 | 7 | +| 2 | 5 | 8 | +| 3 | 6 | 9 | +""" + ) + + +@pl.Config(tbl_hide_column_data_types=False) +def test_df_show_tbl_hide_column_data_types(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame({"abc": [1.0, 2.5, 5.0], "xyz": [True, False, True]}) + + df.show(tbl_hide_column_data_types=True) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (3, 2) +┌─────┬───────┐ +│ abc ┆ xyz │ +╞═════╪═══════╡ +│ 1.0 ┆ true │ +│ 2.5 ┆ false │ +│ 5.0 ┆ true │ +└─────┴───────┘ +""" + ) + + +@pl.Config(tbl_hide_column_names=False) +def test_df_show_tbl_hide_column_names(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame({"abc": [1.0, 2.5, 5.0], "xyz": [True, False, True]}) + + df.show(tbl_hide_column_names=True) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (3, 2) +┌─────┬───────┐ +│ f64 ┆ bool │ +╞═════╪═══════╡ +│ 1.0 ┆ true │ +│ 2.5 ┆ false │ +│ 5.0 ┆ true │ +└─────┴───────┘ +""" + ) + + +@pl.Config(tbl_hide_dtype_separator=False) +def test_df_show_tbl_hide_dtype_separator(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame({"abc": [1.0, 2.5, 5.0], "xyz": [True, False, True]}) + + df.show(tbl_hide_dtype_separator=True) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (3, 2) +┌─────┬───────┐ +│ abc ┆ xyz │ +│ f64 ┆ bool │ +╞═════╪═══════╡ +│ 1.0 ┆ true │ +│ 2.5 ┆ false │ +│ 5.0 ┆ true │ +└─────┴───────┘ +""" + ) + + +@pl.Config(tbl_hide_dataframe_shape=False) +def test_df_show_tbl_hide_dataframe_shape(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame({"abc": [1.0, 2.5, 5.0], "xyz": [True, False, True]}) + + df.show(tbl_hide_dataframe_shape=True) + out, _ = capsys.readouterr() + assert out == ( + "┌─────┬───────┐\n" + "│ abc ┆ xyz │\n" + "│ --- ┆ --- │\n" + "│ f64 ┆ bool │\n" + "╞═════╪═══════╡\n" + "│ 1.0 ┆ true │\n" + "│ 2.5 ┆ false │\n" + "│ 5.0 ┆ true │\n" + "└─────┴───────┘\n" + ) + + +@pl.Config(tbl_width_chars=100) +def test_df_show_tbl_width_chars(capsys: pytest.CaptureFixture[str]) -> None: + df = pl.DataFrame( + { + "id": ["SEQ1", "SEQ2"], + "seq": ["ATGATAAAGGAG", "GCAACGCATATA"], + } + ) + + df.show(tbl_width_chars=12) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (2, 2) +┌─────┬─────┐ +│ id ┆ seq │ +│ --- ┆ --- │ +│ str ┆ str │ +╞═════╪═════╡ +│ SEQ ┆ ATG │ +│ 1 ┆ ATA │ +│ ┆ AAG │ +│ ┆ GAG │ +│ SEQ ┆ GCA │ +│ 2 ┆ ACG │ +│ ┆ CAT │ +│ ┆ ATA │ +└─────┴─────┘ +""" + ) + + +@pl.Config(trim_decimal_zeros=False) +def test_df_show_trim_decimal_zeros(capsys: pytest.CaptureFixture[str]) -> None: + from decimal import Decimal as D + + df = pl.DataFrame( + data={"d": [D("1.01000"), D("-5.67890")]}, + schema={"d": pl.Decimal(scale=5)}, + ) + + df.show(trim_decimal_zeros=True) + out, _ = capsys.readouterr() + assert ( + out + == """shape: (2, 1) +┌──────────────┐ +│ d │ +│ --- │ +│ decimal[*,5] │ +╞══════════════╡ +│ 1.01 │ +│ -5.6789 │ +└──────────────┘ +""" + ) diff --git a/py-polars/tests/unit/lazyframe/test_show.py b/py-polars/tests/unit/lazyframe/test_show.py new file mode 100644 index 000000000000..41ea0156138e --- /dev/null +++ b/py-polars/tests/unit/lazyframe/test_show.py @@ -0,0 +1,46 @@ +from inspect import signature +from unittest.mock import patch + +import pytest + +import polars as pl +from polars.exceptions import PerformanceWarning + + +def test_show_signature_match() -> None: + assert signature(pl.LazyFrame.show) == signature(pl.DataFrame.show) + + +def test_lf_show_calls_df_show() -> None: + lf = pl.LazyFrame({}) + with patch.object(pl.DataFrame, "show") as df_show: + lf.show(5) + + df_show.assert_called_once_with( + 5, + ascii_tables=None, + decimal_separator=None, + thousands_separator=None, + float_precision=None, + fmt_float=None, + fmt_str_lengths=None, + fmt_table_cell_list_len=None, + tbl_cell_alignment=None, + tbl_cell_numeric_alignment=None, + tbl_cols=None, + tbl_column_data_type_inline=None, + tbl_dataframe_shape_below=None, + tbl_formatting=None, + tbl_hide_column_data_types=None, + tbl_hide_column_names=None, + tbl_hide_dtype_separator=None, + tbl_hide_dataframe_shape=None, + tbl_width_chars=None, + trim_decimal_zeros=None, + ) + + +def test_lf_show_no_limit_issues_warning() -> None: + lf = pl.LazyFrame({}) + with pytest.warns(PerformanceWarning): + lf.show(limit=None)