Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use to incidate there are hidden rows #1064

Merged
merged 2 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 77 additions & 16 deletions lib/explorer/data_frame.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6017,7 +6017,17 @@ defmodule Explorer.DataFrame do

## Options

* `:limit` (non_neg_integer() | :infinity) - number of rows to print. Defaults to 5.
* `:limit` (non_neg_integer() | :infinity) - number of rows to print.
Defaults to 5.

* `:limit_dots` (`:bottom` | `:split`) - where to put the row of `…` when
the number of rows exceeds the print limit. For `:bottom`, we print
`:limit` rows followed by a row of `…`. For `:split`, we print half of
`:limit` rows above the `…` and half below. The top half comes from the
head of the dataframe and the bottom from the tail. Defaults to `:split`.

Also, any valid option to `TableRex.render!/2` will be accepted and will
override the defaults.

## Examples

Expand All @@ -6029,6 +6039,38 @@ defmodule Explorer.DataFrame do
@doc type: :introspection
@spec print(df :: DataFrame.t(), opts :: Keyword.t()) :: :ok
def print(df, opts \\ []) do
limit =
case opts[:limit] do
:infinity ->
:infinity

nil ->
@default_sample_nrows

limit when is_integer(limit) and limit >= 0 ->
limit

_ ->
raise ArgumentError,
"expected `:limit` to be a non-negative integer or `:infinity`, got: #{inspect(opts[:limit])}"
end

limit_dots =
case opts[:limit_dots] do
valid when valid in [:split, :bottom] ->
valid

nil ->
:split

_ ->
raise ArgumentError,
"expected `:limit_dots` to be `:split` or `:bottom`, got: #{inspect(opts[:limit_dots])}"
end

opts = Keyword.put(opts, :limit, limit)
opts = Keyword.put(opts, :limit_dots, limit_dots)

string =
if n_columns(df) == 0 do
empty_table_string()
Expand All @@ -6055,32 +6097,51 @@ defmodule Explorer.DataFrame do

headers = df.names

df =
case opts[:limit] do
:infinity -> df
nrow when is_integer(nrow) and nrow >= 0 -> slice(df, 0, nrow)
_ -> slice(df, 0, @default_sample_nrows)
values_list =
if opts[:limit] == :infinity do
[collect(df)]
else
limit_plus_1 = df |> slice(0, opts[:limit] + 1) |> collect()
dots = headers |> Map.new(&{&1, "…"}) |> List.wrap() |> new()

cond do
n_rows(limit_plus_1) <= opts[:limit] ->
[limit_plus_1]

opts[:limit_dots] == :split and opts[:limit] >= 2 ->
bottom_limit = div(opts[:limit], 2)
# For odd limits, the extra row goes on top.
top_limit = opts[:limit] - bottom_limit
top = slice(limit_plus_1, 0, top_limit)
bottom = slice(df, rows - bottom_limit, bottom_limit)
[top, dots, collect(bottom)]

true ->
top = slice(limit_plus_1, 0, opts[:limit])
[top, dots]
end
end
|> collect()

types = Enum.map(df.names, &"\n<#{Shared.dtype_to_string(df.dtypes[&1])}>")

values =
headers
|> Enum.map(&format_column(df[&1]))
|> Enum.zip_with(& &1)
Enum.flat_map(values_list, fn values ->
headers
|> Enum.map(&format_column(values[&1]))
|> Enum.zip_with(& &1)
end)

types = Enum.map(df.names, &"\n<#{Shared.dtype_to_string(df.dtypes[&1])}>")
name_type = Enum.zip_with(headers, types, fn x, y -> x <> y end)
composite_dtype? = &(match?({_, {:list, _}}, &1) or match?({_, {:struct, _}}, &1))
horizontal_style = if Enum.any?(df.dtypes, composite_dtype?), do: :all, else: :header
default_render_opts = [header_separator_symbol: "=", horizontal_style: horizontal_style]
render_opts = Keyword.drop(opts, [:limit, :limit_dots])

