Experimental_data_editor column basic calculation

Summary

Is it possible for experimental_data_editor under version 1.20 to do the dynamic column calculation when changing values?

Steps to reproduce

Code snippet:

For example:

df = pd.DataFrame({'a':[1,2,3], 'b':[4,5,6], 'c': [None, None, None]})
editable_df = st.experimental_data_editor(df)

column 'c' here should be the sum of columns 'a' and 'b', and when I change the column 'a' value in editable_df, the editbale_df's corresponding column 'c' value should change dynamically.
1 Like

Hey @lim,

The “Matrix Operations” example in the demo app for st.experimental_data_editor provides a good example of this functionality.

Here’s the full code for that example:

import operator
import time
from typing import Callable

import numpy as np
import pandas as pd
import streamlit as st
from streamlit_extras.add_vertical_space import add_vertical_space
from streamlit_extras.colored_header import ST_COLOR_PALETTE

st.set_page_config(layout="centered", page_title="Data Editor", page_icon="đź§®")
st.title("🔢 Matrix Operations")
st.caption("This is a demo of the `st.experimental_data_editor`.")
add_vertical_space(2)
BLANK_COLUMNS_CONFIG = {i: {"title": ""} for i in range(4)}


def color_mask(x: pd.DataFrame, mask: pd.DataFrame, background_color: str) -> pd.DataFrame:
    hue, intensity = background_color.split("-")
    color = f"background-color: {ST_COLOR_PALETTE[hue][intensity]}; color:white;"
    style_df = pd.DataFrame("", index=x.index, columns=x.columns)
    style_df[mask] = color
    return style_df


def operation_interface(
    operation_label: str,
    operation_method: Callable,
    background_color: str,
    key_prefix: str,
) -> None:
    a, operation, b, equals, c = st.columns((10, 1, 10, 1, 10))

    with a:
        st.caption("A")
        A = st.experimental_data_editor(
            np.array([[0, 0, 2], [1, 3, 4], [3, 4, 4]]),
            use_container_width=True,
            key=f"{key_prefix}_A",
        )

    with operation:
        add_vertical_space(6)
        st.write(f"###  {operation_label} ")

    with b:
        st.caption("B")
        B = st.experimental_data_editor(
            np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]),
            use_container_width=True,
            key=f"{key_prefix}_B",
        )

    with equals:
        add_vertical_space(6)
        st.write("###  = ")

    with c:
        st.caption("C")
        C = pd.DataFrame(operation_method(A, B))

        key_C = f"{key_prefix}_C"
        avoid_coloring = False
        if key_C not in st.session_state:
            st.session_state[key_C] = C
            avoid_coloring = True

        result_df_container = st.empty()
        mask = (C - st.session_state[key_C]) != 0
        result_df_container.dataframe(
            C.style.apply(
                lambda x: color_mask(
                    x,
                    mask=mask,
                    background_color=background_color,
                ),
                axis=None,
            ),
            use_container_width=True,
        )

        if mask.sum().sum() > 0 and not avoid_coloring:
            time.sleep(0.5)
            result_df_container.dataframe(C, use_container_width=True)

        st.session_state[key_C] = C

    add_vertical_space(2)


st.subheader("âž• Matrix Sum")

""" Edit matrices A and B and see how that impacts their :green[**matrix sum**] C!"""

operation_interface(
    "\+",
    operation_method=operator.__add__,
    background_color="green-80",
    key_prefix="sum",
)

st.subheader("âž– Matrix Difference")

""" Edit matrices A and B and see how that impacts their :blue[**matrix difference**] C!"""

operation_interface(
    "\-",
    operation_method=operator.__sub__,
    background_color="blue-80",
    key_prefix="difference",
)

st.subheader("• Matrix Product")

""" Edit matrices A and B and see how that impacts their :orange[**matrix product**] C!"""

operation_interface(
    "•",
    operation_method=np.dot,
    background_color="orange-80",
    key_prefix="product",
)
# operation_interface("*", operation_method=np.dot, key_prefix="product")
1 Like

thanks for replying. I have already achieved the function in a similar way, but is it possible to do the calculation within a single data frame rather than rerender the result in another dataframe? Such as for the code you provided, column 2 should be the sum of columns 0 and 1, and when I change values in columns 0 and 1, the corresponding column 2’s value will change as well
image

1 Like

Hi @lim ,
were you able to figure this out?

1 Like

unfortunately, i haven’t found any solution that can fulfill my requirement. maybe the next version of streamlit will support such a function?

2 Likes

@Caroline

1 Like

Here’s an example showing one way to do this with session state (click on the “Show code” expander to see the code)

6 Likes

Thank you for the solution ! It works perfectly & fixed a major blocker I was having …
Great work !

1 Like

Could you share the solution? I cannot see the “Show code” expander you mentioned.

1 Like

@risson Here’s a slightly modified version using st.data_editor

from __future__ import annotations

import pandas as pd
import streamlit as st


def add_c(new_df: pd.DataFrame | None = None):
    if new_df is not None:
        if new_df.equals(st.session_state["df"]):
            return
        st.session_state["df"] = new_df

    df = st.session_state["df"]
    df["c"] = df["a"] + df["b"]
    st.session_state["df"] = df
    st.experimental_rerun()


if "df" not in st.session_state:
    st.session_state.df = pd.DataFrame(
        {"a": [1, 2, 3], "b": [4, 5, 6], "c": [None, None, None]}
    )
    add_c()


editable_df = st.data_editor(
    st.session_state["df"],
    key="data",
    column_config={"c": st.column_config.Column(disabled=True)},
    hide_index=True,
)

add_c(editable_df)
1 Like

This work like a charm! You made my day :smiley:

2 Likes

Hi, do you have a suggestion for how this should be modified if there are several other widgets whose values affect the calculation of c?

I have a very similar situation in that I want columns of a data_editor window to be calculated based on the value of the first column and the values in two other input widgets (a number input and a date input).

I managed to get this working using an approach whereby you have variables in session_state to represent each variable that is used in the calculation (so in my case I added two further variables to session_state, to represent my number input and date input) and step out of add_c if none of their values have changed.

However, this could get unwieldy if there are many different variables affecting the calculation. What approach would you recommend in that case?

1 Like

I think the approach you’ve tried seems reasonable to me. I would try it that way, and if it gets unwieldy, you can always refactor later as necessary.

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.