From Static Reports to Living Tools: Building Data Products with Shiny and Quarto

There comes a point in every analyst’s journey when emailing PDF reports feels like sending messages in bottles—you put your hard work out there, but you rarely see how it’s used or what questions it raises. What if instead, you could build a living, breathing data product that lets your colleagues explore, question, and discover insights for themselves?

This is where R truly transforms from an analytical tool into an application platform. With Shiny and Quarto, you’re not just analyzing data—you’re building the interface through which your organization interacts with it.

Shiny: Building Interactive Web Apps in R’s Native Language

Imagine creating a web application where your marketing team can filter customer data, your executives can monitor KPIs, or your researchers can explore datasets—all without writing a single line of JavaScript or HTML. This is Shiny’s promise.

The Shiny Mindset: Reactivity

At its core, Shiny operates on a simple but powerful principle: when the user changes something (an input), the application automatically updates the relevant outputs. This “reactivity” means your app feels alive and responsive.

Let’s build a practical example: a customer segmentation tool for an e-commerce business.

r

library(shiny)

library(ggplot2)

library(dplyr)

# Define UI – the visual layout

ui <- fluidPage(

  titlePanel(“Customer Segmentation Explorer”),

  sidebarLayout(

    sidebarPanel(

      # Input controls

      selectInput(“country_filter”, “Select Country:”,

                  choices = c(“All”, “USA”, “UK”, “Germany”, “France”)),

      sliderInput(“age_range”, “Customer Age Range:”,

                  min = 18, max = 80, value = c(25, 55)),

      checkboxGroupInput(“loyalty_tiers”, “Loyalty Tiers:”,

                         choices = c(“Bronze”, “Silver”, “Gold”, “Platinum”),

                         selected = c(“Bronze”, “Silver”, “Gold”, “Platinum”))

    ),

    mainPanel(

      # Output displays

      plotOutput(“spending_plot”),

      dataTableOutput(“segment_table”)

    )

  )

)

# Define server logic

