pandas 动态更新下拉选项,回调Dash中有2个输入

7lrncoxx  于 2023-02-14  发布在  其他
关注(0)|答案(1)|浏览(122)

我被困在一个部分,我想通过两种方式更新批次下拉列表。如果在材料下拉列表中没有选择任何内容,批次下拉列表应该显示所有批次,但一旦选择材料,它应该更改为只显示该材料类型的批次。
下面是相关代码现在的样子:

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

app = dash.Dash(__name__)

# Convert PARQUET to CSV

df = pd.read_csv("release_cln.csv")

# Store all unique values from Basis column

df_dis = df[df['Analysis_Name_CLN'].str.contains("Dissolution").fillna(False)]

df_dis['Batch'] = df_dis['Batch'].fillna(9999)
df_dis['Basis'] = df_dis['Batch'].fillna(9999)
df_dis['Material'] = df['Material'].fillna(9999)

batch = df_dis['Batch'].unique()
material = df_dis['Material'].unique()
basis = df_dis['Basis'].unique()

#[{'label': i, 'value': i} for i in material]

app.layout = html.Div([
    html.H1(children='Comparing Batches'),
    html.H2('Dissolution vs Timepoint'),

    # Batch 1 Dropdowns
    html.Div([
        html.Div(
            html.H3(children='''DP Batch''')
        ),
        html.Div([
            html.Div(children='''Batch: '''),
            dcc.Dropdown(
                    id='batch_dd',
                    multi=False,
                    clearable=True,
                    disabled=False
                ),
            ],style={'width': '20%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Material: '''),
            dcc.Dropdown(
                    id='material_dd',
                    multi=False,
                    clearable=True,
                    disabled=False
                ),
            ],style = {'width': '15%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Basis: '''),
            dcc.Dropdown(
                    id='basis_dd',
                    multi=False,
                    clearable=True,
                    disabled=False
                ),
            ],style = {'width': '15%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Variant: '''),
            dcc.Dropdown(
                    id='variant_dd',
                    multi=False,
                    clearable=True,
                    disabled=False
                ),
            ],style = {'width': '10%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Analysis: '''),
            dcc.Dropdown(
                    id='analysis_name_dd',
                    multi=False,
                    clearable=True,
                    disabled=False,
                    options=[]
                ),
            ],style = {'width': '40%','display': 'inline-block'}
        )
        ],style = {'width': '90%', 'display': 'inline-block'}
    ),

    html.Br(),

    # Batch 2 Dropdowns
    html.Div([
        html.Div(
            html.H3(children='''DS Batch''')
        ),
        html.Div([
            html.Div(children='''Batch: '''),
            dcc.Dropdown(
                    id='batch_dd_1',
                    multi=False,
                    clearable=True,
                    disabled=False,
                    options=[{'label': i, 'value': i} for i in batch]
                ),
            ],style={'width': '20%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Material: '''),
            dcc.Dropdown(
                    id='material_dd_1',
                    multi=False,
                    clearable=True,
                    disabled=False,
                    options=[],
                ),
            ],style = {'width': '15%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Basis: '''),
            dcc.Dropdown(
                    id='basis_dd_1',
                    multi=False,
                    clearable=True,
                    disabled=False,
                    options=[]
                ),
            ],style = {'width': '15%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Variant: '''),
            dcc.Dropdown(
                    id='variant_dd_1',
                    multi=False,
                    clearable=True,
                    disabled=False,
                    options=[]
                ),
            ],style = {'width': '10%','display': 'inline-block'}
        ),
        html.Div([
            html.Div(children='''Analysis: '''),
            dcc.Dropdown(
                    id='analysis_name_dd_1',
                    multi=False,
                    clearable=True,
                    disabled=False,
                    options=[]
                ),
            ],style = {'width': '40%','display': 'inline-block'}
        )
        ],style = {'width': '90%', 'display': 'inline-block'}
    ),

    html.Br(),
    dcc.Graph(id='dissol_tp')
])

