user53526356
user53526356

Reputation: 968

Plotly-Dash: How to filter dashboard with multiple dataframe columns?

I have a Python dashboard built using dash, that I want to filter on either the Investor or the Fund column.

  Investor    Fund Period Date Symbol  Shares  Value
0     Rick  Fund 3  2019-06-30   AVLR       3      9
1     Faye  Fund 2  2015-03-31    MEG      11     80
2     Rick  Fund 3  2018-12-31    BAC      10    200
3      Dre  Fund 4  2020-06-30   PLOW       2     10
4     Faye  Fund 2  2015-03-31   DNOW      10    100
5     Mike  Fund 1  2015-03-31    JNJ       1     10
6     Mike  Fund 1  2018-12-31    QSR       4     20
7     Mike  Fund 1  2018-12-31  LBTYA       3     12

In other words, the user should be able to input one or more investors, and/or one or more Funds in the same filter field, and the dashboard will update accordingly. So I think I need to change:

options=[{'label': i, 'value': i} for i in df['Investor'].unique()]

to something like groupby but am not positive? Here is my code:

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd

data = {'Investor': {0: 'Rick', 1: 'Faye', 2: 'Rick', 3: 'Dre', 4: 'Faye', 5: 'Mike', 6: 'Mike', 7: 'Mike'},
        'Fund': {0: 'Fund 3', 1: 'Fund 2', 2: 'Fund 3', 3: 'Fund 4', 4: 'Fund 2', 5: 'Fund 1', 6: 'Fund 1', 7: 'Fund 1'},
        'Period Date': {0: '2019-06-30', 1: '2015-03-31', 2: '2018-12-31', 3: '2020-06-30', 4: '2015-03-31', 5: '2015-03-31', 6: '2018-12-31', 7: '2018-12-31'},
        'Symbol': {0: 'AVLR', 1: 'MEG', 2: 'BAC', 3: 'PLOW', 4: 'DNOW', 5: 'JNJ', 6: 'QSR', 7: 'LBTYA'},
        'Shares': {0: 3, 1: 11, 2: 10, 3: 2, 4: 10, 5: 1, 6: 4, 7: 3},
        'Value': {0: 9, 1: 80, 2: 200, 3: 10, 4: 100, 5: 10, 6: 20, 7: 12}}
df = pd.DataFrame.from_dict(data)

def generate_table(dataframe, max_rows=100):
    return html.Table(
        # Header
        [html.Tr([html.Th(col) for col in dataframe.columns])] +

        # Body
        [html.Tr([
            html.Td(dataframe.iloc[i][col]) for col in dataframe.columns
        ]) for i in range(min(len(dataframe), max_rows))]
    )

app = dash.Dash()
app.layout = html.Div(
    children=[html.H4(children='Investor Portfolio'),
    dcc.Dropdown(
        id='dropdown',
        options=[{'label': i, 'value': i} for i in df['Investor'].unique()],
        multi=True, placeholder='Filter by Investor or Fund...'),
    html.Div(id='table-container')
])

@app.callback(dash.dependencies.Output('table-container', 'children'),
    [dash.dependencies.Input('dropdown', 'value')])

def display_table(dropdown_value):
    if dropdown_value is None:
        return generate_table(df)
    dff = df[df.Investor.str.contains('|'.join(dropdown_value))]
    dff = dff[['Investor', 'Period Date', 'Symbol','Shares', 'Value']]
    return generate_table(dff)

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

Upvotes: 2

Views: 4896

Answers (1)

Derek O
Derek O

Reputation: 19545

I am admittedly pretty new to Dash, but from what I can tell, you can achieve what you want by extending your options list, and then using an or condition in the dff that you are displaying in the Dash App to include the Fund column.

This is a bit brute force, and a nicer solution would be for Dash to know which columns your selected options are coming from. However, this would only be an issue if entries from different columns contained the same string (and here the unique values for Investor and Fund aren't ever the same).

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd

data = {'Investor': {0: 'Rick', 1: 'Faye', 2: 'Rick', 3: 'Dre', 4: 'Faye', 5: 'Mike', 6: 'Mike', 7: 'Mike'},
        'Fund': {0: 'Fund 3', 1: 'Fund 2', 2: 'Fund 3', 3: 'Fund 4', 4: 'Fund 2', 5: 'Fund 1', 6: 'Fund 1', 7: 'Fund 1'},
        'Period Date': {0: '2019-06-30', 1: '2015-03-31', 2: '2018-12-31', 3: '2020-06-30', 4: '2015-03-31', 5: '2015-03-31', 6: '2018-12-31', 7: '2018-12-31'},
        'Symbol': {0: 'AVLR', 1: 'MEG', 2: 'BAC', 3: 'PLOW', 4: 'DNOW', 5: 'JNJ', 6: 'QSR', 7: 'LBTYA'},
        'Shares': {0: 3, 1: 11, 2: 10, 3: 2, 4: 10, 5: 1, 6: 4, 7: 3},
        'Value': {0: 9, 1: 80, 2: 200, 3: 10, 4: 100, 5: 10, 6: 20, 7: 12}}
df = pd.DataFrame.from_dict(data)

def generate_table(dataframe, max_rows=100):
    return html.Table(
        # Header
        [html.Tr([html.Th(col) for col in dataframe.columns])] +

        # Body
        [html.Tr([
            html.Td(dataframe.iloc[i][col]) for col in dataframe.columns
        ]) for i in range(min(len(dataframe), max_rows))]
    )

app = dash.Dash()
app.layout = html.Div(
    children=[html.H4(children='Investor Portfolio'),
    dcc.Dropdown(
        id='dropdown',
        ## extend the options to consider unique Fund values as well
        options=[{'label': i, 'value': i} for i in df['Investor'].unique()] + [{'label': i, 'value': i} for i in df['Fund'].unique()],
        multi=True, placeholder='Filter by Investor or Fund...'),
    html.Div(id='table-container')
])

@app.callback(dash.dependencies.Output('table-container', 'children'),
    [dash.dependencies.Input('dropdown', 'value')])

def display_table(dropdown_value):
    if dropdown_value is None:
        return generate_table(df)

    ## add an 'or' condition for the other column you want to use to slice the df 
    ## and update the columns that are displayed
    dff = df[df.Investor.str.contains('|'.join(dropdown_value)) | df.Fund.str.contains('|'.join(dropdown_value))]
    dff = dff[['Investor', 'Fund', 'Period Date', 'Symbol','Shares', 'Value']]
    return generate_table(dff)

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

enter image description here

Upvotes: 3

Related Questions