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.