Как сообщить средние значения столбцов с помощью фильтрации по данным Python Dash - PullRequest
1 голос
/ 17 июня 2020

Я использую Python Da sh для создания приложения, в котором я могу отображать da sh datatable и сообщать средства столбца datatable (базовый pandas dataframe) как da sh datable прямо под ним .. До сих пор я пробовал:


import dash
from dash.dependencies import Input, Output
import dash_table
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import json
wide_data = pd.DataFrame(np.array([[1.24, 2.34, 3.234], [4.24, .45, .06], [7, 8, 9]]),
                   columns=['a', 'b', 'c'])


df = pd.DataFrame(wide_data)


df = pd.DataFrame(wide_data)
df_floats = df.select_dtypes(include=['float64'])
df_floats_means = df_floats.mean(numeric_only=True)
df_floats_means = pd.DataFrame(df_floats_means)
df_floats_means_T = df_floats_means.T


app = dash.Dash(__name__)

app.layout = html.Div([
    html.H1('Customer Complaints Dashboard'),
    html.Label('Read and Writing Queries Filtering'),
#     html.Label2('If "Read filter_query" is chosen, use operators (le, ge, <, <=, >, >=) + tag value to filter'),    
#     html.Label3('If "Write filter_query" is chosen, ___'),

    dcc.RadioItems(
        id='filter-query-read-write',
        options=[
            {'label': 'Read filter_query', 'value': 'read'},
        ],
        value='read'
    ),

    html.Br(),

    dcc.Input(id='filter-query-input', placeholder='Enter filter query'), #this is for write functionality

    html.Div(id='filter-query-output'), #this is associated with read screen

    html.Hr(),

    dash_table.DataTable(
        id='datatable-advanced-filtering',
        columns=[
            {'name': i, 'id': i, 'deletable': True} for i in df.columns
            # omit the id column
            if i != 'id'
        ],

        data=df.to_dict('records'),
        editable=True,
        page_action='native',
        page_size=10,
        filter_action="native",
    fixed_columns={'headers': True, 'data': 1},
    style_table={'minWidth': '100%'}
    ),

    html.Hr(),
    html.Div(id='datatable-query-structure', style={'whitespace': 'pre'}),
    html.Hr(),
    html.Label('Dataframe Means'),

    dash_table.DataTable(
        id='datatable-advanced-filtering2',
        columns=[
            {'name': i, 'id': i, 'deletable': True} for i in df_floats_means_T.columns
            # omit the id column
            if i != 'id'
        ],

        data=df_floats_means_T.to_dict('records'),
        editable=True,
        page_action='native',
        page_size=10,
     #   filter_action="native",
    fixed_columns={'headers': True, 'data': 1},
    style_table={'minWidth': '100%'}
    ),


    html.Hr(),
    html.Div(id='datatable-query-structure2', style={'whitespace': 'pre'}),
    html.Hr(),



])


@app.callback(
    [Output('filter-query-input', 'style'),
     Output('filter-query-output', 'style')],
    [Input('filter-query-read-write', 'value')]
)
def query_input_output(val):
    input_style = {'width': '100%'}
    output_style = {}
    if val == 'read':
        input_style.update(display='none')
        output_style.update(display='inline-block')
    else:
        input_style.update(display='inline-block')
        output_style.update(display='none')
    return input_style, output_style


@app.callback(
    Output('filter-query-output', 'children'),
    [Input('datatable-advanced-filtering', 'filter_query')]
)
def read_query(query):  
    if query is None:
        return "No filter query"
    return dcc.Markdown('`filter_query = "{}"`'.format(query))

@app.callback(
    Output('datatable-advanced-filtering2', 'data'),
    [Input('datatable-advanced-filtering', 'data')]
)

def update_means(data):
    dff = df
    df_floats = dff.select_dtypes(include=['float64'])
    df_floats_means = df_floats.mean(numeric_only=True)
    df_floats_means = pd.DataFrame(df_floats_means)
    df_floats_means_T = df_floats_means.T

    converted_means = df_floats_means_T.to_dict('records')
    return([converted_means])

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

Но я знаю, что это неправильно, поскольку я вычисляю средние значения до того, как будет выполнена фильтрация в этой части макет приложения id='datatable-advanced-filtering2'. Не могли бы вы помочь мне с обратным звонком?

Я на 100% новичок в da sh, и я не видел много рекомендаций, как это сделать надлежащим образом. Любая помощь будет очень признательна. Спасибо.

