Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
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
5 changes: 5 additions & 0 deletions marimo/_data/get_datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ def _get_databases_from_duckdb_internal(
# databases_dict[database][schema] = [table1, table2, ...]
databases_dict: dict[str, dict[str, list[DataTable]]] = {}

SKIP_TABLES = ["duckdb_functions()", "duckdb_types()", "duckdb_settings()"]

for (
database,
schema,
Expand All @@ -141,6 +143,9 @@ def _get_databases_from_duckdb_internal(
column_types,
*_rest,
) in tables_result:
if name in SKIP_TABLES:
continue

assert len(column_names) == len(column_types)
assert isinstance(column_names, list)
assert isinstance(column_types, list)
Expand Down
77 changes: 58 additions & 19 deletions marimo/_plugins/ui/_impl/tables/narwhals_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import functools
import io
from functools import cached_property
from typing import Any, Optional, Union, cast
from typing import Any, Literal, Optional, Union, cast

import msgspec
import narwhals.stable.v2 as nw
Expand Down Expand Up @@ -342,13 +342,22 @@ def _get_stats_internal(self, column: str) -> ColumnStats:
"nulls": col.null_count(),
}

# As of Sep 2025, pyarrow and ibis do not support quantiles
# As of Oct 2025, pyarrow and ibis do not support quantiles
# through narwhals
supports_quantiles = (
supports_numeric_quantiles = (
not frame.implementation.is_pyarrow()
and not frame.implementation.is_ibis()
)
supports_temporal_quantiles = (
not frame.implementation.is_pyarrow()
and not frame.implementation.is_ibis()
)

quantile_interpolation: Literal["nearest", "linear"] = "nearest"
if frame.implementation.is_duckdb():
# As of Oct 2025, DuckDB does not support "nearest" interpolation
quantile_interpolation = "linear"

if is_narwhals_string_type(dtype):
exprs["unique"] = col.n_unique()
elif dtype == nw.Boolean:
Expand Down Expand Up @@ -401,15 +410,25 @@ def _get_stats_internal(self, column: str) -> ColumnStats:
"max": col.max(),
}
)
if supports_quantiles:
if supports_temporal_quantiles:
exprs.update(
{
"mean": col.mean(),
"median": col.quantile(0.5, interpolation="nearest"),
"p5": col.quantile(0.05, interpolation="nearest"),
"p25": col.quantile(0.25, interpolation="nearest"),
"p75": col.quantile(0.75, interpolation="nearest"),
"p95": col.quantile(0.95, interpolation="nearest"),
"median": col.quantile(
0.5, interpolation=quantile_interpolation
),
"p5": col.quantile(
0.05, interpolation=quantile_interpolation
),
"p25": col.quantile(
0.25, interpolation=quantile_interpolation
),
"p75": col.quantile(
0.75, interpolation=quantile_interpolation
),
"p95": col.quantile(
0.95, interpolation=quantile_interpolation
),
}
)
elif is_narwhals_integer_type(dtype):
Expand All @@ -423,13 +442,21 @@ def _get_stats_internal(self, column: str) -> ColumnStats:
"median": col.median(),
}
)
if supports_quantiles:
if supports_numeric_quantiles:
exprs.update(
{
"p5": col.quantile(0.05, interpolation="nearest"),
"p25": col.quantile(0.25, interpolation="nearest"),
"p75": col.quantile(0.75, interpolation="nearest"),
"p95": col.quantile(0.95, interpolation="nearest"),
"p5": col.quantile(
0.05, interpolation=quantile_interpolation
),
"p25": col.quantile(
0.25, interpolation=quantile_interpolation
),
"p75": col.quantile(
0.75, interpolation=quantile_interpolation
),
"p95": col.quantile(
0.95, interpolation=quantile_interpolation
),
}
)
elif dtype.is_numeric():
Expand All @@ -443,13 +470,21 @@ def _get_stats_internal(self, column: str) -> ColumnStats:
"median": col.median(),
}
)
if supports_quantiles:
if supports_numeric_quantiles:
exprs.update(
{
"p5": col.quantile(0.05, interpolation="nearest"),
"p25": col.quantile(0.25, interpolation="nearest"),
"p75": col.quantile(0.75, interpolation="nearest"),
"p95": col.quantile(0.95, interpolation="nearest"),
"p5": col.quantile(
0.05, interpolation=quantile_interpolation
),
"p25": col.quantile(
0.25, interpolation=quantile_interpolation
),
"p75": col.quantile(
0.75, interpolation=quantile_interpolation
),
"p95": col.quantile(
0.95, interpolation=quantile_interpolation
),
}
)

Expand All @@ -461,6 +496,10 @@ def _get_stats_internal(self, column: str) -> ColumnStats:
if key in units:
stats_dict[key] = f"{value} {units[key]}"

# Maybe coerce null count to int
if stats_dict["nulls"] is not None:
stats_dict["nulls"] = int(stats_dict["nulls"])

return ColumnStats(**stats_dict)

def get_bin_values(self, column: str, num_bins: int) -> list[BinValue]:
Expand Down
5 changes: 3 additions & 2 deletions tests/_cli/test_cli_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import pytest

from marimo._dependencies.dependencies import DependencyManager
from marimo._utils.platform import is_windows
from tests._server.templates.utils import normalize_index_html
from tests.mocks import snapshotter

Expand Down Expand Up @@ -922,8 +923,8 @@ def test_export_ipynb_with_multiple_definitions(
assert p.stdout.decode() == ""

@pytest.mark.skipif(
not DependencyManager.nbformat.has(),
reason="This test requires nbformat.",
not DependencyManager.nbformat.has() or is_windows(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is an actual error, but just cosmetic so not worried

reason="This test requires nbformat. Or windows.",
)
def test_export_ipynb_with_errors(
self, temp_marimo_file_with_errors: str
Expand Down
2 changes: 1 addition & 1 deletion tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ def assert_serialize_roundtrip(obj: msgspec.Struct) -> None:
serialized = encode_json_bytes(obj)
cls = type(obj)
parsed = parse_raw(serialized, cls)
assert asdict(obj) == asdict(parsed)
assert asdict(obj) == asdict(parsed), f"{asdict(obj)} != {asdict(parsed)}"
Loading