A nonexistent object was used in an `Input` of a Dash callback - Bug with dcc.store

Hey,

currently I am working on an app, in this app i want to take a loaded dataframe, create synthetic data via oversampling and store this new generated data in a different dcc.store object.

I’ve been facing this issue for quite some time right now. Even though my code runs perfectly smooth in my multipage app I receive this error message:

A nonexistent object was used in an Input of a Dash callback. The id of this object is imb_data_drpdwn and the property is value. The string ids in the current layout are: […]

One can use the app completely normally, the only annoying thing is the bug popping up.

Although the imb_data_drpdwn is clearly defined and operationable in this callback. I digged deeper into the code and figured that it has to do with the way I create my dcc.store object. If I remove the data output the error doesn’t show.

Here is my callback in my callback file:

# Implement Default Rate over time and general information:
@app.callback(
     Output(component_id='synthetic_datasets', component_property="data"),
    [Input(component_id='imbalanced_data_tabs', component_property="active_tab"),
     Input(component_id='imb_data_drpdwn', component_property="value"),
     Input(component_id='imb_data_percentage', component_property="value"),
     Input(component_id='imb_data_tune_btn', component_property="n_clicks")],
    [State (component_id='synthetic_datasets', component_property="data"),
     State(component_id='chosen_dataset', component_property="data")],
     prevent_initial_call=True
)
def update_data_overview_graph(active_tab, algorithm, weight, tune_btn, synthetic_datastore, chosen_data):
    if active_tab == 'tab_oversampling_data':
        y = DATASETS[chosen_data]['MODEL_CALI_DATA']["RDS"]['y_emp']
        X = DATASETS[chosen_data]['MODEL_CALI_DATA']["RDS"]['X']
        button_id = [p['prop_id'] for p in dash.callback_context.triggered][0]
        if 'imb_data_tune_btn' in button_id:
            X_res, y_res = over_under_sampling(X, y,algorithm, weight)
            synthetic_datastore['X_res'] = X_res
            synthetic_datastore['y_res'] = y_res
    return synthetic_datastore

That’s how I create the dcc store (In the index.py file)

 # Storage for Datasets with Synthetic Defaults
        dcc.Store(id='synthetic_datasets', storage_type='session', data={
            'X_res': None,
            'y_res': None}),

This is the code of the app, in a different function I return the .getlayout function if the tab is activated:

from dash import html, no_update, dcc, dash_table as dt
import dash_bootstrap_components as dbc

from aicreditrisk.modules_and_functions.data_quality.functions_and_plots.data_summary import provide_data_summary as pds
from aicreditrisk.dashboard.all_layouts.utils.card_headline_and_information_row import get_headline_and_information_row
from aicreditrisk.dashboard.all_layouts.utils.loading_circle import get_loading_circle_div