Ответы [ 2 ]

1 голос
/ 17 июня 2020

Я считаю, что вы можете использовать свойство data вашего основного DataTable в качестве ввода для функции обратного вызова, а Output - свойство data ваших средств DataTable. Структура этого может выглядеть примерно так:

@app.callback(
    Output('datatable-advanced-filtering2', 'data'),
    [Input('datatable-advanced-filtering', 'data')]
)
def update_means(data):
    # Use data to construct dataframe
    # Find means using same method used at top of post
    # Convert means df using .to_dict('records')
    # Return converted dict

В документах говорится о фильтрации (выделено мной):

По умолчанию эти преобразования выполняются на стороне клиента. Ваши обратные вызовы Da sh могут реагировать на эти изменения, прослушивая свойство data как входное .

Вы можете прочитать больше здесь: https://dash.plotly.com/datatable/interactivity

РЕДАКТИРОВАТЬ

Итак, после небольшого экспериментирования я понял, что, когда filter_action установлен на 'native', фильтрация DataTable на самом деле не обновить его атрибут data. Поскольку атрибут на самом деле не обновляется, любой обратный вызов, использующий атрибут data как Input, не будет срабатывать при фильтрации. Чтобы обойти это и использовать атрибут data в качестве входных данных для нашего обратного вызова, нам нужно установить filter_action на 'custom', а затем самостоятельно реализовать обратный вызов фильтрации. К счастью, в документации Da sh есть пример того, как это сделать здесь: https://dash.plotly.com/datatable/filtering

Используя информацию из этих документов, я смог заставить пример работать. Я оставлю это на ваше усмотрение, чтобы выяснить внутреннюю работу метода фильтрации, но следующий код выполняется для меня локально и показывает средства обновления DataTable, когда основной DataTable фильтруется. Единственная другая модификация, которую я внес, была в функцию update_means, в результате чего я изменил ее с возврата list, как в вашем примере кода, на возврат dict, который является правильным типом для атрибута data.

import dash
from dash.dependencies import Input, Output
import dash_table
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import json
import numpy as np
from dash import Dash

# Instantiate df and calculate initial means
wide_data = pd.DataFrame(np.array([[1.24, 2.34, 3.234], [4.24, .45, .06], [7, 8, 9]]),
                         columns=['a', 'b', 'c'])
df = pd.DataFrame(wide_data)
df_floats = df.select_dtypes(include=['float64'])
df_floats_means = df_floats.mean(numeric_only=True)
df_floats_means = pd.DataFrame(df_floats_means)
df_floats_means_T = df_floats_means.T

# Operators used by custom filtering
operators = [['ge ', '>='],
             ['le ', '<='],
             ['lt ', '<'],
             ['gt ', '>'],
             ['ne ', '!='],
             ['eq ', '='],
             ['contains '],
             ['datestartswith ']]

# Initialize app
app = Dash(__name__)

# Init layout
app.layout = html.Div([
    html.H1('Customer Complaints Dashboard'),
    html.Label('Read and Writing Queries Filtering'),
    #     html.Label2('If "Read filter_query" is chosen, use operators (le, ge, <, <=, >, >=) + tag value to filter'),
    #     html.Label3('If "Write filter_query" is chosen, ___'),

    dcc.RadioItems(
        id='filter-query-read-write',
        options=[
            {'label': 'Read filter_query', 'value': 'read'},
        ],
        value='read'
    ),

    html.Br(),

    # this is for write functionality
    dcc.Input(id='filter-query-input', placeholder='Enter filter query'),

    html.Div(id='filter-query-output'),  # this is associated with read screen

    html.Hr(),

    dash_table.DataTable(
        id='datatable-advanced-filtering',
        columns=[
            {'name': i, 'id': i, 'deletable': True} for i in df.columns
            # omit the id column
            if i != 'id'
        ],

        data=df.to_dict('records'),
        editable=True,
        page_action='native',
        page_size=10,
        filter_action="custom",
        fixed_columns={'headers': True, 'data': 1},
        style_table={'minWidth': '100%'}
    ),

    html.Hr(),
    html.Div(id='datatable-query-structure', style={'whitespace': 'pre'}),
    html.Hr(),
    html.Label('Dataframe Means'),

    dash_table.DataTable(
        id='datatable-advanced-filtering2',
        columns=[
            {'name': i, 'id': i, 'deletable': True} for i in df_floats_means_T.columns
            # omit the id column
            if i != 'id'
        ],

        data=df_floats_means_T.to_dict('records'),
        editable=True,
        page_action='native',
        page_size=10,
        filter_action="native",
        fixed_columns={'headers': True, 'data': 1},
        style_table={'minWidth': '100%'}
    ),


    html.Hr(),
    html.Div(id='datatable-query-structure2', style={'whitespace': 'pre'}),
    html.Hr(),



])


