我正在尝试使用绘图仪表板中的多个下拉栏来过滤数据。总共有 5 个下拉选项。我希望前 3 个独立运行,而后两个应该双向链接到前 3 个。
具体来说,我要实现的功能是:
所有值的默认值应始终是初始起点
前 3 个选项(年份、季节和月份)应独立运行。如上所示,这 3 个的任意组合都可以添加到输出中。 如果选择了一项,则应使用这些值更新输出。但是,如果从另一个下拉列表中选择某个项目,则应将这些值添加到输出中。以下 i) 中的示例。
选项 4-5(temp 和 prec)应双向链接到前三个下拉选项(Year、Season 和 Month)。这应该是可逆的或双向的。 如果选择前 3 个下拉选项之一,则表输出应使用这些值进行更新,并且下拉列表应减少为仅允许用户从这些值中进行选择。以下 ii) 中的示例。
提供具体例子;
i) 从第一个下拉选项中的年份中选择 2012 年。表输出显示相关值。用户应该能够在“年份”下拉列表中选择任何后续值(功能)。 但是,如果用户还想从第二个下拉选项中查看 Spr 值,则应将该数据添加到输出中。
ii) 对于应链接到前 3 个的 4-5 个下拉选项,如果在 temp 中选择 Hot 和 Mild,在 prec 中选择 Wet,则前三个选项中的下拉列表应减少为: Year = 2013 年、2015 年;季节 = 春季、秋季;月份 = 四月、六月、十月、十二月
import pandas as pd
from dash import Dash, dcc, html, Input, Output, dash_table
import dash_bootstrap_components as dbc
from itertools import cycle
import random
Year = cycle(['2012','2013','2014','2015'])
Season = cycle(['Win','Spr','Sum','Fall'])
Month = cycle(['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'])
temp_group = cycle(['Hot','Cold','Mild'])
prec_group = cycle(['Dry','Wet'])
df = pd.DataFrame(index = range(20))
df['option1'] = [next(Year) for count in range(df.shape[0])]
df['option2'] = [next(Season) for count in range(df.shape[0])]
df['option3'] = [next(Month) for count in range(df.shape[0])]
df['option4'] = [next(temp_group) for count in range(df.shape[0])]
df['option5'] = [next(prec_group) for count in range(df.shape[0])]
option1_list = sorted(df['option1'].unique().tolist())
option2_list = df['option2'].unique().tolist()
option3_list = df['option3'].unique().tolist()
option4_list = sorted(df['option4'].unique().tolist())
option5_list = sorted(df['option5'].unique().tolist())
app = Dash(__name__)
app.layout = html.Div([
dbc.Card(
dbc.CardBody([
dbc.Row([
dbc.Col([
html.P("Option 1"),
html.Div([
dcc.Dropdown(id='option1_dropdown',
options=option1_list,
value=[],
placeholder='All',
multi=True,
clearable=True),
],
style={'width': '100%', 'display': 'inline-block'})
]),
dbc.Col([
html.P("Option 2"),
html.Div([
dcc.Dropdown(id='option2_dropdown',
options=option2_list,
value=[],
placeholder='All',
multi=True,
clearable=True),
],
style={'width': '100%', 'display': 'inline-block'})
]),
dbc.Col([
html.P("Option 3"),
html.Div([
dcc.Dropdown(id='option3_dropdown',
options=option3_list,
value=[],
placeholder='All',
multi=True,
clearable=True),
],
style={'width': '100%', 'display': 'inline-block'})
]),
dbc.Col([
html.P("Option 4"),
html.Div([
dcc.Dropdown(id='option4_dropdown',
options=option4_list,
value=[],
placeholder='All',
multi=True,
clearable=True),
],
style={'width': '100%', 'display': 'inline-block'})
]),
dbc.Col([
html.P("Option 5"),
html.Div([
dcc.Dropdown(id='option5_dropdown',
options=option5_list,
value=[],
placeholder='All',
multi=True,
clearable=True),
],
style={'width': '100%', 'display': 'inline-block'})
]),
], align='center'),
]), color='dark'
),
dbc.Card(
dbc.CardBody([
dbc.Row([
html.Div([
html.Div(id='dd-output-container')
])
], align='center'),
]), color='dark'
),
dbc.Card(
dbc.CardBody([
dbc.Row([
html.Div([
dash_table.DataTable(
id='table_container',
data=df.to_dict('records')
)
])
], align='center'),
]), color='dark'
)
])
@app.callback(
Output('table_container', 'data'),
[Input('option1_dropdown', 'value'),
Input('option2_dropdown', 'value'),
Input('option3_dropdown', 'value'),
Input('option4_dropdown', 'value'),
Input('option5_dropdown', 'value')
])
def set_dropdown_options(value1, value2, value3, value4, value5):
if not value1 or value1 == 'All':
value1 = option1_list
if not value2 or value2 == 'All':
value2 = option2_list
if not value3 or value3 == 'All':
value3 = option3_list
if not value4 or value4 == 'All':
value4 = option4_list
if not value5 or value5 == 'All':
value5 = option5_list
ddf = df.query('option1 == @value1 and '
'option2 == @value2 and '
'option3 == @value3 and '
'option4 == @value4 and '
'option5 == @value5',
engine='python')
return ddf.to_dict('records')
# ====== Using this as a way to view the selections
@app.callback(
Output('dd-output-container', 'children'),
[Input('option1_dropdown', 'value'),
Input('option2_dropdown', 'value'),
Input('option3_dropdown', 'value'),
Input('option4_dropdown', 'value'),
Input('option5_dropdown', 'value')
])
def selection(value1, value2, value3, value4, value5):
# If value lists are empty or equal to the default of 'All', use the initial df values
if not value1 or value1 == 'All':
value1 = option1_list
if not value2 or value2 == 'All':
value2 = option2_list
if not value3 or value3 == 'All':
value3 = option3_list
if not value4 or value4 == 'All':
value4 = option4_list
if not value5 or value5 == 'All':
value5 = option5_list
ddf = df.query('option1 == @value1 and '
'option2 == @value2 and '
'option3 == @value3 and '
'option4 == @value4 and '
'option5 == @value5',
engine='python')
return
if __name__ == '__main__':
app.run_server(debug=True, dev_tools_hot_reload = False)
编辑2:
有没有办法包含原始列名而不转换为使用整数后缀?
Year = cycle(["2012", "2013", "2014", "2015"])
Season = cycle(["Win", "Spr", "Sum", "Fall"])
Month = cycle(
["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
)
temp_group = cycle(["Hot", "Cold", "Mild"])
prec_group = cycle(["Dry", "Wet"])
df = pd.DataFrame(index=range(20))
df["Year"] = [next(Year) for count in range(df.shape[0])]
df["Season"] = [next(Season) for count in range(df.shape[0])]
df["Month"] = [next(Month) for count in range(df.shape[0])]
df["Temp"] = [next(temp_group) for count in range(df.shape[0])]
df["Prec"] = [next(prec_group) for count in range(df.shape[0])]
Year_list = sorted(df["Year"].unique().tolist())
Season_list = df["Season"].unique().tolist()
Month_list = df["Month"].unique().tolist()
Temp_list = sorted(df["Temp"].unique().tolist())
Prec_list = sorted(df["Prec"].unique().tolist())
df = df.rename(columns = {'Year':'option1',
'Season':'option2',
'Month':'option3',
'Temp':'option4',
'Prec':'option5'})
app = Dash(__name__)
app.layout = html.Div(
[
dbc.Card(
dbc.CardBody(
[
dbc.Row(
[
dbc.Col(
[
html.P("Year"),
html.Div(
[
dcc.Dropdown(
id="Year_dropdown",
options=Year_list,
value=[],
placeholder="All",
multi=True,
clearable=True,
),
],
style={
"width": "100%",
"display": "inline-block",
},
),
]
),
dbc.Col(
[
html.P("Season"),
html.Div(
[
dcc.Dropdown(
id="Season_dropdown",
options=Season_list,
value=[],
placeholder="All",
multi=True,
clearable=True,
),
],
style={
"width": "100%",
"display": "inline-block",
},
),
]
),
dbc.Col(
[
html.P("Month"),
html.Div(
[
dcc.Dropdown(
id="Month_dropdown",
options=Month_list,
value=[],
placeholder="All",
multi=True,
clearable=True,
),
],
style={
"width": "100%",
"display": "inline-block",
},
),
]
),
dbc.Col(
[
html.P("Temp"),
html.Div(
[
dcc.Dropdown(
id="Temp_dropdown",
options=Temp_list,
value=[],
placeholder="All",
multi=True,
clearable=True,
),
],
style={
"width": "100%",
"display": "inline-block",
},
),
]
),
dbc.Col(
[
html.P("Prec"),
html.Div(
[
dcc.Dropdown(
id="Prec_dropdown",
options=Prec_list,
value=[],
placeholder="All",
multi=True,
clearable=True,
),
],
style={
"width": "100%",
"display": "inline-block",
},
),
]
),
],
align="center",
),
]
),
color="dark",
),
dbc.Card(
dbc.CardBody(
[
dbc.Row(
[html.Div([html.Div(id="dd-output-container")])], align="center"
),
]
),
color="dark",
),
dbc.Card(
dbc.CardBody(
[
dbc.Row(
[
html.Div(
[
dash_table.DataTable(
id="table_container", data=df.to_dict("records")
)
]
)
],
align="center",
),
]
),
color="dark",
),
]
)
df = df.rename(columns = {'Year':'option1',
'Season':'option2',
'Month':'option3',
'Temp':'option4',
'Prec':'option5'})
def construct_query(filter_values):
additive_clauses = list()
subtractive_clauses = list()
for i, filter_value in enumerate(filter_values):
if filter_value and filter_value != "All":
clause = f"option{i + 1} == @value{i + 1}"
if i <= 3:
additive_clauses.append(clause)
else:
subtractive_clauses.append(clause)
if len(additive_clauses) > 0 or len(subtractive_clauses) > 0:
additive_section = " or ".join(additive_clauses)
subtractive_clauses = " and ".join(subtractive_clauses)
if additive_section and subtractive_clauses:
query = f"({additive_section}) and {subtractive_clauses}"
else:
query = additive_section or subtractive_clauses
return query
@app.callback(
[
Output("Year_dropdown", "options"),
Output("Season_dropdown", "options"),
Output("Month_dropdown", "options"),
],
[
Input("Temp_dropdown", "value"),
Input("Prec_dropdown", "value"),
],
)
def update_additive_options(value4, value5):
query = None
option4_query = "option4 == @value4"
option5_query = "option5 == @value5"
if value4 and value4 != "All" and value5 and value5 != "All":
query = f"{option4_query} and {option5_query}"
elif value4 and value4 != "All":
query = option4_query
elif value5 and value5 != "All":
query = option5_query
if query:
df_filtered = df.query(
query,
engine="python",
)
else:
df_filtered = df
return (
sorted(df_filtered["option1"].unique().tolist()),
df_filtered["option2"].unique().tolist(),
df_filtered["option3"].unique().tolist(),
)
@app.callback(
[Output("Temp_dropdown", "options"), Output("Prec_dropdown", "options")],
[
Input("Year_dropdown", "options"),
Input("Season_dropdown", "options"),
Input("Month_dropdown", "options"),
],
)
def update_subtractive_options(value1, value2, value3):
query = None
additive_clauses = []
for i, filter_value in enumerate([value1, value2, value3]):
if filter_value and filter_value != "All":
clause = f"option{i + 1} == @value{i + 1}"
additive_clauses.append(clause)
if len(additive_clauses) > 0:
query = " or ".join(additive_clauses)
if query:
df_filtered = df.query(
query,
engine="python",
)
else:
df_filtered = df
return (
sorted(df_filtered["option4"].unique().tolist()),
sorted(df_filtered["option5"].unique().tolist()),
)
@app.callback(
Output("table_container", "data"),
[
Input("Year_dropdown", "value"),
Input("Season_dropdown", "value"),
Input("Month_dropdown", "value"),
Input("Temp_dropdown", "value"),
Input("Prec_dropdown", "value"),
],
)
def update_table(value1, value2, value3, value4, value5):
query = construct_query(filter_values=[value1, value2, value3, value4, value5])
if query:
df_filtered = df.query(
query,
engine="python",
)
else:
df_filtered = df
return df_filtered.to_dict("records")
# ====== Using this as a way to view the selections
@app.callback(
Output("dd-output-container", "children"),
[
Input("Year_dropdown", "value"),
Input("Season_dropdown", "value"),
Input("Month_dropdown", "value"),
Input("Temp_dropdown", "value"),
Input("Prec_dropdown", "value"),
],
)
def selection(value1, value2, value3, value4, value5):
# If value lists are empty or equal to the default of 'All', use the initial df values
if not value1 or value1 == "All":
value1 = Year_list
if not value2 or value2 == "All":
value2 = Season_list
if not value3 or value3 == "All":
value3 = Month_list
if not value4 or value4 == "All":
value4 = Temp_list
if not value5 or value5 == "All":
value5 = Prec_list
ddf = df.query(
"option1 == @value1 and "
"option2 == @value2 and "
"option3 == @value3 and "
"option4 == @value4 and "
"option5 == @value5",
engine="python",
)
return
if __name__ == "__main__":
app.run_server(debug=True, dev_tools_hot_reload=False)
这可以通过绘图破折号来完成。诀窍是在前三个过滤器之间使用
or
运算符使它们相加,并在后两个过滤器之间使用 and
运算符使它们相减。在涉及最后两个选项之前,前三个选项需要作为一个块来解析。
查询结构示例:
(option1 == @value1 or option2 == @value2 or option3 == @value3) and option4 == @value4 and option5 == @value5
我选择根据哪些过滤器具有值以编程方式构建查询,以使
or
逻辑正常工作。 请参阅下面示例中的 construct_query()
函数。
def construct_query(filter_values):
additive_clauses = list()
subtractive_clauses = list()
for i, filter_value in enumerate(filter_values):
if filter_value and filter_value != "All":
clause = f"option{i + 1} == @value{i + 1}"
if i <= 3:
additive_clauses.append(clause)
else:
subtractive_clauses.append(clause)
if len(additive_clauses) > 0 or len(subtractive_clauses) > 0:
additive_section = " or ".join(additive_clauses)
subtractive_clauses = " and ".join(subtractive_clauses)
if additive_section and subtractive_clauses:
query = f"({additive_section}) and {subtractive_clauses}"
else:
query = additive_section or subtractive_clauses
return query
另一个挑战是避免创建具有相同输入和输出组件的循环回调。实现此目的的一种方法是将大型回调分解为多个单独的回调,以便输入和输出不是循环的。在下面的示例中,我将前三个下拉列表的更新分为
update_additive_options()
,将后两个下拉列表的更新分为 update_subtractive_options()
。
Plotly 还描述了另一种管理循环回调的方法
高级回调文档与上下文功能。
这是我的代码的完整版本:
import pandas as pd
from dash import Dash, dcc, html, Input, Output, dash_table
import dash_bootstrap_components as dbc
from itertools import cycle
import random
Year = cycle(["2012", "2013", "2014", "2015"])
Season = cycle(["Win", "Spr", "Sum", "Fall"])
Month = cycle(
["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
)
temp_group = cycle(["Hot", "Cold", "Mild"])
prec_group = cycle(["Dry", "Wet"])
df = pd.DataFrame(index=range(20))
df["option1"] = [next(Year) for count in range(df.shape[0])]
df["option2"] = [next(Season) for count in range(df.shape[0])]
df["option3"] = [next(Month) for count in range(df.shape[0])]
df["option4"] = [next(temp_group) for count in range(df.shape[0])]
df["option5"] = [next(prec_group) for count in range(df.shape[0])]
option1_list = sorted(df["option1"].unique().tolist())
option2_list = df["option2"].unique().tolist()
option3_list = df["option3"].unique().tolist()
option4_list = sorted(df["option4"].unique().tolist())
option5_list = sorted(df["option5"].unique().tolist())
app = Dash(__name__)
app.layout = html.Div(
[
dbc.Card(
dbc.CardBody(
[
dbc.Row(
[
dbc.Col(
[
html.P("Option 1"),
html.Div(
[
dcc.Dropdown(
id="option1_dropdown",
options=option1_list,
value=[],
placeholder="All",
multi=True,
clearable=True,
),
],
style={
"width": "100%",
"display": "inline-block",
},
),
]
),
dbc.Col(
[
html.P("Option 2"),
html.Div(
[
dcc.Dropdown(
id="option2_dropdown",
options=option2_list,
value=[],
placeholder="All",
multi=True,
clearable=True,
),
],
style={
"width": "100%",
"display": "inline-block",
},
),
]
),
dbc.Col(
[
html.P("Option 3"),
html.Div(
[
dcc.Dropdown(
id="option3_dropdown",
options=option3_list,
value=[],
placeholder="All",
multi=True,
clearable=True,
),
],
style={
"width": "100%",
"display": "inline-block",
},
),
]
),
dbc.Col(
[
html.P("Option 4"),
html.Div(
[
dcc.Dropdown(
id="option4_dropdown",
options=option4_list,
value=[],
placeholder="All",
multi=True,
clearable=True,
),
],
style={
"width": "100%",
"display": "inline-block",
},
),
]
),
dbc.Col(
[
html.P("Option 5"),
html.Div(
[
dcc.Dropdown(
id="option5_dropdown",
options=option5_list,
value=[],
placeholder="All",
multi=True,
clearable=True,
),
],
style={
"width": "100%",
"display": "inline-block",
},
),
]
),
],
align="center",
),
]
),
color="dark",
),
dbc.Card(
dbc.CardBody(
[
dbc.Row(
[html.Div([html.Div(id="dd-output-container")])], align="center"
),
]
),
color="dark",
),
dbc.Card(
dbc.CardBody(
[
dbc.Row(
[
html.Div(
[
dash_table.DataTable(
id="table_container", data=df.to_dict("records")
)
]
)
],
align="center",
),
]
),
color="dark",
),
]
)
def construct_query(filter_values):
additive_clauses = list()
subtractive_clauses = list()
for i, filter_value in enumerate(filter_values):
if filter_value and filter_value != "All":
clause = f"option{i + 1} == @value{i + 1}"
if i <= 3:
additive_clauses.append(clause)
else:
subtractive_clauses.append(clause)
if len(additive_clauses) > 0 or len(subtractive_clauses) > 0:
additive_section = " or ".join(additive_clauses)
subtractive_clauses = " and ".join(subtractive_clauses)
if additive_section and subtractive_clauses:
query = f"({additive_section}) and {subtractive_clauses}"
else:
query = additive_section or subtractive_clauses
return query
@app.callback(
[
Output("option1_dropdown", "options"),
Output("option2_dropdown", "options"),
Output("option3_dropdown", "options"),
],
[
Input("option4_dropdown", "value"),
Input("option5_dropdown", "value"),
],
)
def update_additive_options(value4, value5):
query = None
option4_query = "option4 == @value4"
option5_query = "option5 == @value5"
if value4 and value4 != "All" and value5 and value5 != "All":
query = f"{option4_query} and {option5_query}"
elif value4 and value4 != "All":
query = option4_query
elif value5 and value5 != "All":
query = option5_query
if query:
df_filtered = df.query(
query,
engine="python",
)
else:
df_filtered = df
return (
sorted(df_filtered["option1"].unique().tolist()),
df_filtered["option2"].unique().tolist(),
df_filtered["option3"].unique().tolist(),
)
@app.callback(
[Output("option4_dropdown", "options"), Output("option5_dropdown", "options")],
[
Input("option1_dropdown", "value"),
Input("option2_dropdown", "value"),
Input("option3_dropdown", "value"),
],
)
def update_subtractive_options(value1, value2, value3):
query = None
additive_clauses = []
for i, filter_value in enumerate([value1, value2, value3]):
if filter_value and filter_value != "All":
clause = f"option{i + 1} == @value{i + 1}"
additive_clauses.append(clause)
if len(additive_clauses) > 0:
query = " or ".join(additive_clauses)
if query:
df_filtered = df.query(
query,
engine="python",
)
else:
df_filtered = df
return (
sorted(df_filtered["option4"].unique().tolist()),
sorted(df_filtered["option5"].unique().tolist()),
)
@app.callback(
Output("table_container", "data"),
[
Input("option1_dropdown", "value"),
Input("option2_dropdown", "value"),
Input("option3_dropdown", "value"),
Input("option4_dropdown", "value"),
Input("option5_dropdown", "value"),
],
)
def update_table(value1, value2, value3, value4, value5):
query = construct_query(filter_values=[value1, value2, value3, value4, value5])
if query:
df_filtered = df.query(
query,
engine="python",
)
else:
df_filtered = df
return df_filtered.to_dict("records")
# ====== Using this as a way to view the selections
@app.callback(
Output("dd-output-container", "children"),
[
Input("option1_dropdown", "value"),
Input("option2_dropdown", "value"),
Input("option3_dropdown", "value"),
Input("option4_dropdown", "value"),
Input("option5_dropdown", "value"),
],
)
def selection(value1, value2, value3, value4, value5):
# If value lists are empty or equal to the default of 'All', use the initial df values
if not value1 or value1 == "All":
value1 = option1_list
if not value2 or value2 == "All":
value2 = option2_list
if not value3 or value3 == "All":
value3 = option3_list
if not value4 or value4 == "All":
value4 = option4_list
if not value5 or value5 == "All":
value5 = option5_list
ddf = df.query(
"option1 == @value1 and "
"option2 == @value2 and "
"option3 == @value3 and "
"option4 == @value4 and "
"option5 == @value5",
engine="python",
)
return
if __name__ == "__main__":
app.run_server(debug=True, dev_tools_hot_reload=False)