Dash table update values for column/ row based on callback/user input from another column

Hello, I am using data table to build interactive table which is editable for one column. Could I update value for other columns in the same table when user enter input value? As long as table data can’t be both input and output in the same callback. I can’t think about a strategy to achieve this. Could anyone help with it? Many thanks!

Hi @jing0703

Check out this link: https://dash.plotly.com/datatable/editable

See the example called " Updating Columns of the Same Table"

It sounds like what you are looking for

1 Like

Hi @AnnMarieW, that’s it! I should check the document carefully. Thank you so much!

1 Like

@AnnMarieW @jing0703

I am working on the same update, except, my data-table is updated on callback and then when the user makes a change to editable column, I’d like to update another column in same data-table. My problem is I can’t have a second callback for data-table, so I need to do it in the same one. Here’s my code:


# Update DataTable
@app.callback(Output("deal-table", "data"),
              [

                  Input("income", "value"),
                  Input("expenses", "value"),
                  Input("lease-button", "n_clicks"),
                  Input("deal-table", "data_timestamp")

              ],
              [
                  State("deal-table", "data"),
                  State("comps-store", "data")
              ]
             )
def cashflow_table(income, expenses, n_clicks, timestamp, rows, comps_store):

    if n_clicks:

        # Construct Pandas DataFrame
        df = pd.DataFrame({
                           "Year": year_list,
                           "Periods": np.nan,
                           "Income": np.nan,
                           "Expenses": np.nan,
                           "Cash Flow": np.nan
                         })

        # Update a column based on user input
        if rows:
           for row in rows:
               try:
                  row['Cash Flow'] =  row['Income'] - row['Expenses']
               except:
                  row['Cash Flow'] = 0

        df_table = df.to_dict("rows")

        return (df_table)

    else:

       return (no_update)

This doesn’t work.

Hi @keval

I think I see what’s going on.

A bit of background for others who might be new to this: The content of a dash DataTable, the data parameter, is a list of dictionaries. In most of the examples in the Dash tutorial, the data for the app starts as a dataframe, and before it’s used in the DataTable, it’s converted to the correct format using the Pandas function data=df.to_dict('records')

In the Editable DataTable example, the data is used as an input argument in the callback function - in this example it’s called rows

@app.callback(
    Output('computed-table', 'data'),
    [Input('computed-table', 'data_timestamp')],
    [State('computed-table', 'data')])
def update_columns(timestamp, rows):
    for row in rows:
        try:
            row['output-data'] = float(row['input-data']) ** 2
        except:
            row['output-data'] = 'NA'
    return rows

Note that the data stays in the original format - a list of dictionaries. So there is no need to convert it, you can just use return rows

If you prefer to do the calculations using Pandas, you can start by converting rows to df like this:

def update_columns(timestamp, rows):
    df=pd.DataFrame(rows)

    # do calculations using pandas

    return df.to_dict('records')

(In your example df.to_dict(“rows”) the “rows” is not a valid parameter https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_dict.html)

If this is still unclear, if you provide a minimal working example (including sample data) I might be able to be more helpful.

Hi @AnnMarieW -

Thanks for the background and pointing out the distinction between rows.

My initial DataFrame is generated and rendered from the callback, unlike the example here:https://dash.plotly.com/datatable/editable. I do the calculations in the callback function as well.
When the user updates an editable column, I’d like to update the values in another column in same data-table.

Here’s a minimal reproducible example:

import dash
from dash.dependencies import Input, Output, State
import dash_table
import dash_html_components as html

app = dash.Dash(__name__)

app.layout = html.Div([

    dbc.Button("Run", id="dt-button", size="lg"),

    dash_table.DataTable(
        id='table1',
        columns=[
            {'name': 'Year', 'id': 'year'},
            {'name': 'Income', 'id': 'income'},
            {'name': 'Expenses', 'id': 'expense'},
            {'name': 'Cash flow', 'id': 'cash flow'}
        ],
      
        editable=True,
    ),
])


# Callback

@app.callback(Output("table1", "data"),
                     [

                        Input("dt-button", "n_clicks"),
                        Input("table1", "data_timestamp")
                       
                    ],
                    [
                       State("table1", "data"),
                   ]
                  )
def table(n_clicks, timestamp, rows):

         if n_clicks:
            # Construct Pandas DataFrame 
             df = pd.DataFrame({
                                           "Year": ['2018','2019','2020'],
                                           "Income": ['200','300','400'],
                                           "Expense": np.nan, # Editable column, user inputs expenses
                                           "Cash Flow": np.nan #Income - expense
                                        })

            # Update columns
            for row in rows:
                 try:
                      row['cash flow'] = (row['income'] - row['expense']) # Expense is entered by the user
                except:
                      row['cash flow'] = row['income']

            df_table = df.to_dict("records")

            return (df_table)

         else:

           return (no_update)

Hi @keval

Since you want to generate the initial dataframe in the callback, you need to know which input triggered the callback - either the start button, or the the user entering data in the table. You can do that by using callback_context. More info on that here: https://dash.plotly.com/advanced-callbacks

Here is something to get you started - based on the code you provided:

import dash
from dash.dependencies import Input, Output, State
import dash_table
import dash_html_components as html
import pandas as pd



app = dash.Dash(__name__)

app.layout = html.Div(
    [
        html.Button("Run", id="dt-button"),
        dash_table.DataTable(
            id="table1",
            columns=[
                {"name": "Year", "id": "Year"},
                {"name": "Income", "id": "Income", "type": "numeric"},
                {"name": "Expenses", "id": "Expense", "type": "numeric"},
                {"name": "Cash flow", "id": "Cash Flow", "type": "numeric"},
            ],
            editable=True,
        ),
    ]
)


@app.callback(
    Output("table1", "data"),
    [Input("dt-button", "n_clicks"), Input("table1", "data_timestamp")],
    [State("table1", "data"),],
)
def table(n_clicks, timestamp, rows):

    ctx = dash.callback_context
    input_id = ctx.triggered[0]["prop_id"].split(".")[0]

    if input_id == "dt-button":       
        df = pd.DataFrame(
            {
                "Year": ["2018", "2019", "2020"],
                "Income": [200, 300, 400],
                "Expense": [0, 0, 0],  # Editable column, user inputs expenses
                "Cash Flow": [0, 0, 0],  # Income - expense
            }
        )
        return df.to_dict("records")

    if rows:
        for row in rows:
            try:
                row["Cash Flow"] = (row["Income"] - row["Expense"])
            except:
                row["Cash Flow"] = row["Income"]
        return rows
    else:
        return dash.no_update


if __name__ == "__main__":
    app.run_server(debug=True)

@AnnMarieW

Converting to pandas dataframe, like you suggested worked for me. Here’s the code snippet to update column value based on edits from another column in same data-table:

    # Check state
    if rows and timestamp:
        # Convert to Pandas DataFrame
        df = pd.DataFrame(rows)

        for index, row in df.iterrows():

            if row['Expense'] is not None:

                df.at[index, 'Cash Flow'] = int(row['Income']) - int(row['Expense'])

        return df.to_dict('records')

Thanks for your help.