R
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Data Structures
-
Section 4: Data Manipulation
-
Section 5: Visualization and Statistics
-
Section 6: Advanced R
-
Section 7: Practical Projects
-
Section 8: Interview Practice
-
Section 9: More Practice Exercises
-
Section 10: Shiny Apps in Depth
-
Section 11: More Data Wrangling
-
Section 12: Tidyverse Deep Dive
-
Section 13: Statistical Modeling Deep Dive
-
Section 14: Machine Learning in R
-
Section 15: R Visualization Deep Dive
-
Section 16: R Package Development Deep Dive
-
Section 17: R for Reproducible Research
-
Section 18: R and Databases
-
Section 19: R Performance Optimization
-
Section 20: Bioinformatics and Specialized R
-
Section 21: More Shiny Practice
-
Section 22: More Practice Exercises
-
Section 23: R Data Structures Deep Dive
-
Section 24: More Interview and Review
-
Section 25: R for Business Analytics
-
Section 26: R Text Mining and NLP
-
Section 27: R Spatial Data Analysis
-
Section 28: R Deep Learning
-
Section 29: Advanced Statistical Techniques
-
Section 30: R Object Systems Deep Dive
-
Section 31: R Environments and Metaprogramming
-
Section 32: R for Finance
-
Section 33: R for Clinical and Health Data
-
Section 34: More Shiny Advanced Practice
-
Section 35: R Data Cleaning Deep Dive
-
Section 36: R Reporting Automation
-
Section 37: More Practical Projects Round 2
-
Section 38: R Ecosystem and Career
-
Section 39: More Visualization Practice
-
Section 40: R for Experimentation
-
Section 41: R for Genomics and Bioinformatics Deep Dive
-
Section 42: R for Marketing Analytics
-
Section 43: R Data Import/Export Deep Dive
-
Section 44: R String Processing Deep Dive
-
Section 45: R for Actuarial and Insurance Analytics
-
Section 46: R Testing and Quality Assurance Deep Dive
-
Section 47: R Version Control and Collaboration
-
Section 48: R Advanced Functional Programming
-
Section 49: R for Supply Chain and Operations
-
Section 50: More Practice Exercises Round 3
-
Section 51: R Dashboards and BI Integration
-
Section 52: R Data Governance and Ethics
-
Section 53: More Modeling Practice
-
Section 54: R Final Capstone Projects
-
Section 55: R for Sports Analytics
-
Section 56: More Interview Practice Round 2
-
Section 57: R Networking and APIs
-
Section 58: R for Environmental Science
-
Section 59: R Notebook and Documentation Practices
-
Section 60: More Data Wrangling Mastery
-
Section 61: R for A/B Testing at Scale
-
Section 62: R Package Ecosystem Deep Dive
147: Building a Customer Churn Dashboard
You've likely spent the last few lessons mastering ggplot2 and dplyr, but putting them into a live dashboard using Shiny is where things usually get messy. I see the same mistake constantly when engineers move from static scripts to interactive apps: they treat the server logic like a linear script.
Take a look at this snippet. I'm trying to build a simple churn dashboard where the user can filter the data by "Contract Type" to see how it affects the churn rate. It looks logically sound, right?
# BROKEN CODE
ui <- fluidPage(
selectInput("contract", "Choose Contract:", choices = c("Month-to-month", "One year", "Two year")),
plotOutput("churnPlot")
)
server <- function(input, output) {
# Filtering the data based on the input
filtered_data <- churn_df %>% filter(Contract == input$contract)
output$churnPlot <- renderPlot({
ggplot(filtered_data, aes(x = Tenure, fill = Churn)) +
geom_histogram()
})
}
shinyApp(ui, server)
The Dashboard that Refuses to Update
If you run this, you'll notice something frustrating: you can change the dropdown menu all you want, but the plot never changes. It's stuck on whatever the default value was when the app first launched.
Here is why: in Shiny, the server function only runs once when the user session starts. In the broken code above, filtered_data is created exactly once at startup. Because it's a standard R variable and not a "reactive" object, the renderPlot function has no reason to think it needs to re-run when input$contract changes. It just keeps plotting that original, static filtered_data dataframe.
Wrapping Logic in Reactive Expressions
To fix this, we need to tell Shiny that filtered_data depends on an input. We do this using reactive(). This creates a reactive expression—essentially a piece of code that "listens" for changes to any inputs inside it.
# FIXED CODE
server <- function(input, output) {
# Now this is a reactive expression
filtered_data <- reactive({
churn_df %>% filter(Contract == input$contract)
})
output$churnPlot <- renderPlot({
# Note: we now call filtered_data() as a function!
ggplot(filtered_data(), aes(x = Tenure, fill = Churn)) +
geom_histogram()
})
}
The fix is subtle but critical. By wrapping the filter in reactive(), Shiny creates a dependency graph. When input$contract changes, Shiny marks filtered_data as "invalid." Then, because renderPlot calls filtered_data(), it also becomes invalid and triggers a re-render. I cannot stress this enough: if you are transforming data based on a user input, it must be in a reactive block.
Structuring the Churn Layout
A real churn dashboard isn't just one plot; it's a story. You want to show the "What" (the current churn rate), the "Who" (the segments churning), and the "Why" (the correlations). For this, I recommend using shinydashboard instead of the basic fluidPage. It gives you that professional sidebar and header layout that stakeholders expect.
When building your layout, keep your "Value Boxes" at the top. A quick glance at the total churn percentage is more valuable than digging through a histogram. Use valueBox() for the headline metrics and tabBox() to separate your "Customer Demographics" from your "Usage Patterns." This keeps the UI clean and prevents the user from feeling overwhelmed by a wall of charts.
Optimizing Heavy Data Calculations
Churn datasets can get massive. If you find your dashboard lagging every time a user toggles a filter, check if you're doing expensive calculations (like a complex logistic regression or a heavy join) inside your renderPlot.
Never put heavy data processing directly inside the render function. If you do, that calculation runs every single time the plot needs to redraw, even if the underlying data hasn't changed. Move that logic into its own reactive() expression. That way, Shiny caches the result and only re-calculates it when the specific input it depends on actually changes.
📋 Practical Task
Build a Multi-Segment Churn Analysis Tool
You have been provided with a dataset called customer_churn_data. Your task is to expand the existing basic dashboard to include a "Service Filter."
- The Goal: Create a Shiny app that allows users to filter the churn visualization by both
Contract TypeandInternet Service(e.g., Fiber optic, DSL, No). - Requirement 1: Implement two
selectInputfilters in the UI. - Requirement 2: Create a single
reactive()expression that handles the filtering for both inputs simultaneously. - Requirement 3: Use a
ggplot2bar chart to display the count of churned vs. non-churned customers based on the filtered data. - Constraint: Ensure the plot updates instantly when either dropdown is changed. If the filtered dataset is empty, the app should not crash; instead, ensure your plot handles empty data gracefully.
There are no comments for now.