## Batch 1 Dropdowns
#Call back to update Batch options if user searches for Batches
@app.callback(
    Output('batch_dd','options'),
    [Input('batch_dd','search_value'),
    Input('material_dd','value')]
)
def batch_options(search_value, material_dd):
    if material_dd is None:
        return [{'label': i, 'value': i} for i in batch]
    else:
        batch_df = df[df['Material']==material_dd]
        return [{'label':i,'value':i} for i in batch_df['Batch'].fillna('None').unique()]

@app.callback(
    Output('material_dd','options'),
    Input('material_dd','search_value'))
def material_options(search_value):
    return [{'label': i, 'value': i} for i in material]

# Callback to select default value in basis list
@app.callback(
    Output('basis_dd','options'),
    Input('basis_dd','search_value'))
def variant_default(batch_dd):
    return [k['value'] for k in batch_dd]

# Chained callback to select variants with the selected batch
@app.callback(
    Output('variant_dd','options'),
    Input('batch_dd','value'))
def get_variant(batch_dd):
    batch_df = df[df['Batch']==batch_dd]
    return [{'label':i,'value':i} for i in batch_df['Variant'].fillna('None').unique()]

当前发生的是批次下拉菜单显示所有批次值,即使选择了材料值,也不会动态更改

2ic8powd

2ic8powd1#

动态下拉实时更新

编辑(2023年2月):* 原始答案的简化代码,现在还演示了可选 * Dash dcc.Dropdownmulti=True * 参数 * 的使用。*

