Skip to content
Advertisement

Callback error on plotly dash dashboard when trying to add a dropdown to show pie charts

I’m quite new to dash but I’m trying to put together a data dashboard. Of the things I want to have is a drop down, that based on the input, renders 1 of two pie charts. The logic to structure the pie chart is included in my callback function. It is saying it is expecting 1 output but it had two. I’ve had a look online and tried different suggestions. I think I’m pretty close to getting this to work, there is just something dumb I’m not doing.

I know people here are wizards, so I was hoping someone might be able to help me. Also if anyone is Dash savvy, can you point me in the direction of good documentation to learn how to orient this, so I can change the layout to make these plots fit better together in a dashboard, rather than just a web page?

So much love

Thanks

import pandas as pd
import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output, State
from jupyter_dash import JupyterDash
import plotly.graph_objects as go
import plotly.express as px
from dash import no_update
import plotly.figure_factory as ff



app = dash.Dash(__name__)

df = pd.read_csv('nyc-jobs.csv')
#top job categories
counts = df['Job Category'].value_counts()
counts = pd.DataFrame(counts)
counts = counts.head(10)
counts.sort_values(['Job Category'],ascending=True, inplace = True)

fig = px.bar(df, y=counts.index, x=counts['Job Category'])

#Salary range distribution
salary_counts = df['Salary Range To'].value_counts()
salary_counts = pd.DataFrame(counts)
group_labels = ['Salary Range From','Salary Range To']
fig3 = ff.create_distplot([df['Salary Range From'],df['Salary Range To']], group_labels, bin_size= 10000)


fig4 = go.Figure()
fig4.add_trace(go.Box(y=df['Salary Range From'], name='Salary Range From',
                marker_color = 'indianred'))
fig4.add_trace(go.Box(y=df['Salary Range To'], name = 'Salary Range To',
                marker_color = 'lightseagreen'))

# # of positions
df.sort_values(by = ['# Of Positions'], ascending = True, inplace = True)
df_group = df.groupby(['Business Title']).mean(['# Of Positions'])
df_group.sort_values('# Of Positions', ascending = True, inplace = True)
df_group.index = df_group.index.str.capitalize()

fig5 = px.bar(df, y=df_group.index[-5:], x=df_group['# Of Positions'][-5:])


app.layout = html.Div([
    html.H1("New York City Job Postings", style = {'text-align': 'center', 'font-family': 'Helvetica'}),
    
    #Job postings graph
    dcc.Graph(
        id='Top Job Postings',
        figure=fig
    ),
    html.Div([html.H2('Report Type:', style={'margin-right': '2em', 'font-family': 'Helvetica'}),]),
    dcc.Dropdown(id='input-type', 
                   options=[
                           {'label': 'Full vs part time report ', 'value': 'OPT1'},
                           {'label': 'Posting type', 'value': 'OPT2'}
                           ],
                  placeholder='Select a report type',
                  multi=False,
                  clearable=False,
                  style={'width':800, 'padding':3, 'font-size':20, 'text-align-last':'center', 'font-family': 'Helvetica'}),

    html.Div(id='output_container', children=[]),

    html.Div(dcc.Graph(id='pie_chart_reports')),


    #Salary Distributions
    dcc.Graph(
        id="Salary Distribution",
        figure = fig3),

    dcc.Graph(
        id="Salary Distribution boxplot",
        figure = fig4),

    dcc.Graph(
        id='Highest number of positions',
        figure=fig5
    )
])

@app.callback(
    [Output(component_id='pie_chart_reports', component_property='figure')],
    [Input(component_id='input-type', component_property='value')]
)

def update_graph(report_type):
    dff = df
    container = "The chosen report was: {}".format(report_type)
    
    if report_type == 'OPT1':
        #full time vs part time
        ft_pt = dff['Full-Time/Part-Time indicator']
        ft_pt.fillna('Not listed', inplace = True)
        ft_pt.replace('F', 'Full Time', inplace = True)
        ft_pt.replace('P', 'Part Time', inplace = True)
        value_counts_ft_pt = dff['Full-Time/Part-Time indicator'].value_counts()
        labels_ft_pt = value_counts_ft_pt.index.tolist()

        fig1 = px.pie(dff, 
        values = value_counts_ft_pt, 
        names = labels_ft_pt)

        return container, dcc.Graph(id='pie_chart_reports',figure=fig1)

    else:
        #internal vs externl
        value_counts_posting_type = dff['Posting Type'].value_counts()
        labels_posting_type = value_counts_posting_type.index.tolist()
        fig2 = px.pie(
            df, 
            values = value_counts_posting_type, 
            names = labels_posting_type, 
            color_discrete_sequence=px.colors.sequential.Bluyl)
        
        return container, dcc.Graph(id='pie_chart_reports',figure=fig2)


    

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

Advertisement

Answer

The first problem is that your callback has one output, but you return a tuple of two things. So you could add an Output that targets the element which you want to have the value of content, I’m guessing that element is the element with id output_container. The other option is to remove content from the return statement.

The second problem is that you have the Output surrounded by a list, so dash expects the return value to be a list containing one value. You can remove the list surrounding your Ouput so it expects a tuple

Output(component_id='pie_chart_reports', component_property='figure')

or you can surround your return values with a list.

The third problem is that you target the component_property figure, but you’re returning a Graph component. So you should return fig1 instead of dcc.Graph(id='pie_chart_reports', figure=fig1) for example.

Advertisement