Skip to content

Fix: expand dict transformation#9561

Merged
kirangadhave merged 38 commits into
marimo-team:mainfrom
Shamik-07:fix/expand_dict_transformation
Jul 8, 2026
Merged

Fix: expand dict transformation#9561
kirangadhave merged 38 commits into
marimo-team:mainfrom
Shamik-07:fix/expand_dict_transformation

Conversation

@Shamik-07

@Shamik-07 Shamik-07 commented May 15, 2026

Copy link
Copy Markdown
Contributor

📝 Summary

Using narwahls to convert all backend to polars and then using the unnest function of polars for expanding the dict and then convert it back to the original backend.
Closes #4583

Screen.Recording.2026-05-15.at.18.07.461.mov

📋 Pre-Review Checklist

  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it.
  • Video or media evidence is provided for any visual changes (optional).

✅ Merge Checklist

  • I have read the contributor guidelines.
  • Documentation has been updated where applicable, including docstrings for API changes.
  • Tests have been added for the changes made.

@vercel

vercel Bot commented May 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Jul 7, 2026 10:28pm

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 3 files

Architecture diagram
sequenceDiagram
    participant User as User (Marimo UI)
    participant DFPlugin as Dataframe Plugin
    participant Handler as ExpandDict Handler (handlers.py)
    participant Narwhals as Narwhals Layer
    participant Polars as Polars Engine
    participant Backend as Original Backend (pandas/Ibis/other)

    Note over User,Backend: Expand Dict Transformation Flow

    User->>DFPlugin: Trigger expand dict on column
    DFPlugin->>Handler: handle_expand_dict(df, transform)

    Handler->>Narwhals: collect_and_preserve_type(df)
    Narwhals->>Backend: Collect actual data from original backend
    Backend-->>Narwhals: Data as native type
    Narwhals-->>Handler: Collected DataFrame + undo function

    Handler->>Polars: collected_df.to_polars()
    Note over Handler,Polars: Convert to Polars for unnest support

    Polars->>Polars: polars_df.unnest(column_id)
    Note over Polars: Handles null dict values correctly

    Polars-->>Handler: Unnested Polars DataFrame

    Handler->>Narwhals: nw.from_native(unnested)
    Narwhals->>Handler: Narwhals wrapper

    Handler->>Handler: undo(narwhals_df)
    Note over Handler: Convert back to original backend type

    Handler-->>DFPlugin: Transformed DataFrame
    DFPlugin-->>User: Updated table with expanded columns
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Re-trigger cubic

Comment thread marimo/_plugins/ui/_impl/dataframes/transforms/handlers.py Outdated
Comment thread tests/_plugins/ui/_impl/dataframes/test_handlers.py

@mscolnick mscolnick left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

need to take an optional dep on polars

Shamik-07 added 3 commits May 19, 2026 17:45
… rows with the create test dataframes instead.
refactor: adding None == NaN in assert frame equal with nans method to use it in expand_dict test.
@Shamik-07

Copy link
Copy Markdown
Contributor Author

need to take an optional dep on polars

Done.
Added allow_none_equals_nan for assert_frame_equal_with_nans as None!=NaN, which was causing the use of assert_frame_equal_with_nans to fail for test_expand_dict.

@Shamik-07 Shamik-07 requested a review from mscolnick May 19, 2026 22:30
@Shamik-07

Copy link
Copy Markdown
Contributor Author

There are some pandas CI errors that i am looking into.

@Shamik-07

Copy link
Copy Markdown
Contributor Author

There are some pandas CI errors that i am looking into.

This is happening because of data conversion mismatch between pandas and narwahls with mixed data types

"Could not convert '3' with type str: tried to convert to double"

for

test_print_code_result_matches_actual_transform_pandas(
    transform=ExpandDictTransform(
        type=TransformType.EXPAND_DICT,
        column_id='strings',
    ),
)

So my only option is to fallback to pandas backend processing separately for the unnest.
This should fix it.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread marimo/_plugins/ui/_impl/dataframes/transforms/handlers.py
@codecov

codecov Bot commented May 20, 2026

Copy link
Copy Markdown

Bundle Report

Bundle size has no change ✅

Affected Assets, Files, and Routes:

view changes for bundle: marimo-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/dist-*.js -81 bytes 102 bytes -44.26%
assets/dist-*.js 119 bytes 256 bytes 86.86% ⚠️
assets/dist-*.js -14 bytes 169 bytes -7.65%
assets/dist-*.js -68 bytes 335 bytes -16.87%
assets/dist-*.js -5 bytes 164 bytes -2.96%
assets/dist-*.js 17 bytes 177 bytes 10.62% ⚠️
assets/dist-*.js -76 bytes 183 bytes -29.34%
assets/dist-*.js 90 bytes 259 bytes 53.25% ⚠️
assets/dist-*.js -8 bytes 169 bytes -4.52%
assets/dist-*.js -231 bytes 104 bytes -68.96%
assets/dist-*.js -139 bytes 137 bytes -50.36%
assets/dist-*.js 283 bytes 387 bytes 272.12% ⚠️
assets/dist-*.js 33 bytes 137 bytes 31.73% ⚠️
assets/dist-*.js -111 bytes 276 bytes -28.68%
assets/dist-*.js 299 bytes 403 bytes 287.5% ⚠️
assets/dist-*.js 46 bytes 183 bytes 33.58% ⚠️
assets/dist-*.js -152 bytes 104 bytes -59.38%
assets/dist-*.js 58 bytes 160 bytes 56.86% ⚠️
assets/dist-*.js -60 bytes 104 bytes -36.59%
assets/ttcn-*.js 7 bytes 64 bytes 12.28% ⚠️
assets/ttcn-*.js -7 bytes 57 bytes -10.94%
assets/__vite-*.js -5 bytes 93 bytes -5.1%
assets/__vite-*.js 5 bytes 98 bytes 5.38% ⚠️