def get_layout():
    oversampling_algos = ['SMOTE', 'BorderlineSMOTE', 'SMOTENC', 'SVMSMOTE', 'KMeansSMOTE']
    return html.Div(children=[
            dbc.Card(
                dbc.CardBody([
                    get_headline_and_information_row("Oversample Dataset",
                                                     "Oversampling means to create synthetic data points,"
                                                     " to do that please select the percentage and the "
                                                     "corresponding algorithm you want to choose."
                                                     ),

                    html.Label('Default Rate over time:'),
                    dbc.Col([
                        dbc.Spinner([
                            html.Div(id='imb_data_overview_df_rate_div')
                        ], spinnerClassName="loader_fancy",
                            spinner_style={'textAlignment': 'center',
                                           'paddingTop': '20px',
                                           'paddingBottom': '20px'})
                    ], width=12),
                    html.Label(id="imb_data_avg_df"),
                    html.Br(),
                    dbc.Row([
                        dbc.Col([
                            dbc.Row([
                                html.Label("Select Algorithm to Oversample:",  style={'textAlign': 'left'}),
                                html.Br(),
                                dcc.Dropdown(id='imb_data_drpdwn', options=[{'label': data, 'value': data} for data in oversampling_algos])
                            ]),

                        ]),
                        dbc.Col([
                            dbc.Row([
                                html.Label("Select percentage of Defaults (Regular + Synthetic):", style={'textAlign': 'left'}),
                                html.Br(),
                                dbc.Input(id="imb_data_percentage",
                                          type="number",
                                          placeholder="Please input number in between 0-100")
                            ]),
                            html.Br()
                        ]),
                    ]),
                    html.Br(),
                    dbc.Row([
                        dbc.Col([
                            dbc.Row([
                                html.Label("To identify the synthetic datapoints we asign the CUSTOMER_ID: 99999, REFERENCE_DATE: 01.__.____(corresponding to the first date of the month)", style={'textAlign': 'left'}),
                                html.Br(),
                            ]),
                            ]),
                        ]),
                ]), className='w-100 card-style',
            ),
        html.Br(),
            dbc.Card(
                dbc.CardBody([
                    dbc.Row([
                        dbc.Col([
                            dbc.Row([
                                html.Label("Create Synthetic Data:", style={'textAlign': 'left'},),
                                html.Br(),
                                html.Button('Create Synthetic Data Points', id='imb_data_tune_btn',  n_clicks=0,
                                            className="d-grid gap-2 col-2 mx-auto button_fancy type1")
                            ]),

                        ]),
                        dbc.Col([
                            dbc.Row([
                                html.Label("Continue to use new dataset:", style={'textAlign': 'left'}),
                                html.Br(),
                                html.Button('Change Dataframe', id='imb_data_ctn_btn',  n_clicks=0,
                                            className="d-grid gap-2 col-2 mx-auto button_fancy type1")
                            ]),
                            html.Br()
                        ]),
                    ])
                ]), className='w-100 card-style',
            ),
        html.Br(),
        dbc.Card(
            dbc.CardBody([
                get_headline_and_information_row("Synthetic Data Graphical Display",
                                                 "Here one can see the distribution of the just created synthetic data."
                                                 " Select two riskdrivers to see the data in a two dimensional space."
                                                ),
                dbc.Row([
                    dbc.Col([
                        dbc.Row([
                            html.Label("Select Riskdriver:", style={'textAlign': 'left'}),
                            html.Br(),
                            dcc.Dropdown(id='imb_data_first_rd',
                                         options=[{'label': data, 'value': data} for data in oversampling_algos])
                        ]),
                    ]),
                    dbc.Col([
                        dbc.Row([
                            html.Label("Select Riskdriver:", style={'textAlign': 'left'}),
                            html.Br(),
                            dcc.Dropdown(id='imb_data_scd_rd',
                                         options=[{'label': data, 'value': data} for data in oversampling_algos])
                        ]),
                        html.Br()
                    ]),
                ])
            ]), className='w-100 card-style',
        ),
        html.Br(),
        dbc.Card(
            dbc.CardBody([
                get_headline_and_information_row("Export Dataframe"),
                dbc.Row([
                    dbc.Col([
                        dbc.Row([
                            html.Label("press button to export new generated Dataframe:", style={'textAlign': 'left'}),
                            html.Br(),
                            html.Button('Export Dataframe', id='imb_data_export', n_clicks=0,
                                        className="d-grid gap-2 col-2 mx-auto button_fancy type1")
                        ]),
                    ]),
                ])
            ]), className='w-100 card-style',
        ),
    ],
    )

It would be nice to know how to fix that error message.
Sincerely,
Peter

I have to add, that the error does not show if I don’t include the dcc.store output → The object does exist only not in this layer. Plotly somehow wants to call it earlier (even though the prevent initial callback) . The error also only appears as soon as I open the tab.

Hi,

in a different function I return the .getlayout function if the tab is activated

The components in get_layout are added dynamically to the layout, depending on which tab you are. Some of the added components are Inputs to the callback that updates the Store, but you also have the active tab, which seems to be always present in the layout.

Now, suppose that your app starts with the tab in question. If you switch to a new tab, the components “imb_data_*” are present in the layout when Input('imbalanced_data_tabs', "active_tab") triggers the callback, so all Inputs are present in the layout and the execution is fine. However, if you come back to the tab, then these components are gone, but the active_tab remains, triggering the callback and raising the error. prevent_initial_call won’t be of any help in this case, since this is not necessarily a problem when the app starts.


The solution is relatively simple. Looking at your callback, it seems to me that you don’t need active_tab to trigger it, since you just want to check the value to know if synthetic_datastore has to be updated. The only case when it is updated is i-when the tab is active and ii-when “imb_data_tune_btn” is pressed, but note that ii can happen only if i is true (the button must be in the layout), so basically “imb_data_tune_btn” should be your only Input to this callback (based on the conditional you have on it).

There is a gotcha though… :slightly_frowning_face: Callbacks are also triggered when an Input is added to the layout (see here), so the callback will be triggered when “imb_data_tune_btn” is added to the layout. This can be solved by setting n_clicks (say, to None) in the button when it gets inserted and explicitly testing if the value is not None in the callback body. I am pretty sure triggered can’t distinguish when the button is pressed or insterted, but I might be wrong on that…

Apologies for the long explanation, I hope that you’ll find it helpful to understand the error. :slightly_smiling_face:

3 Likes

Thank you for your detailed explanation! Yes I figured it had to do smth with the different layers.
All I did was adding n_clicks=None and removing the active tab element!