两个UI下拉菜单元素。第一个是硬编码的,是一个可能的"材料"选项列表,对应于唯一的"批次"的子集(这里简单地表示为具有#'s 1 - 100的总批次集合)。每种材料通过从总集合(即,这里为total_batches)中抽取50次(即,k=50)来随机取样而不进行替换。

* 用户选择的多个选项(multi=True

如果用户从第一个下拉列表中选择了多个材料选项,则只有在所有选定子集中找到的那些批次将立即在第二个"批次"下拉列表中动态更新(使用set.intersection()在集-投列表的列表上计算[即,材料选定批次子集])。

import random

import dash
import pandas as pd

from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output

app = dash.Dash(__name__)

total_batches = [n + 1 for n in range(100)]

materials = sorted(["wood", "metal", "fire", "water", "ice", "rock"])

df = pd.DataFrame(
    {"Batch": random.sample(total_batches, k=50), "Material": m}
    for m in materials
).set_index("Material")

batches = df.to_dict()["Batch"]

app.layout = html.Div(
    [
        html.H1(children="Comparing Batches"),
        html.H2("Dissolution vs Timepoint"),
        # Batch 1 Dropdowns
        html.Div(
            [
                html.Div(html.H3(children="""DP Batch""")),
                html.Div(
                    [
                        html.Div(children="""Material: """),
                        dcc.Dropdown(
                            id="material_dd",
                            options=[
                                {"label": m, "value": m} for m in materials
                            ],
                            multi=False, # Or set to False
                            clearable=True,
                            disabled=False,
                        ),
                    ],
                    style={"width": "15%", "display": "inline-block"},
                ),
                html.Div(
                    [
                        html.Div(children="""Batch: """),
                        dcc.Dropdown(
                            id="batch_dd",
                            multi=False,
                            clearable=True,
                            disabled=False,
                        ),
                    ],
                    style={"width": "20%", "display": "inline-block"},
                ),
            ]
        ),
    ]
)

## Dynamic Batches Dropdown Updates
# Callback to update Batch options dynamically upon user selection
# actions at the first dropdown ("Materials").
@app.callback(
    Output("batch_dd", "options"), [Input("material_dd", "value")],
)
def batch_options(material_dd):
    if material_dd is None or len(material_dd) < 1:
        return [{"label": i, "value": i} for i in sorted(total_batches)]
    elif type(material_dd) == list and len(material_dd) > 1:
        print(f"User has selected multiple materials options: {material_dd}")
        mult_batch_sets = [set(batches[m]) for m in material_dd]
        batches_intersect = list(set.intersection(*mult_batch_sets))
        print(batches_intersect)
        return [{"label": i, "value": i} for i in sorted(batches_intersect)]
    elif type(material_dd) == list:
        """NOTE: Because the 'multi' parameter for the `material_dd` dropdown
        is set `== True`, even if only one material is currently selected
        the value is returned as a single item list. Hence, the need
        for zero-indexing `material_dd` to prevent a "TypeError: unhashable type:
        'list'" when indexing the `batches` dictionary."""
        # print(sorted(batches[material_dd[0]]))
        m = material_dd[0]
        print(f"User has selected single material value: {m}")
        return [{"label": i, "value": i} for i in sorted(batches[m])]
    else: # ∴ `multi=False` must be set
        return [{"label": i, "value": i} for i in sorted(batches[material_dd])]


print("\n---New Batches random sampling simulation started---")
print(f"{'_'*40}")
print(f"All Batch #'s:\n{sorted(total_batches)}")
print(f"Possible materials:\n{materials}")
print(f"Batches subset by material type:\n{batches}")
print("\n...Awaiting user input...\n")

if __name__ == "__main__":
    app.run_server(debug=True, dev_tools_hot_reload=True)

请注意,每次添加另一个用户选择的选项时,动态同步的第二个下拉列表将更改为显示更新的相应"批处理"(在这种情况下,每个选项["材料"]具有值= 50个从范围1 - 100中唯一随机采样的整数)并且如预期的那样,随着每个进一步选项("材料")的添加,随机采样子集之间存在相当大的(尽管在减少)交集。
(最终,在这个特定的模拟示例中,碰巧在每个可能的"材料"选项中随机抽取了3个批次ID #。)

Stderr报告示例(来自上面记录和显示的实时样本模拟)

Dash is running on http://127.0.0.1:8050/

 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on

---New Batches random sampling simulation started---
________________________________________
All Batch #'s:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
Possible materials:
['fire', 'ice', 'metal', 'rock', 'water', 'wood']
Batches subset by material type:
{'fire': [97, 25, 37, 34, 90, 57, 8, 23, 28, 16, 38, 88, 13, 24, 36, 56, 50, 41, 40, 43, 81, 87, 71, 91, 62, 92, 69, 83, 1, 66, 19, 30, 58, 65, 68, 45, 59, 55, 29, 84, 7, 6, 77, 67, 3, 74, 18, 33, 72, 98],
 'ice': [28, 33, 77, 26, 4, 14, 13, 20, 72, 39, 56, 35, 50, 31, 24, 90, 17, 73, 63, 99, 29, 44, 98, 92, 47, 67, 79, 82, 41, 93, 87, 15, 71, 52, 2, 22, 94, 18, 34, 74, 1, 27, 38, 57, 84, 88, 68, 11, 49, 8],
 'metal': [19, 14, 90, 33, 34, 16, 63, 10, 53, 92, 46, 82, 78, 81, 64, 69, 31, 45, 73, 39, 68, 5, 38, 100, 24, 7, 61, 50, 83, 54, 62, 84, 74, 23, 32, 40, 1, 3, 51, 25, 79, 29, 91, 52, 88, 77, 57, 35, 58, 96],
 'rock': [9, 42, 13, 59, 44, 27, 72, 97, 70, 80, 65, 63, 53, 40, 85, 78, 90, 58, 60, 69, 98, 89, 46, 36, 88, 15, 37, 79, 95, 93, 62, 82, 7, 41, 94, 51, 20, 16, 4, 49, 99, 54, 76, 8, 48, 39, 19, 100, 67, 25],
 'water': [16, 10, 91, 71, 54, 41, 94, 26, 65, 87, 30, 39, 20, 60, 72, 18, 74, 5, 1, 76, 66, 77, 13, 6, 92, 36, 80, 24, 95, 25, 45, 96, 59, 78, 14, 2, 67, 82, 99, 83, 69, 8, 50, 29, 27, 93, 46, 70, 22, 49],
 'wood': [4, 69, 92, 83, 30, 61, 7, 35, 12, 93, 65, 8, 85, 72, 100, 68, 41, 88, 46, 60, 34, 37, 78, 33, 50, 77, 95, 96, 45, 39, 40, 48, 99, 3, 36, 18, 54, 5, 91, 80, 87, 31, 51, 23, 98, 94, 13, 20, 56, 59]}

...Awaiting user input...

User has selected single material value: fire
User has selected multiple materials options: ['fire', 'ice']
[18, 19, 20, 25, 27, 28, 29, 33, 35, 48, 53, 59, 63, 64, 66, 67, 70, 77, 80, 91, 92, 98]
User has selected multiple materials options: ['fire', 'ice', 'metal']
[98, 67, 77, 92, 48, 18, 19, 25, 59, 28, 29, 63]
User has selected multiple materials options: ['fire', 'ice', 'metal', 'rock']
[98, 67, 77, 18, 19, 59, 63]
User has selected multiple materials options: ['fire', 'ice', 'metal', 'rock', 'water']
[59, 77, 63]
User has selected multiple materials options: ['fire', 'ice', 'metal', 'rock', 'water', 'wood']
[59, 77, 63]

原始答案(不使用multi=True参数)

注:即使不需要用户从第一个下拉列表中选择多个选项(在这种情况下,只需进行更改:将multi=False设置为定义的应用布局中的dcc.Dropdown参数。
下面是一个基于您的代码的演示,展示了如何使批处理下拉列表动态地依赖于在物料下拉列表中选择的内容。

import random
import dash
import numpy as np
import pandas as pd

from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output

app = dash.Dash(__name__)

# Convert PARQUET to CSV
# df = pd.read_csv("release_cln.csv")

n = 50
df_dis = pd.DataFrame(
    {
        "Batch": np.random.randint(1, 2000, n),
        "Material": random.choices(
            ["wood", "metal", "fire", "water", "ice", "rock"], k=n
        ),
    }
)

batches = {
    m: list(df_dis[df_dis.Material == m].Batch.values)
    for m in df_dis.Material
}

batch = df_dis["Batch"].unique()
material = df_dis["Material"].unique()

app.layout = html.Div(
    [
        html.H1(children="Comparing Batches"),
        html.H2("Dissolution vs Timepoint"),
        # Batch 1 Dropdowns
        html.Div(
            [
                html.Div(html.H3(children="""DP Batch""")),
                html.Div(
                    [
                        html.Div(children="""Material: """),
                        dcc.Dropdown(
                            id="material_dd",
                            multi=False,
                            clearable=True,
                            disabled=False,
                        ),
                    ],
                    style={"width": "15%", "display": "inline-block"},
                ),
                html.Div(
                    [
                        html.Div(children="""Batch: """),
                        dcc.Dropdown(
                            id="batch_dd",
                            multi=False,
                            clearable=True,
                            disabled=False,
                        ),
                    ],
                    style={"width": "20%", "display": "inline-block"},
                ),
            ]
        ),
    ]
)

## Batch 1 Dropdowns
# Call back to update Batch options if user searches for Batches
@app.callback(
    Output("batch_dd", "options"),
    [Input("batch_dd", "search_value"), Input("material_dd", "value")],
)
def batch_options(search_value, material_dd):
    if material_dd is None:
        return [{"label": i, "value": i} for i in batch]
    else:
        return [{"label": i, "value": i} for i in batches[material_dd]]

@app.callback(
    Output("material_dd", "options"), Input("material_dd", "search_value")
)
def material_options(search_value):
    return [{"label": i, "value": i} for i in material]

if __name__ == "__main__":
    app.run_server(debug=True, dev_tools_hot_reload=True)

我是这么做的:

  • 创建一个字典,其中 Dataframe 中的每个唯一物料构成关键字,并且每个物料都有一个相应的列表(假设所有物料可能值在df中有〉1行),特别是具有所选物料类型的批次。
  • 同样,您可以继续进一步嵌套下拉菜单🙂

相关问题