Графический штриховой график с несколькими раскрывающимися списками не работает

Я пытаюсь создать пунктирный линейный график временного ряда, который имеет несколько интерактивных выпадающих переменных, вводимых пользователем. В идеале я хотел бы, чтобы каждый из раскрывающихся входов позволял выбирать несколько вариантов.

Хотя я могу успешно создавать раскрывающиеся меню, диаграмма не обновляется, как хотелось бы. Когда я разрешаю раскрывающимся спискам иметь несколько вариантов выбора, я получаю сообщение об ошибке, что массивы имеют разную длину. И когда я ограничиваю раскрывающиеся списки одним выбором, я получаю сообщение об ошибке, что [‘Vendor_Name’] отсутствует в индексе. Так что это могут быть две отдельные проблемы.

График, который не работает:

Фрагмент данных Excel, импортированных в DF

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import pandas as pd
#import plotly.graph_objs as go 
df = pd.read_csv("Data.csv", sep = "\t")
df['YearMonth'] = pd.to_datetime(df['YearMonth'], format = '%Y-%m')
cols = ['Product_1', 'Product_2', 'Product_3']
vendor = df['Vendor'].unique()

app = dash.Dash('Data')

app.layout = html.Div([
    html.Div([
        html.Div([

            html.Label('Product'),
            dcc.Dropdown(
                 id = 'product',
                 options = [{
                         'label' : i, 
                         'value' : i
                 } for i in cols],
                multi = True,
                value = 'Product_1'

                 ),
                ]),

        html.Div([

            html.Label('Vendor'),
            dcc.Dropdown(
             id = 'vendor',
             options = [{
                     'label' : i, 
                     'value' : i
             } for i in vendor],
            multi = True,
             value = 'ABC')
             ,
        ]),
            ]),

    dcc.Graph(id = 'feature-graphic')
    ])


@app.callback(Output('feature-graphic', 'figure'),
    [Input('product', 'value'),
     Input('vendor', 'value')])


def update_graph(input_vendor, input_column):


    df_filtered = df[df['Vendor'] == input_vendor]

##also tried setting an index because of the error I was getting. Not sure if necessary
    df_filtered = df_filtered.set_index(['Vendor']) 

    traces = []

    df_by_col = df_filtered[[input_column, 'YearMonth']]

    traces.append({

        'x' :pd.Series(df_by_col['YearMonth']),
        'y' : df_by_col[input_column],
        'mode' : 'lines',
        'type' : 'scatter',
        'name' :'XYZ'}
        )

    fig = {
                    'data': traces,
                    'layout': {'title': 'Title of Chart'}
                    }
    return fig


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

Заранее благодарим за помощь! Все еще новичок в Python, но очень взволнован возможностями Dash. Мне удалось создать другие графики с отдельными входными данными, и я прочитал документацию.


person oshapira    schedule 20.12.2018    source источник


Ответы (2)


Вот подход, которого я придерживался: (редактируя общий пример, доступный в Google, с моим подходом):

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash(__name__)

all_options = {
    'America': ['New York City', 'San Francisco', 'Cincinnati'],
    'Canada': [u'Montréal', 'Toronto', 'Ottawa']
}
app.layout = html.Div([
    dcc.Dropdown(
        id='countries-dropdown',
        options=[{'label': k, 'value': k} for k in all_options.keys()],
        value='America',  #default value to show
        multi=True,
        searchable=False
    ),

    dcc.Dropdown(id='cities-dropdown', multi=True, searchable=False, placeholder="Select a city"),

    html.Div(id='display-selected-values')
])

@app.callback(
    dash.dependencies.Output('cities-dropdown', 'options'),
    [dash.dependencies.Input('countries-dropdown', 'value')])
def set_cities_options(selected_country):
    if type(selected_country) == 'str':
        return [{'label': i, 'value': i} for i in all_options[selected_country]]
    else:
        return [{'label': i, 'value': i} for country in selected_country for i in all_options[country]]

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

