Dash Plotly回调更新图表失败求助(IBM数据科学课程作业)
一、原始代码的核心问题与修复
原始代码出现Callback error updating output-container.children的原因如下:
下拉菜单初始值不匹配
dropdown-statistics的初始值设为'Select Statistics',但选项列表中无该值,导致回调触发时selected_statistics为无效值,直接进入else分支返回None,无图表渲染。
修复:将初始值改为选项中的有效值,比如'Recession Period Statistics'。Plotly图表列名引用错误
所有图表中使用的y='Sales Volume'是自定义标题,而非数据列名,实际数据列名为Automobile_Sales,直接引用会导致找不到列的错误。
修复:将y='Sales Volume'改为y='Automobile_Sales',同时通过labels参数设置显示的轴标题。Recession模式下冗余且错误的代码
Recession模式不需要input_year,但代码中错误添加了yearly_data = data[data['Year'] == input_year],当未选择年份时input_year为字符串'Select Year',与数值类型的Year列比较会触发类型错误。
修复:删除该行冗余代码。Yearly模式下图表逻辑错误
Y_chart3的标题是“按车型平均销量”,但代码中仅按Year分组,未按Vehicle_Type分组,导致图表不符合预期。
修复:修改分组逻辑为yearly_data.groupby('Vehicle_Type')['Automobile_Sales'].mean().reset_index()。
二、调整布局后的核心问题与修复
调整布局后出现更新chart1-4.figure的回调错误,原因是:
回调的输出是四个dcc.Graph的figure属性,需要返回Plotly figure对象,但代码中返回的是包含dcc.Graph的html.Div结构,类型完全不匹配。
修复:回调函数直接返回四个Plotly figure实例,而非组件结构。
修复后的完整代码
import dash from dash import dcc, html from dash.dependencies import Input, Output import pandas as pd import plotly.express as px # 加载数据 data = pd.read_csv('https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMDeveloperSkillsNetwork-DV0101EN-SkillsNetwork/Data%20Files/historical_automobile_sales.csv') # 初始化Dash应用 app = dash.Dash(__name__) app.title = "Automobile Sales Statistics Dashboard" # 下拉选项与年份列表 dropdown_options = [ {'label': 'Yearly Statistics Report', 'value': 'Yearly Statistics'}, {'label': 'Recession Period Statistics', 'value': 'Recession Period Statistics'} ] year_list = [i for i in range(1980, 2024, 1)] # 应用布局 app.layout = html.Div(children=[ html.H1("Automobile Sales Statistics Dashboard", style={'textAlign': 'center', 'color': '#503D36', 'font-size': 24}), html.Div([ html.Label("Select Statistics:"), dcc.Dropdown( id='dropdown-statistics', options=dropdown_options, value='Recession Period Statistics', # 设置有效初始值 placeholder='Select a report type', style={'width': '80%', 'padding': 3, 'font-size': 20} ) ]), html.Div([ dcc.Dropdown( id='select-year', options=[{'label': i, 'value': i} for i in year_list], value=1980, # 设置有效初始年份 style={'width': '80%', 'padding': 3, 'font-size': 20}, disabled=True # 默认禁用 ) ]), # 调整后的布局:固定四个图表容器 html.Div(style={'display': 'flex', 'justify-content': 'center', 'gap': '20px', 'margin': '20px 0'}, children=[ html.Div(dcc.Graph(id='chart1'), style={'width': '45%'}), html.Div(dcc.Graph(id='chart2'), style={'width': '45%'}) ]), html.Div(style={'display': 'flex', 'justify-content': 'center', 'gap': '20px', 'margin': '20px 0'}, children=[ html.Div(dcc.Graph(id='chart3'), style={'width': '45%'}), html.Div(dcc.Graph(id='chart4'), style={'width': '45%'}) ]) ]) # 回调:控制年份下拉菜单的禁用状态 @app.callback( Output('select-year', 'disabled'), Input('dropdown-statistics', 'value') ) def toggle_year_dropdown(selected_stat): return selected_stat != 'Yearly Statistics' # 回调:更新四个图表的figure @app.callback( [Output('chart1', 'figure'), Output('chart2', 'figure'), Output('chart3', 'figure'), Output('chart4', 'figure')], [Input('dropdown-statistics', 'value'), Input('select-year', 'value')] ) def update_charts(selected_stat, input_year): if selected_stat == 'Recession Period Statistics': recession_data = data[data['Recession'] == 1] # 图表1:衰退期年度平均销量趋势 yearly_rec = recession_data.groupby('Year')['Automobile_Sales'].mean().reset_index() fig1 = px.line( yearly_rec, x='Year', y='Automobile_Sales', title="衰退期平均汽车销量波动", labels={'Automobile_Sales': '销量'} ) # 图表2:各车型平均销量 average_sales = recession_data.groupby('Vehicle_Type')['Automobile_Sales'].mean().reset_index() fig2 = px.bar( average_sales, x='Vehicle_Type', y='Automobile_Sales', title="各车型平均销量", labels={'Automobile_Sales': '平均销量', 'Vehicle_Type': '车型'} ) # 图表3:衰退期各车型广告支出占比 exp_rec = recession_data.groupby('Vehicle_Type')['Advertising_Expenditure'].sum().reset_index() fig3 = px.pie( exp_rec, values='Advertising_Expenditure', names='Vehicle_Type', title="衰退期各车型广告支出占比", labels={'Advertising_Expenditure': '广告总支出'} ) # 图表4:失业率对各车型销量的影响 un_sales = recession_data.groupby(['Vehicle_Type', 'unemployment_rate'])['Automobile_Sales'].mean().reset_index() fig4 = px.bar( un_sales, x='unemployment_rate', y='Automobile_Sales', color='Vehicle_Type', title="失业率对各车型销量的影响", labels={'unemployment_rate': '失业率', 'Automobile_Sales': '平均销量', 'Vehicle_Type': '车型'} ) return [fig1, fig2, fig3, fig4] elif selected_stat == 'Yearly Statistics': yearly_data = data[data['Year'] == input_year] # 图表1:年度总销量趋势(全周期) yas = data.groupby('Year')['Automobile_Sales'].sum().reset_index() fig1 = px.line( yas, x='Year', y='Automobile_Sales', title="年度汽车总销量趋势", labels={'Automobile_Sales': '总销量'} ) # 图表2:月度总销量(当年) mas = yearly_data.groupby('Month')['Automobile_Sales'].sum().reset_index() fig2 = px.line( mas, x='Month', y='Automobile_Sales', title=f"{input_year}年月度汽车总销量", labels={'Automobile_Sales': '总销量', 'Month': '月份'} ) # 图表3:当年各车型平均销量 avr_vdata = yearly_data.groupby('Vehicle_Type')['Automobile_Sales'].mean().reset_index() fig3 = px.bar( avr_vdata, x='Vehicle_Type', y='Automobile_Sales', title=f"{input_year}年各车型平均销量", labels={'Automobile_Sales': '平均销量', 'Vehicle_Type': '车型'} ) # 图表4:当年各车型广告支出占比 exp_data = yearly_data.groupby('Vehicle_Type')['Advertising_Expenditure'].sum().reset_index() fig4 = px.pie( exp_data, values='Advertising_Expenditure', names='Vehicle_Type', title=f"{input_year}年各车型广告支出占比", labels={'Advertising_Expenditure': '广告总支出'} ) return [fig1, fig2, fig3, fig4] # 默认返回空图表 empty_fig = px.scatter(title="请选择报表类型") return [empty_fig, empty_fig, empty_fig, empty_fig] # 运行应用 if __name__ == '__main__': app.run_server(debug=True)
关键修复说明
- 移除了重复的应用初始化代码,统一设置应用标题
- 下拉菜单初始值改为有效选项,避免无效触发
- 修正了所有图表的列名引用错误,通过
labels参数设置友好的显示标题 - 调整回调逻辑,确保返回值与输出属性类型匹配(直接返回Plotly figure对象)
- 优化了布局的样式,添加间隙和宽度设置,提升可视化效果
内容的提问来源于stack exchange,提问作者user23669588

