✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🐇 Announcing Dash VTK for 3d simulation graphics. Check out the March webinar.

Callback error when plotting multiple graph objects

Hi Everybody,

I’m fairly new to dash and plotly. I’m working on a multi-page app dashboard. For one of the pages of my dashboard, I’m plotting data based on a radio item input. Here is the page:

trunk_angle.py

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly.graph_objs as go
import numpy as np

from app import app
from components import utils

layout = html.Div([
    html.Div([
        html.Br(),
        html.Label('Plotting Options'),
        dcc.RadioItems(
            id='trunk-angle-radio',
            options=[
                {'label':'Sagittal', 'value':'Sagittal'},
                {'label':'Lateral', 'value':'Lateral'},
                {'label':'Twist', 'value':'Twist'}
            ],
            value='Sagittal'
        )
    ]),
    html.Div([
        html.Div([
            dcc.Graph(id='trunk-angle-plot')
            ],
            style = {'width':'48%','display':'inline-block'}
        ),
        html.Div([
            dcc.Graph(id='trunk-angle-dist')
        ],
            style = {'width':'48%','display':'inline-block','float':'right' }
        )
    ]),
    html.Div([
        html.Label('Data Statistics'),
        html.Div(
            id='data-stats-div',
            style={'padding':10})
    ])
])

@app.callback(
    Output('trunk-angle-plot','figure'),
    [Input('trunk-angle-radio','value')])
def update_angle_plot(radio_option):
    (x, y) = utils.get_trunk_angles(radio_option)
    fig = {
        'data':[
            go.Scatter(
                x = x,
                y = y,
                mode = 'lines+markers'
            )],
        'layout': go.Layout(
            title='Trunk Angle Time Series Plot',
            xaxis={'title': 'Time (sec)'},
            yaxis={'title': 'Degrees'})
    }
    return fig

@app.callback(
    Output('trunk-angle-dist', 'figure'),
    [Input('trunk-angle-radio', 'value')])
def update_dist_plot(radio_option):
    (x,y) = utils.get_trunk_angles(radio_option)
    counts, bins = np.histogram(y, bins=range(-90, 91, 30))
    bins = bins + (bins[1] - bins[0]) / 2
    #print(counts, bins)
    fig={
        'data': [
            go.Bar(
                x=bins,
                y=counts
            )],
        'layout': go.Layout(
            title = 'Trunk Angle Distributions',
            xaxis = {
                'title': 'Bin midpoint (degrees)',
                'tickmode' : 'array',
                'tickvals' : bins,
                'ticktext' : [str(int(bin)) for bin in bins]},
            yaxis = {'title': 'Percentage of time'})
    }
    return fig

@app.callback(
    Output('data-stats-div', 'children'),
    [Input('trunk-angle-radio', 'value')])
def update_stats(radio_option):
    (x, y) = utils.get_trunk_angles(radio_option)
    stats_div = [
            html.Div('Minimum: {}'.format(np.min(y)),id='trunk-angle-dist-min'),
            html.Div('Maximum: {}'.format(np.max(y)),id='trunk-angle-dist-max'),
            html.Div('Mean: {:.2f}'.format(np.mean(y)),id='trunk-angle-dist-mean'),
            html.Div('Standard Deviation: {:.2f}'.format(np.std(y)),id='trunk-angle-dist-std'),
            html.Div('Range: {}'.format(np.max(y)-np.min(y)),id='trunk-angle-dist-range')
        ]
    return stats_div

The utils.get_trunk_angles() function just provides some fake data for me to plot and is shown below:

def get_trunk_angles(radio_option):

    dummy_x = np.linspace(0, 50, 101)

    if radio_option == 'Sagittal':
        dummy_y = np.random.randint(-90,90,101)
    elif radio_option == 'Lateral':
        dummy_y = np.random.randint(-90,90,101)
    elif radio_option == 'Twist':
        dummy_y = np.random.randint(-90,90,101)

    return (dummy_x, dummy_y)

My problem is that when I first load this page, one of the graph objects plots the data as expected, but the other one does not and gives me a callback error. Once I change the radio button selection, both graphs will plot the data. Here is a screenshot of the callback error:

Here is the Traceback:

