Create dash-plotly stack bar chart that has interactive x axis items based on dropdown values

I am writing a dash plotly python code that draws a stack barchart for each county’s sanitation indicators like below.

However, I cannot seem to find the correct way to ensure the stacked bar chart increments the counties on the x axis based on the dropdown values ie. if I select 5 values in the dropdown, the stack barchart should show 5 stacked barcharts for each county (eg. Baringo, Kisumu, Mombasa, Nairobi, Nyeri). Likewise, if I narrow down the counties in the dropdown to just 2, the plotly graph should show just two stacked bars for each county (eg. Bomet, Kiambu).

All the files used on the code are available in this github repository, but the name of the python file is stack_barchart.py. The code in the aformentioned .py file is as below:

# Import the necessary packages
import pandas as pd
import plotly.express as px
from dash import Dash, dash_table, dcc, html, Input, Output

df = pd.read_csv('sanitation_long2.csv')
df2 = pd.read_csv('human_waste_filtered.csv')
df2 = df2[['County', 'Conventional Households', 'Main Sewer', 'Septic tank', 'Cess pool', 'VIP Latrine',
           'Pit latrine covered', 'Pit Latrine uncovered', 'Bucket latrine', 'Open/ Bush', 'Bio-septic tank/ Biodigester', 'Not Stated']]

# Initialize the app
app = Dash(__name__)
server = app.server

# App layout
app.layout = html.Div([
    html.H2(children="Pie chart showing human waste disposal as proportion of conventional households (Kenya 2019 census)"),

    # Create dropdown
    dcc.Dropdown([name for name in df.County], id='selected_county', multi=True),

    # Create stack bar graph/chart
    dcc.Graph(figure={}, id='controls-and-graph'),

    # Create filterable datatable
    dash_table.DataTable(columns= [
        {'name': i, 'id': i} for i in df2.columns
    ],
        data=df2.to_dict('records'),
        filter_action='native',
        sort_action='native',
        page_size=5),

    # Create download button
    html.Div([
        html.Button("Download CSV", id='btn-csv'),
        dcc.Download(id='download-dataframe-csv')
    ])
])

@app.callback(
    Output('controls-and-graph', 'figure'),
    Input('selected_county', 'value'),
)
def update_graph(dropdown_choices):
    dff = df[df.County == dropdown_choices]
    fig = px.bar(dff, x='County', y='value', color='indicator')
    return fig

# Function to show and download dataframe
@app.callback(
    Output('download-dataframe-csv', 'data'),
    Input('btn-csv', 'n-clicks'),
    prevent_initial_call=True
)
def func(n_clicks):
    return dcc.send_data_frame(df2.to_csv, 'mydf.csv')

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

I tried playing around with the def update_graph() code block values, including following the callback examples from here. However, they only made the problem more complex and incomprehensible. The persistent error that popped up in most tries was:

ValueError: ('Lengths must match to compare', (470,), (1,))

Googling the problem only brought one solution which referenced a medium article. However, I couldn’t seem to understand the logic of the solution.

Could someone help me have a stack bar chart plotly graph that upscales and downscales county name and their indicators based on the multi-value dropdown? For more info on dash plotly dropdowns, see this page.

Hi @sammigachuhi welcome to the forums.

The problem in your code is the following line:

I’m assuming that you want to filter your DataFrame which is not possible like this

You will need to check if any value of dropdown_choices is in your column County. You can do so using .isin(). An example:

import pandas as pd
import numpy as np
import string

rows = 100
cols = 5
names = list(string.ascii_uppercase[: cols])

dropdown_choices = [1]

df = pd.DataFrame(np.random.randint(0, 200, size=(rows, cols)), columns=names)

df['F'] = [i % 2 for i in range(rows)]

dff = df[df['F'].isin(dropdown_choices)]

Hello, not wishing to sound lazy but could you please make it simpler? The only part I could clearly understand is the last line of code which I implemented but of course no results.

def update_graph(dropdown_choices):
    dff = df[df.County.isin(dropdown_choices)]
    fig = px.bar(dff, x='County', y='value', color='indicator')
    return fig

Found the solution.

@app.callback(
    Output('controls-and-graph', 'figure'),
    Input('selected_county', 'value'),
)
def update_graph(dropdown_choices):
    if isinstance(dropdown_choices, list):
        dff = df[df.County.isin(dropdown_choices)]
        fig = px.bar(dff, x='County', y='value', color='indicator')
        return fig
    return go.Figure()

# Function to show and download dataframe
@app.callback(
    Output('download-dataframe-csv', 'data'),
    Input('btn-csv', 'n_clicks'),
    prevent_initial_call=True
)
def func(n_clicks):
    return dcc.send_data_frame(df2.to_csv, 'mydf.csv')

After looking at it with a golden eye, I also had to change n-clicks to n_clicks.