Shiny and Dash - The R and Python Champions

March 04, 2024  ·  4m read
Shiny Output

Continuing on from part 1 of our exploration of analytics dashboard tools, let’s dive into two of the most popular platforms: Shiny for R and Dash for Python. These tools have reshaped how data scientists create interactive, web-based visualisations. We’ll examine them through a simple, yet illustrative example: filtering and displaying data from the Iris dataset.

Shiny in Depth

Shiny, developed by RStudio, is an R package allowing the creation of interactive web applications directly from R. It’s known for its reactivity and ability to incorporate CSS themes, html widgets, and JavaScript actions.

Key Features:

  • Reactive Programming: Shiny operates on a reactive programming model, making the UI elements dynamic and responsive to user inputs.
  • Integration with R: Being an R package, it integrates smoothly with various R functions and libraries, making it a powerful tool for R users.

How to Create a Dashboard in Shiny:


Our example involves reading, filtering, and displaying the Iris dataset. The final product is a simple dashboard allowing users to select different species from the Iris dataset and view the filtered results in a table and a graph.
library(shiny)
library(shinydashboard)
library(datasets)
library(DT)
library(plotly)

data <- datasets::iris 

ui <- dashboardPage(
dashboardHeader(title = "Basic dashboard"),
dashboardSidebar(),
dashboardBody(
    fluidRow(
    selectInput(inputId="species", label="Species", choices = levels(data$Species),
                selected=levels(data$Species), multiple = TRUE),
    DT::dataTableOutput("table"), # https://shiny.rstudio.com/articles/datatables.html
    plotlyOutput("plot", width=1000)
    )
)
)

server <- function(input, output) {

user_data <- reactive({
    # replace below with any complicated operation ("model")
    data %>%
    filter(Species %in% input$species)
})

output$table <-DT::renderDataTable({
    user_data()
    })

output$plot <- renderPlotly({
    p<-plot_ly(data = user_data(), x = ~Sepal.Length, 
            y = ~Petal.Length, type = "scatter", mode="markers", color=~Species)
})

}

shinyApp(ui, server)
Shiny Output
Shiny Output

This code demonstrates the elegance of Shiny in producing a reactive, interactive data visualisation application.

Exploring Dash

Dash, built on top of Plotly.js, React, and Flask, allows Python users to create interactive web applications with ease. It’s known for its simplicity and effectiveness in tying together Python code with modern web elements.

How to Build a Dashboard with Dash:

The same Iris dataset example in Dash involves creating a web application where users can filter the dataset and view the results similarly.

from bokeh.sampledata.iris import flowers
import dash
import dash_table
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import pandas as pd
import plotly_express as px

data = flowers.copy()

# First define and use dash's more sleeky table UI output
def generate_dash_table(df):
    return dash_table.DataTable(
        id='table',
        columns=[{"name": i, "id": i} for i in df.columns],
        data=df.to_dict('records'),
        filtering=True,
        sorting=True,
        style_table={
            'maxHeight': '300px',
            'overflowY': 'scroll'
        },
    )


### This is where the dash app's code starts
app = dash.Dash(__name__)

app.layout = html.Div([
    dcc.Dropdown(
        id='demo-dropdown',
        options=[
            {'label': 'setosa', 'value': 'setosa'},
            {'label': 'versicolor', 'value': 'versicolor'},
            {'label': 'virginica', 'value': 'virginica'}
            ],
            value=['setosa', 'versicolor', 'virginica'],
            multi=True,
            style={'width': '400px'}
),
    html.Div(id='table-output'),
    dcc.Graph(id="graph", style={"width": "75%", "display": "inline-block"})
])

@app.callback(Output(component_id='table-output', component_property='children'),
        [Input(component_id='demo-dropdown', component_property='value')]
)
def update_info_table(input_value):
    # replace below with any complicated operation ("model")
    user_data = data[data.species.isin(input_value)]
    return generate_dash_table(user_data)

@app.callback(Output(component_id="graph", component_property="figure"), 
        [Input(component_id='demo-dropdown', component_property='value')]
    )
def make_figure(input_value):
    # replace below with any complicated operation ("model")
    user_data = data[data.species.isin(input_value)]
    return px.scatter(
        user_data,
        x="sepal_length",
        y="sepal_width",
        color="species",
        height=700,
    )

if __name__ == '__main__':
    app.run_server(host='0.0.0.0', port=8051, debug=True)

# Run with `python <app_filename>.py`
Shiny Output
Dash Output

This example highlights Dash’s capability in creating a highly interactive and visually appealing web application using Python.

In wrapping up our exploration of Shiny and Dash, it’s important to acknowledge a significant difference in their design philosophy, particularly in terms of reactivity and data handling.

Dash’s Limitation in Reactivity

While Dash excels in creating modern, visually appealing dashboard elements with a relatively straightforward coding approach, it falls short in one key area: reactivity, especially when compared to Shiny. In Dash, the same data processing operation often needs to be repeated for different components of the dashboard. For instance, in our Iris dataset example, the filter operation must be executed separately for each callback function, once for the table and again for the plot.

This issue is not just a minor inconvenience but a known limitation, as discussed in Plotly Dash GitHub Issues. The lack of native reactivity in Dash means that any operation, simple or complex, must be replicated across different components, leading to redundant code and potentially inefficient processing.

Workarounds and Their Drawbacks

Several workarounds have been proposed within the Dash community to address this issue. These include:

  • Storing Data in the User’s Browser Session: This involves using hidden Divs to store intermediate results. However, this approach can be less intuitive and may lead to cluttered code.

  • Saving Data on Disk or Database: While this method can centralise data processing, it introduces additional I/O operations and can become a bottleneck, especially for data-intensive applications.

  • Using Shared Memory Space (e.g., Redis): This is a more efficient approach but requires setting up and managing an additional data store, adding complexity to the deployment.

Shiny’s Superior Reactivity

In contrast, Shiny’s reactive programming model inherently handles such dependencies more gracefully. Shiny’s architecture allows for a more streamlined approach where a single reactive expression can update multiple components of the app. This feature makes Shiny particularly powerful and efficient, especially when dealing with computationally intensive operations, such as complex data manipulations or running machine learning models.

Final Thoughts

Both Shiny and Dash have their strengths and are excellent choices for building interactive dashboards in their respective ecosystems of R and Python. However, when it comes to reactivity and efficient data handling, Shiny currently holds an edge. Dash’s limitations in this area highlight the importance of carefully considering the specific requirements and complexities of your data visualisation project when selecting a tool.

Keep an eye out for part three where we will take a look at Bokeh, Panel, and Notebooks - Versatile Python Tools for Interactive Dashboards!