marimo-team / marimo

A reactive notebook for Python — run reproducible experiments, execute as a script, deploy as an app, and version with git.
https://marimo.io
Apache License 2.0
7.96k stars 280 forks source link

UI components in tables render differently depending on the data structre (dict/list, Pandas DataFrame) #2656

Open vangberg opened 1 month ago

vangberg commented 1 month ago

Describe the bug

UI components (e.g. mo.ui.button) behave inconsistently when rendered in a marimo.ui.table, depending on whether it is stored within a Python dict/list or a Pandas DataFrame.

In a dict, the button is rendered as an interactive UI component, but when placed in a Pandas DataFrame, it is rendered as a string instead. This behavior is unexpected, as one would expect the button to be rendered consistently across both data structures:

Image

I think it is down to how to_data is implemented in DefaultTableManager vs. PandasTableManager. In DefaultTableManager, to_data normalizes the data and returns it:

https://github.com/marimo-team/marimo/blob/257238bec616f4548f92508ccb510e9640faa958/marimo/_plugins/ui/_impl/tables/default_table.py#L76-L83

In PandasTableManager, to_data is not defined, so to_data from TableManager is used, which turns it into CSV:

https://github.com/marimo-team/marimo/blob/257238bec616f4548f92508ccb510e9640faa958/marimo/_plugins/ui/_impl/tables/table_manager.py#L40-L50

Environment

{ "marimo": "0.9.10", "OS": "Darwin", "OS Version": "22.3.0", "Processor": "arm", "Python Version": "3.12.6", "Binaries": { "Browser": "129.0.6668.101", "Node": "v22.9.0" }, "Dependencies": { "click": "8.1.7", "importlib-resources": "missing", "jedi": "0.19.1", "markdown": "3.7", "pygments": "2.18.0", "pymdown-extensions": "10.10.1", "ruff": "0.6.7", "starlette": "0.39.0", "tomlkit": "0.13.2", "typing-extensions": "4.12.2", "uvicorn": "0.30.6", "websockets": "12.0" }, "Optional Dependencies": {} }

Code to reproduce

# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "marimo",
#     "pandas==2.2.3",
# ]
# ///

import marimo

__generated_with = "0.9.10"
app = marimo.App(width="medium")

@app.cell
def __():
    import marimo as mo
    import pandas as pd
    return mo, pd

@app.cell
def __(mo):
    mo.md(r"""How an `mo.ui.button` is rendered in an `mo.ui.table` varies depending on the data structure it is contained within.""")
    return

@app.cell
def __(mo):
    button = mo.ui.button(label='Click me')
    return (button,)

@app.cell
def __(button):
    data = {'Buttons': [button]}
    return (data,)

@app.cell
def __(data, mo):
    mo.md(f"""
    ## dict/list

    If the button is in a regular dict/list, it is rendered as a UI component:

    {mo.ui.table(data)}
    """)
    return

@app.cell
def __(button, pd):
    df = pd.DataFrame({'Buttons': [button]})
    return (df,)

@app.cell
def __(df, mo):
    mo.md(f"""
    ## Pandas DataFrame

    If the button is in a Pandas DataFrame, it is rendered as a string:

    {mo.ui.table(df)}
    """)
    return

if __name__ == "__main__":
    app.run()
mscolnick commented 1 month ago

Yea i think we can write a proper to_data, which can contain rich objects, instead of writing to_csv (which is limited to strings) for each dataframe implementation