server <- function(input, output) {

  # Reactive data filtering – this recalculates when inputs change

  filtered_data <- reactive({

    data <- customer_segments

    if (input$country_filter != “All”) {

      data <- data %>% filter(country == input$country_filter)

    }

    data <- data %>%

      filter(age >= input$age_range[1] & age <= input$age_range[2],

             loyalty_tier %in% input$loyalty_tiers)

    return(data)

  })

  # Output 1: Interactive plot

  output$spending_plot <- renderPlot({

    ggplot(filtered_data(), aes(x = age, y = avg_order_value, color = loyalty_tier)) +

      geom_point(alpha = 0.7, size = 3) +

      geom_smooth(method = “lm”, se = FALSE) +

      labs(title = “Customer Spending Patterns”,

           x = “Age”, y = “Average Order Value ($)”) +

      scale_color_manual(values = c(“Bronze” = “#CD7F32”, “Silver” = “#C0C0C0”,

                                   “Gold” = “#FFD700”, “Platinum” = “#E5E4E2”))

  })

  # Output 2: Summary table

  output$segment_table <- renderDataTable({

    filtered_data() %>%

      group_by(loyalty_tier, country) %>%

      summarise(

        avg_spending = mean(avg_order_value),

        customer_count = n(),

        .groups = ‘drop’

      )

  })

}

# Run the application

shinyApp(ui = ui, server = server)

What we’ve built in just 60 lines of R code is a fully functional web application that would typically require multiple programming languages and frameworks. The marketing team can now answer questions like:

  • “Do Gold-tier customers in Germany spend more than those in France?”
  • “What’s the spending pattern of younger Platinum members?”
  • “How many Silver-tier customers are between 30-40 years old?”

Taking Shiny Further: Real-World Sophistication

Production Shiny apps can include:

  • User authentication and permissions
  • Email notifications when metrics hit thresholds
  • Integration with databases that update in real-time
  • Custom CSS styling for brand consistency
  • Export functionality for reports

r

# Advanced feature: Download handler for reports

output$download_report <- downloadHandler(

  filename = function() {

    paste(“customer-analysis-“, Sys.Date(), “.pdf”, sep = “”)

  },

  content = function(file) {

    # Generate a parameterized PDF report

    rmarkdown::render(“report_template.Rmd”,

                      output_file = file,

                      params = list(country = input$country_filter,

                                    age_range = input$age_range))

  }

)

Quarto: The Modern Document Engine

While Shiny excels at building interactive applications, Quarto represents the evolution of reproducible reporting. Think of it as R Markdown 2.0—a system for creating dynamic documents that can include interactive elements while remaining lightweight and portable.

The Quarto Advantage: One Source, Multiple Outputs

With a single Quarto document (.qmd file), you can generate:

  • Interactive HTML dashboards
  • Print-ready PDF reports
  • Microsoft PowerPoint presentations
  • Academic journal articles
  • Entire websites

Here’s what a Quarto dashboard for our customer analysis might look like:

yaml

title: “Q4 Customer Performance Dashboard”

format:

  html:

    theme: flatly

    toc: true

execute:

  echo: false

  warning: false

markdown

## Executive Summary

“`{r}

#| echo: false

library(plotly)

library(dplyr)

total_customers <- nrow(customer_segments)

avg_order_value <- mean(customer_segments$avg_order_value)

cat(“### Active Customers:”, total_customers, “\n”)

cat(“### Average Order Value: $”, round(avg_order_value, 2), “\n”)

“`

## Regional Performance

“`{r}

#| echo: false

ggplot(customer_segments, aes(x = country, y = avg_order_value, fill = country)) +

  geom_boxplot() +

  labs(title = “Spending Distribution by Country”,

       y = “Average Order Value ($)”) +

  theme_minimal()

“`

## Interactive Exploration

“`{r}

#| echo: false

library(DT)

datatable(customer_segments,

          filter = ‘top’,

          options = list(pageLength = 10,

                         autoWidth = TRUE,

                         dom = ‘Bfrtip’))

“`

## Notes and Methodology

This analysis includes all active customers from Q4 2024.

Data refreshed on `r Sys.Date()`.

Parameterized Reporting: Mass Customization

One of Quarto’s killer features is parameterized reports. You can create one template that generates hundreds of customized versions.

yaml

title: “Monthly Performance Report for `r params$region`”

params:

  region: “North”

  month: “2024-10-01”

Then, from R, you can generate reports for all regions automatically:

r

regions <- c(“North”, “South”, “East”, “West”)

for(region in regions) {

  quarto::quarto_render(

    “regional_report.qmd”,

    output_file = paste0(region, “_report.html”),

    execute_params = list(region = region, month = “2024-10-01”)

  )

}

Choosing Your Tool: Shiny vs. Quarto

So when should you use which? Think about the user’s needs:

Build a Shiny app when:

  • Users need real-time interaction with data
  • You’re building a decision-making tool
  • Multiple users need to explore different scenarios
  • You need user authentication and permissions
  • The data updates frequently

Create a Quarto dashboard when:

  • You’re producing regular reports (weekly, monthly)
  • You need multiple output formats (PDF, HTML, PPT)
  • The analysis is computation-heavy (render once, view many)
  • You want lightweight, easily shareable documents
  • You’re combining R, Python, and SQL in one document

The Hybrid Approach: Quarto + Shiny

Sometimes, the best solution combines both:

yaml

title: “Sales Performance Monitor”

format: html

markdown

## Monthly Summary (Static)

“`{r}

# Static summary calculated at render time

monthly_summary <- sales_data %>%

  group_by(month = floor_date(date, “month”)) %>%

  summarise(total_sales = sum(amount))

ggplot(monthly_summary, aes(x = month, y = total_sales)) +

  geom_col(fill = “steelblue”) +

  labs(title = “Monthly Sales Trend”)

“`

## Live Product Explorer (Interactive)

“`{r}

#| context: shiny

library(plotly)

selectInput(“product_category”, “Choose Category:”,

            choices = unique(sales_data$category))

renderPlotly({

  filtered <- sales_data %>%

    filter(category == input$product_category)

  plot_ly(filtered, x = ~date, y = ~amount, type = ‘scatter’, mode = ‘lines’)

})

“`

This hybrid gives you the best of both worlds: pre-computed summaries for quick loading, plus interactive elements for deeper exploration.

Deployment: Sharing Your Creation

Building these tools is only half the battle—you need to get them to your users.

For Shiny Apps:

  • ShinyApps.io: Simple cloud hosting (free tier available)
  • RStudio Connect: Enterprise platform with scheduling and security
  • Shiny Server: Self-hosted solution for your organization’s infrastructure

For Quarto Documents:

  • Quarto Pub: Free publishing platform for HTML documents
  • GitHub Pages: Perfect for public-facing dashboards
  • Company intranet: Simple file sharing for internal reports

Conclusion: From Analyst to Impact Multiplier

Learning Shiny and Quarto represents a fundamental shift in how you create value with data. You’re no longer just the person who has the answers—you’re building the tools that help everyone find the answers.

The progression looks like this:

  1. Beginner: You analyze data and send static reports
  2. Intermediate: You build Shiny apps for specific use cases
  3. Advanced: You create parameterized Quarto reports that generate themselves
  4. Expert: You maintain a suite of data products that your organization relies on daily

The most powerful outcome isn’t technical—it’s cultural. When you build these tools, you’re not just automating your own work; you’re enabling data-driven decision-making across your organization. The marketing team can check campaign performance without waiting for you. Executives can monitor KPIs in real-time. Researchers can explore data themselves.

In the modern data landscape, the most valuable analysts aren’t just those who can find insights—they’re the ones who can build the bridges that help everyone else reach those insights too. With Shiny and Quarto in your toolkit, you’re not just reading the data; you’re building the conversation around it.

Leave a Comment