Временное решение: если в родительском раскрывающемся списке присутствует один ввод, значение имеет строковый формат. Но для нескольких значений он представлен в формате списка.
Этот код также отлично работает и автоматически обновляется, даже когда вы нажимаете на крестик, чтобы удалить любой выбранный вариант.

Примечание. Я использовал атрибут "заполнитель" вместо определения для него значения по умолчанию, поскольку в данном случае это не имело смысла. Но вы также можете динамически обновлять значение аналогичным образом.

person Ayushi    schedule 28.06.2020

1 входные данные

Данные в csv трудно зациклить. И я бы сказал, что это основная причина, по которой ваш код не работает, потому что вы, кажется, понимаете фундаментальную структуру кода. Надев мои SQL-очки, я думаю, вам стоит попытаться заставить его что-то вроде

Date, Vendor, ProductName, Value

2 изменения типа ввода обратного вызова

multi сложно, потому что он меняет переключатели между возвратом str, если выбран только 1 элемент, и list, если выбрано несколько

3 тип возврата обратного вызова

код возвращает dict, но обратный вызов объявил figure как возвращаемый тип

но вот код со следами отладки print() и sleep()

import pandas as pd
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly.graph_objs as go
import time

df = pd.read_csv("Data.csv", sep="\t")
df['YearMonth'] = pd.to_datetime(df['YearMonth'], format='%Y-%m')
products = ['Product_1', 'Product_2', 'Product_3']
vendors = df['Vendor'].unique()

app = dash.Dash('Data')

app.layout = html.Div([
    html.Div([
        html.Div([
            html.Label('Product'),
            dcc.Dropdown(
                id='product',
                options=[{'label' : p, 'value' : p} for p in products],
                multi=True,
                value='Product_1'
            ),
        ]),
        html.Div([
            html.Label('Vendor'),
            dcc.Dropdown(
                id='vendor',
                options=[{'label': v, 'value': v} for v in vendors],
                multi=True,
                value='ABC'
            ),
        ]),
    ]),
    dcc.Graph(id='feature-graphic', figure=go.Figure())
])


@app.callback(
    Output('feature-graphic', 'figure'),
    [Input('product', 'value'),
     Input('vendor', 'value')])
def update_graph(input_product, input_vendor):
    # df_filtered[['Product_1', 'YearMonth']]
    if type(input_product) == str:
        input_product = [input_product]
    if type(input_vendor) == str:
        input_vendor= [input_vendor]

    datasets = ['']
    i = 1
    for vendor in input_vendor:
        df_filtered = df[df['Vendor'] == vendor]
        for product in input_product:
            datasets.append((df_filtered[['YearMonth', 'Vendor', product]]).copy())
            datasets[i]['ProductName'] = product
            datasets[i].rename(columns={product: 'Value'}, inplace=True)
            i += 1
    datasets.pop(0)
    print(datasets)

    traces = ['']
    for dataset in datasets:
        print(dataset)
        time.sleep(1)
        traces.append(
            go.Scatter({
                'x': dataset['YearMonth'],
                'y': dataset['Value'],
                'mode': 'lines',
                'name': f"Vendor: {dataset['Vendor'].iloc[0]} Product: {dataset['ProductName'].iloc[0]}"
        }))
    traces.pop(0)
    layout = {'title': 'Title of Chart'}

    fig = {'data': traces, 'layout': go.Layout(layout)}
    return go.Figure(fig)


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

быстрое и грязное раскрытие:

Если вы обрабатываете файл 1. выпуск резко упростит все. Поэтому я бы попытался изолировать pd.DataFrame() жонглирование обратным вызовом и верхней частью ввода-вывода.

1) не используйте счетчики в циклах

2) мои имена переменных тоже не самые лучшие

3) следующий стиль - питон пещерного человека и there must be a better way:

traces = ['']
traces.append(this_and_that)
traces.pop(0)

В целом:

используя print(input_variable) и print(type(input_variable)) большую часть времени вытаскиваю колеса из грязи.

после всего

вы должны заметить, что каждый trace получил свое индивидуальное имя, которое будет отображаться в легенде. Щелчок по имени в легенде добавит или удалит trace без необходимости@app.callback()

person erkandem    schedule 29.12.2018