TableRex.Table.new()
|> TableRex.Table.put_title("Explorer DataFrame: [rows: #{rows}, columns: #{columns}]")
|> TableRex.Table.put_header(name_type)
|> TableRex.Table.put_header_meta(0..columns, align: :center)
|> TableRex.Table.add_rows(values)
|> TableRex.Table.render!(
header_separator_symbol: "=",
horizontal_style: :all
)
|> TableRex.Table.render!(Keyword.merge(default_render_opts, render_opts))
end

defp format_column(%Series{} = series) do
Expand Down
39 changes: 15 additions & 24 deletions test/explorer/data_frame_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2543,22 +2543,19 @@ defmodule Explorer.DataFrameTest do
df = Datasets.iris()

assert capture_io(fn -> DF.print(df) end) == """
+-----------------------------------------------------------------------+
| Explorer DataFrame: [rows: 150, columns: 5] |
+--------------+-------------+--------------+-------------+-------------+
| sepal_length | sepal_width | petal_length | petal_width | species |
| <f64> | <f64> | <f64> | <f64> | <string> |
+==============+=============+==============+=============+=============+
| 5.1 | 3.5 | 1.4 | 0.2 | Iris-setosa |
+--------------+-------------+--------------+-------------+-------------+
| 4.9 | 3.0 | 1.4 | 0.2 | Iris-setosa |
+--------------+-------------+--------------+-------------+-------------+
| 4.7 | 3.2 | 1.3 | 0.2 | Iris-setosa |
+--------------+-------------+--------------+-------------+-------------+
| 4.6 | 3.1 | 1.5 | 0.2 | Iris-setosa |
+--------------+-------------+--------------+-------------+-------------+
| 5.0 | 3.6 | 1.4 | 0.2 | Iris-setosa |
+--------------+-------------+--------------+-------------+-------------+
+--------------------------------------------------------------------------+
| Explorer DataFrame: [rows: 150, columns: 5] |
+--------------+-------------+--------------+-------------+----------------+
| sepal_length | sepal_width | petal_length | petal_width | species |
| <f64> | <f64> | <f64> | <f64> | <string> |
+==============+=============+==============+=============+================+
| 5.1 | 3.5 | 1.4 | 0.2 | Iris-setosa |
| 4.9 | 3.0 | 1.4 | 0.2 | Iris-setosa |
| 4.7 | 3.2 | 1.3 | 0.2 | Iris-setosa |
| … | … | … | … | … |
| 6.2 | 3.4 | 5.4 | 2.3 | Iris-virginica |
| 5.9 | 3.0 | 5.1 | 1.8 | Iris-virginica |
+--------------+-------------+--------------+-------------+----------------+

"""
end
Expand All @@ -2574,6 +2571,7 @@ defmodule Explorer.DataFrameTest do
| <f64> | <f64> | <f64> | <f64> | <string> |
+==============+=============+==============+=============+=============+
| 5.1 | 3.5 | 1.4 | 0.2 | Iris-setosa |
| … | … | … | … | … |
+--------------+-------------+--------------+-------------+-------------+

"""
Expand All @@ -2595,21 +2593,13 @@ defmodule Explorer.DataFrameTest do
| <s64> | <string> | <f64> |
+=============+================+=============+
| 1 | a | 9.1 |
+-------------+----------------+-------------+
| 2 | b | 8.2 |
+-------------+----------------+-------------+
| 3 | c | 7.3 |
+-------------+----------------+-------------+
| 4 | d | 6.4 |
+-------------+----------------+-------------+
| 5 | e | 5.5 |
+-------------+----------------+-------------+
| 6 | f | 4.6 |
+-------------+----------------+-------------+
| 7 | g | 3.7 |
+-------------+----------------+-------------+
| 8 | h | 2.8 |
+-------------+----------------+-------------+
| 9 | i | 1.9 |
+-------------+----------------+-------------+

Expand All @@ -2627,6 +2617,7 @@ defmodule Explorer.DataFrameTest do
| <f64> | <f64> | <f64> | <f64> | <string> |
+==============+=============+==============+=============+=============+
| 5.1 | 3.5 | 1.4 | 0.2 | Iris-setosa |
| … | … | … | … | … |
+--------------+-------------+--------------+-------------+-------------+

"""
Expand Down
Loading