@Shamik-07

Copy link
Copy Markdown
Contributor Author

please address copilot review. We can do a manual round of reviews after that.

Thanks, fixed.

@kirangadhave kirangadhave left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Requesting a major change which should remove hard dependency on polars for other backends which support structs.

Also please address other nits.

expanded.index = result_df.index
return undo(nw.from_native(result_df.join(expanded)))

DependencyManager.polars.require(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

non pandas dataframes are forced to go through polars conversion here. For duckdb or ibis, that means forcing polars installation. We should not do that.

Narwhals has struct.field, so we can do:

schema = df.collect_schema()
fields = [f.name for f in schema[col].fields]
df.with_columns(
    [nw.col(col).struct.field(f).alias(f) for f in fields]
).drop(col)

This approach also stays lazy. Pandas approach with json_serialize is correct.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Are you sure that all backends, which support struct schema would necessarily have struct.field?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

struct.field is part of the narwhals' public api, so it should translate okay. We support pandas, polars, ibis, pyarrow and duckdb backends. Except pandas all others have struct.field support.

It might be worth adding tests though. Thoughts?

@Shamik-07 Shamik-07 Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, you are correct about narwahl's api.

create_test_dataframes is used for generating the dataframes for the expand dict tests.

To support duckdb, should i create a new test for expand dict or it doesn't matter as the tests are only supported for pandas, polars, ibis, pyarrow?

# older versions of pandas running on py310 otherwise CI will fail
expanded = pd.json_normalize(
result_df.pop(transform.column_id).map(
lambda value: {} if value is None else value

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also check for NaN here

import pandas as pd

result_df = native_df.copy()
# max_level=0 was used so that pandas doesn't recursively unnest dicts

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the comment is narrating the code, simplify to explain the why instead.

Comment thread marimo/_plugins/ui/_impl/dataframes/transforms/handlers.py
why="to expand dict/struct columns for non-pandas backends"
)
polars_df = collected_df.to_polars()
unnested = polars_df.unnest(transform.column_id)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Duplicate column names after unnest will throw error here. Handle it gracefully.

Comment thread tests/_plugins/ui/_impl/dataframes/test_handlers.py
Shamik-07 added 2 commits July 3, 2026 11:17
feat: raising duplicate columns error
test: adding necessary tests for duplicate columns
@Shamik-07

Copy link
Copy Markdown
Contributor Author

@kirangadhave

Thanks for the review comments.
I have addressed your review comments.
I still have a question, are you sure that all backends, which support struct schema would necessarily have struct.field?

@kirangadhave kirangadhave self-requested a review July 7, 2026 15:41

@kirangadhave kirangadhave left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks again for the changes. We will be good to merge after this set of changes. Especially the test related ones are must do.

allow_none_equals_nan=True,
)

@pytest.mark.xfail(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

xfail is used to mark tests where failure is expected. Raising error is the expected pass case here, so don't mark as xfail.

# pandas and other backends agree on expand-dict behaviour.
expanded = pd.json_normalize(
result_df.pop(transform.column_id).map(normalise_empty_dict),
# type: ignore[arg-type]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

type ignore comment on new line doesn't do anything


schema = collected_df.collect_schema()
dtype = schema.get(transform.column_id)
fields = getattr(dtype, "fields", None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use isinstance(dtype, nw.Struct) here?

.select(expanded_columns)
)

raise nw.exceptions.InvalidOperationError(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's add a test for this

fix: checking if schema is of dtype struct for handle expand dict.
tests: add separate tests for expand dict error.
fix: remove xfail from expand dict test.
@Shamik-07

Copy link
Copy Markdown
Contributor Author

Thanks again for the changes. We will be good to merge after this set of changes. Especially the test related ones are must do.

Thank you again for the detailed review.

@kirangadhave kirangadhave left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🚀

@kirangadhave

Copy link
Copy Markdown
Member

Thanks @Shamik-07 for your contribution and addressing multiple review rounds

@kirangadhave kirangadhave self-requested a review July 8, 2026 15:56
@kirangadhave kirangadhave dismissed mscolnick’s stale review July 8, 2026 15:56

addressed in later commits

@kirangadhave kirangadhave merged commit dbcb600 into marimo-team:main Jul 8, 2026
40 checks passed
@Shamik-07 Shamik-07 deleted the fix/expand_dict_transformation branch July 8, 2026 19:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New dataframe transform: Polars Native Expand Dict Transformation

4 participants