You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Dash中使用带双下拉输入的@app.callback实现联动绘图

Dash 联动下拉饼图仪表盘修复方案

核心错误点

  • 未实现下拉联动逻辑:原代码将时间周期下拉选项硬编码,没有新增回调监听地理区域选择值,无法实现二级下拉的动态可选范围更新
  • 下拉模式配置错误:两个下拉均开启multi=True多选,返回值为列表类型,和单值筛选的等值判断逻辑冲突,会直接触发运行报错
  • 数据集引用错误:绘制价值份额饼图时,筛选条件错误引用了存储销量份额的df3表,与目标价值份额数据集df2不匹配,会返回空数据或维度报错
  • 布局逻辑缺陷:外层容器设置display:flex后未指定排列方向,下拉框、图表会横向挤压错位;未给下拉设置初始默认值,搭配prevent_initial_call=True会导致页面初始加载无内容
  • 空值兼容缺失:未处理下拉未选中状态的边界逻辑,容易触发空值筛选报错

关键修改说明

  • 地理区域下拉选项直接从清洗完成的df2中提取唯一值生成,无需硬编码国家列表,后续数据集更新时选项自动同步
  • 新增独立回调实现二级下拉联动:监听地理区域选中值,动态返回对应区域下存在的时间周期选项,未选地理区域时自动同步可选范围
  • 关闭两个下拉的多选模式,设置默认选中值,保证页面初始加载即可渲染图表
  • 修正数据集筛选逻辑,统一使用价值份额数据集df2做筛选聚合
  • 调整布局结构,将控制区下拉和图表分区排列,避免组件挤压

修复后可运行代码

import dash
from dash import html
from dash import dcc
from dash.dependencies import Input, Output
import plotly.express as px
import pandas as pd

pd.options.display.max_columns = None
pd.options.display.max_rows = None
pd.options.display.width=None

# 读取数据集,注意替换为本地实际文件路径
data =  pd.read_csv(r'C:\Users\Sara.Munoz\OneDrive - Unilever\Documents\Sarita.csv', 
                            encoding = "ISO-8859-1",
                            )
df=data

### 数据集宽表转长表逻辑保持原有
df = pd.melt(df, id_vars=['Geography Node Name', 'Geography Id', 'Geography Level',
       'Category Node Name', 'Category Id', 'Category Level',
       'Global Manufacturer Name', 'Global Manufacturer Id',
       'Brand Position Type', 'Brand Position Name', 'Brand Position Id',
       'Local Brand Name', 'Local Brand Id', 'Measure',
       'Currency or Unit of Measure','Latest Available Date'],value_vars=['MAT','L12W','L4W'], var_name='Period',value_name='Data')

### 字段重命名与冗余字段清洗逻辑保持原有
df.rename(columns = {'Geography Node Name':'Geography','Category Node Name':'Category',
                     'Global Manufacturer Name':'Manufacturer','Geography Level':'GLevel'},inplace = True)

df.drop(["Geography Id", "Category Id","Global Manufacturer Id","Brand Position Type",
                  "Brand Position Name","Brand Position Id","Local Brand Name","Local Brand Id","Latest Available Date",
         "Currency or Unit of Measure"], axis = 1, inplace=True)

##### 价值份额数据集处理
df2 = df.loc[df['GLevel'] == 5]
df2 = df2.loc[df2['Measure'] == 'Value Share']
df2 = df2.loc[df2['Category'] == 'Toothpaste']
df2 = df2[df2.Manufacturer != 'ALL MANUFACTURERS']
df2 = df2[df2.Category != 'Oral Care']
df2.drop(["GLevel", "Category","Category Level"], axis = 1, inplace=True)

##### 销量份额数据集处理(本次价值份额饼图暂未调用,保留原有逻辑)
df3 = df.loc[df['GLevel'] == 5]
df3 = df3.loc[df3['Measure'] == 'Volume Share']
df3 = df3.loc[df3['Category'] == 'Toothpaste']
df3 = df3[df3.Manufacturer != 'ALL MANUFACTURERS']
df3 = df3[df3.Category != 'Oral Care']
df3.drop(["GLevel", "Category","Category Level"], axis = 1, inplace=True)
df3=df3.sort_values(['Geography', 'Period'],ascending = [True, True])

###############################################################################

app = dash.Dash(__name__)

# 自动生成地理区域下拉选项,设置默认值
geo_options = [{'label': geo, 'value': geo} for geo in sorted(df2['Geography'].unique())]
default_geo = geo_options[0]['value']
# 生成默认区域对应的时间周期选项
default_period_options = [{'label': p, 'value': p} for p in sorted(df2[df2['Geography']==default_geo]['Period'].unique())]
default_period = default_period_options[0]['value']

app.layout = html.Div(
    [
        # 下拉控制区
        html.Div(
            [
                html.Div(
                    [
                        html.Label("选择地理区域:"),
                        dcc.Dropdown(
                            id="dropdown-1",
                            options=geo_options,
                            value=default_geo,
                            multi=False,
                            clearable=False
                        )
                    ], style={'width': '45%', 'display': 'inline-block', 'margin-right': '5%'}
                ),
                html.Div(
                    [
                        html.Label("选择时间周期:"),
                        dcc.Dropdown(
                            id="dropdown-2",
                            options=default_period_options,
                            value=default_period,
                            multi=False,
                            clearable=False
                        )
                    ], style={'width': '45%', 'display': 'inline-block'}
                )
            ], style={'padding': '20px', 'border-bottom': '1px solid #eee'}
        ),
        # 图表展示区
        html.Div([], id="plot1", style={'padding': '20px'})
    ]
)

# 回调1:实现二级下拉联动,根据选中地理区域更新时间周期可选范围
@app.callback(
    [Output("dropdown-2", "options"), Output("dropdown-2", "value")],
    Input("dropdown-1", "value")
)
def update_period_dropdown(selected_geo):
    available_periods = sorted(df2[df2['Geography']==selected_geo]['Period'].unique())
    period_options = [{'label': p, 'value': p} for p in available_periods]
    selected_period = available_periods[0]
    return period_options, selected_period

# 回调2:根据两个下拉选中值渲染价值份额饼图
@app.callback(
    Output("plot1", "children"),
    [Input("dropdown-1", "value"), Input("dropdown-2", "value")]
)
def get_graph(entered_Geo, entered_Period):
    # 修正数据集引用错误,统一使用价值份额表df2做筛选
    fd = df2[(df2['Geography']==entered_Geo) &
             (df2['Period']==entered_Period)]
    # 按厂商聚合份额,兼容单厂商多条重复数据的场景
    g1= fd.groupby(['Manufacturer'],as_index=False)['Data'].mean()
    plot1= px.pie(g1, values='Data', names='Manufacturer', title=f"{entered_Geo} {entered_Period} 牙膏品类厂商价值份额")
    return dcc.Graph(figure=plot1)

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

注:如果后续需要切换展示销量份额饼图,仅需将图表回调中的筛选数据集从df2替换为df3,同步修改图表标题即可。

内容的提问来源于stack exchange,提问作者Sara Arenas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 17:15:44