@app.callback(
    [Output('filter-query-input', 'style'),
     Output('filter-query-output', 'style')],
    [Input('filter-query-read-write', 'value')]
)
def query_input_output(val):
    input_style = {'width': '100%'}
    output_style = {}
    if val == 'read':
        input_style.update(display='none')
        output_style.update(display='inline-block')
    else:
        input_style.update(display='inline-block')
        output_style.update(display='none')
    return input_style, output_style


@app.callback(
    Output('filter-query-output', 'children'),
    [Input('datatable-advanced-filtering', 'filter_query')]
)
def read_query(query):
    if query is None:
        return "No filter query"
    return dcc.Markdown('`filter_query = "{}"`'.format(query))


# Callback to re-calculate means after filtering is
@app.callback(
    Output('datatable-advanced-filtering2', 'data'),
    [Input('datatable-advanced-filtering', 'data')]
)
def update_means(data):
    # Calculate means from data currently stored in top datatable
    dff = pd.DataFrame.from_dict(data)
    df_floats = dff.select_dtypes(include=['float64'])
    df_floats_means = df_floats.mean(numeric_only=True)
    df_floats_means = pd.DataFrame(df_floats_means)
    df_floats_means_T = df_floats_means.T

    # Return means to means datatable
    #THIS NOW RETURNS DICT INSTEAD OF LIST
    converted_means = df_floats_means_T.to_dict('records')
    return converted_means


def split_filter_part(filter_part):
    '''Helper function for custom filtering'''
    for operator_type in operators:
        for operator in operator_type:
            if operator in filter_part:
                name_part, value_part = filter_part.split(operator, 1)
                name = name_part[name_part.find('{') + 1: name_part.rfind('}')]

                value_part = value_part.strip()
                v0 = value_part[0]
                if (v0 == value_part[-1] and v0 in ("'", '"', '`')):
                    value = value_part[1: -1].replace('\\' + v0, v0)
                else:
                    try:
                        value = float(value_part)
                    except ValueError:
                        value = value_part

                # word operators need spaces after them in the filter string,
                # but we don't want these later
                return name, operator_type[0].strip(), value

    return [None] * 3


@app.callback(
    Output('datatable-advanced-filtering', "data"),
    [Input('datatable-advanced-filtering', "filter_query")])
def update_table(filter):
    '''Callback that handles custom filtering of top datatable'''
    if filter is None:
        return df.to_dict('records')
    filtering_expressions = filter.split(' && ')
    dff = df
    for filter_part in filtering_expressions:
        col_name, operator, filter_value = split_filter_part(filter_part)

        if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
            # these operators match pandas series operator method names
            dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
        elif operator == 'contains':
            dff = dff.loc[dff[col_name].str.contains(filter_value)]
        elif operator == 'datestartswith':
            # this is a simplification of the front-end filtering logic,
            # only works with complete fields in standard format
            dff = dff.loc[dff[col_name].str.startswith(filter_value)]

    return dff.to_dict('records')


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

Сообщите мне, помогло ли это или возникли проблемы с запуском кода.

0 голосов
/ 18 июня 2020

@ ncascale Большое спасибо, но здесь есть одна небольшая проблема:

def update_table(filter):
    '''Callback that handles custom filtering of top datatable'''
    filtering_expressions = filter.split(' && ')
    dff = df
    for filter_part in filtering_expressions:
        col_name, operator, filter_value = split_filter_part(filter_part)

        if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
            # these operators match pandas series operator method names
            dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
        elif operator == 'contains':
            dff = dff.loc[dff[col_name].str.contains(filter_value)]
        elif operator == 'datestartswith':
            # this is a simplification of the front-end filtering logic,
            # only works with complete fields in standard format
            dff = dff.loc[dff[col_name].str.startswith(filter_value)]

    return dff.to_dict('records')

Он работает, но появляется ошибка:

AttributeError: 'NoneType' object has no attribute 'split'

Это связано с тем, что я должен сначала проверить, должен ли переданный здесь параметр "filter" быть None с оператором If?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...