Traceback (most recent call last):
File “/Users/bwsturm/PycharmProjects/spinetrack-dashboard2/apps/trunk_angle.py”, line 73, in update_dist_plot
go.Bar(
File “/Users/bwsturm/anaconda3/envs/sm-dash/lib/python3.8/site-packages/plotly/graph_objs/init.py”, line 94246, in init
self[“x”] = x if x is not None else _v
File “/Users/bwsturm/anaconda3/envs/sm-dash/lib/python3.8/site-packages/plotly/basedatatypes.py”, line 3490, in setitem
self._set_prop(prop, value)
File “/Users/bwsturm/anaconda3/envs/sm-dash/lib/python3.8/site-packages/plotly/basedatatypes.py”, line 3772, in _set_prop
val = validator.validate_coerce(val)
File “/Users/bwsturm/anaconda3/envs/sm-dash/lib/python3.8/site-packages/_plotly_utils/basevalidators.py”, line 385, in validate_coerce
v = copy_to_readonly_numpy_array(v)
File “/Users/bwsturm/anaconda3/envs/sm-dash/lib/python3.8/site-packages/_plotly_utils/basevalidators.py”, line 93, in copy_to_readonly_numpy_array
if pd and isinstance(v, (pd.Series, pd.Index)):
AttributeError: partially initialized module ‘pandas’ has no attribute ‘Series’ (most likely due to a circular import)

Can anybody provide some clues to this callback error? I would greatly appreciate any help.

Thanks!
Ben

Hi @bens44, this does not seem to be an issue with Dash as the error says it comes from a pandas import. However, I can’t see you importing nor using pandas anywhere in those files. Do you do so in other modules?

You could try upgradng your pandas version pip install pandas -U.

I’m having the same problem. If you manage to find a solution please post and I will do the same.
R

Hi @RenaudLN. Thank you so much for your input. I don’t think that’s the problem, though. I verified that I have the latest version of pandas installed.

import pandas as pd
print(pd.__version__)
1.0.3

I played around with your example and it looks like it is a bug.

However I managed to find a workaround by returning the three outputs at once. It is probably what you should do anyway when the outputs depend on exactly the same inputs.

@alexcjohnson any ideas where this could come from? I got lost in the plotly basevalidator logic.

The working code below:

@app.callback(
    [
        Output('trunk-angle-plot','figure'),
        Output('trunk-angle-dist', 'figure'),
        Output('data-stats-div', 'children'),
    ],
    [Input('trunk-angle-radio','value')])
def update_angle_plot(radio_option):
    (x, y) = utils.get_trunk_angles(radio_option)
    fig1 = {
        'data':[
            go.Scatter(
                x = x,
                y = y,
                mode = 'lines+markers'
            )],
        'layout': go.Layout(
            title='Trunk Angle Time Series Plot',
            xaxis={'title': 'Time (sec)'},
            yaxis={'title': 'Degrees'})
    }

    counts, bins = np.histogram(y, bins=range(-90, 91, 30))
    bins = bins + (bins[1] - bins[0]) / 2
    fig2 = {
        'data': [
            go.Bar(
                x=bins,
                y=counts,
            )],
        'layout': go.Layout(
            title = 'Trunk Angle Distributions',
            xaxis = {
                'title': 'Bin midpoint (degrees)',
                'tickmode' : 'array',
                'tickvals' : bins,
                'ticktext' : [str(int(bin)) for bin in bins]},
            yaxis = {'title': 'Percentage of time'})
    }

    stats_div = [
        html.Div('Minimum: {}'.format(np.min(y)),id='trunk-angle-dist-min'),
        html.Div('Maximum: {}'.format(np.max(y)),id='trunk-angle-dist-max'),
        html.Div('Mean: {:.2f}'.format(np.mean(y)),id='trunk-angle-dist-mean'),
        html.Div('Standard Deviation: {:.2f}'.format(np.std(y)),id='trunk-angle-dist-std'),
        html.Div('Range: {}'.format(np.max(y)-np.min(y)),id='trunk-angle-dist-range')
    ]

    return [fig1, fig2, stats_div]

Hope this helps!

Peculiar - I see this too, not sure where it comes from but I’ve posted it as a bug in plotly.py. https://github.com/plotly/plotly.py/issues/2433

Until we get to the bottom of this, another way to make it work is to explicitly import pandas at the top level in your app. Ugly since linters will complain about unused imports, but for me that fixes it.

Thanks for bringing this to our attention @bens44!

1 Like

Hi @alexcjohnson! Thank you so much for your help. This solved it! It’s strange, because I didn’t even use pandas in my app, but apparently it’s a dependency for plotly. Anyway, for anybody else experiencing this issue, just put this line in your app and it should work great!

import pandas as pd

Thanks again!!

1 Like

Thank you @RenaudLN. I used the solution provided by @alexcjohnson, however I definitely will implement what you suggested, because that’s probably better coding practice to combine all three outputs in one Callback. Thanks again!