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
Hi @AnnMarieW, that’s it! I should check the document carefully. Thank you so much!
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)
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.