Building Interactive Tools in R: An Intro to Shiny Apps

Author

Joshua Lizardi

1 Introduction

Trine Logo

Data Analysis Graphic

This tutorial was developed for students at Trine University as part of a growing effort to equip learners with modern, real-world data skills. Whether you’re majoring in business, engineering, computer science, or health sciences, learning to use tools like R and Shiny can open doors to careers in data analysis, UX design, financial modeling, and more. This resource is designed to be approachable, hands-on, and immediately useful — no advanced programming background required.

By walking through the creation of a Retirement Contribution Analysis app, students gain exposure to real-world challenges in financial planning, data visualization, and interactive dashboard development. The goal is not just to teach R, but to show how it can be used to solve meaningful problems, simulate scenarios, and communicate insights clearly. This project can also be used as a portfolio piece or capstone example for students preparing for internships or entry into the data-driven workforce.

Explore the App Here

1.1 Case Study: Client Retirement Profiles

In this project, the goal is to model retirement scenarios for distinct clients, each with unique financial, demographic, and lifestyle characteristics. The purpose of this simulation is to analyze how different variables — such as contribution rates, healthcare costs, inflation, investment returns, and retirement age — impact long-term financial sustainability. This kind of scenario-based learning is widely supported in business analytics education (Evans, 2020).

Here are a few brief client examples:

  1. Client A: $250,000 in current savings, contributes 5% + 5%, modest salary growth, low health risk, moderate lifestyle.
  2. Client B: $100,000 saved, contributes 10% + 6%, high salary but delayed retirement at 70, high expenses.
  3. Client C: Self-employed with no employer match, contributes 15%, starts late with only $50,000 saved.
  4. Client D: Strong investor with a 6% return, contributes only 4%, lives frugally post-retirement.
  5. Client E: High-income earner ($200k+), but late saver. Aggressively invests and expects early retirement at 60.
  6. Client F: Minimal contributions but expects inheritance and low living costs.
  7. Client G: Focuses on Roth accounts, assumes tax-free withdrawals, low inflation environment.
  8. Client H: High healthcare costs, conservative investor, and long life expectancy due to healthy lifestyle.

The diversity in these profiles makes them perfect for teaching how small parameter changes can drastically affect long-term outcomes.


1.2 Backround

This tutorial assumes some basic familiarity with R, but no prior experience with Shiny is required. All code is provided, and each section builds incrementally toward a fully functioning app.

📌 Note: This project can serve as an example of a capstone or portfolio piece for students studying Data Science, UX Design, Financial Analytics, or Applied Statistics.

1.2.1 CC License Info


1.3 R & RStudio

R is freely distributed online and serves as the computational engine behind this app (R Core Team, 2024), it can be downloaded from: http://cran.r-project.org/. At the top of the page – under the heading “Download and Install R” – there are also download links for Windows users, Mac users, and Linux users. After you have installed R, download and install RStudio from: http://rstudio.org. RStudio provides a convenient interface for using R interactively under any platform and is also freely available.(Posit, 2024)

1.4 Introduction to Shiny

Shiny is an R package from RStudio that makes it easy to build interactive web applications (Chang et al., 2023). With Shiny, you can turn statistical analyses, data visualizations, and reports into dynamic, user-friendly dashboards—no HTML, CSS, or JavaScript required.

Shiny bridges the gap between traditional data analysis and real-world communication. Instead of sharing static charts or PDF reports, you can create applications that let users explore data, change inputs, and see results update instantly.

Whether you’re studying statistics, finance, public health, education, or business, Shiny equips you with tools to translate R skills into powerful, interactive tools used in industry and academia.(Chang, Cheng, Allaire, Xie, & McPherson, 2023)

1.5 What is Shiny?

Shiny is a powerful R package developed by Posit (formerly RStudio) that allows you to turn R code into interactive web applications — without needing to learn JavaScript, HTML, or CSS.

With just R and a bit of practice, you can create dashboards, data visualizations, simulations, and tools that anyone can use in their browser. It’s a perfect way to bridge data analysis and user experience, allowing people who don’t code to explore data insights dynamically.

1.6 Key Concepts in Shiny

Shiny apps have two main components:

1.6.1 1. UI (User Interface)

This is what the user sees — sliders, buttons, inputs, and output plots or tables. It’s created using layout functions like fluidPage() and sidebarLayout().

ui <- fluidPage(
  titlePanel("My Shiny App"),
  sidebarLayout(
    sidebarPanel(
      sliderInput("number", "Pick a number", 1, 100, 50)
    ),
    mainPanel(
      textOutput("result")
    )
  )
)
  1. Server The server contains the logic. It listens for changes in the UI and updates the output accordingly. This is where the math goes!
server <- function(input, output) {
  output$result <- renderText({
    paste("You selected:", input$number)
  })
}
  1. Reactive Programming Shiny uses reactive programming — a way of building apps that update automatically whenever the user changes something.

Key parts:

input$: Holds UI values

output$: Holds results that get shown

renderPlot(), renderText(): Create output

reactive(): Stores reactive values

observeEvent(): Runs code when a specific input changes (like a button click)

  1. Running the App To launch the app, wrap everything in shinyApp():
shinyApp(ui = ui, server = server)

🛠️ Summary Shiny helps you turn R projects into live web apps

It’s perfect for students, researchers, and analysts

You don’t need to learn HTML or JavaScript

You build apps using just R — through ui, server, and shinyApp()

This skill opens doors for internships, job interviews, data storytelling, and more

1.7 Why Use Shiny for This?

🗣️ Why Build an App When a simple R script will Work? Yes — it’s absolutely true that with a well-written R script, we can calculate and visualize retirement outcomes for each of the clients in one go. So why build an app? Because static code is rarely the end goal. Insight usually is. With a static script, you’re locked into one scenario per client. Any change — a retirement age, contribution rate, inflation assumption — means editing code, rerunning it, and re-generating plots. Building an app allows users to run scenarios instantly — a core part of data-driven decision-making (Evans, 2020).

The app lets users: Instantly update parameters See changes reflected in real time Compare different “what-if” scenarios without touching a line of code

Our target audience will always include professionals who don’t know R — and shouldn’t have to.

With the Shiny app:End Users just open a browser, They adjust sliders, click buttons, and get insights No R knowledge required — the interface becomes the communicator. Building an app allows users to run scenarios instantly — a core part of data-driven decision-making (Evans, 2020).

An app isn’t just a calculator — it’s a communication tool.It turns abstract financial models into interactive dashboards people can explore. The app tells a story. The script just does math. The base script solves one task — but the app grows with us.

We can (and did):Add PDF/CSV export Build a FIRE simulator Model taxes and healthcare Add voice narration and dark mode, This is no longer just a simple fanatical calculator — it’s a full retirement planning simulation platform.

Shiny apps transforms R code into insight, usability, and communication. It makes our work: Accessible, Interactive, Real-world ready. We’re not just coding for answers. We’re building tools for people.

LETS GET STARTED!

2 R: Installing packages and libraries

To install the R packages and libraries necessary to follow along with this text in RStudio, open a RStudio session and paste in the console

2.1 Step 1: Install Required Packages

install.packages(c(
  "shiny", "ggplot2", "plotly", "DT", "dplyr", "scales", 
  "gridExtra", "grid", "Cairo", "png"
), repos = "https://cloud.r-project.org")

2.2 Step 2: App File Structure

Suggested project layout:

📂 RetirementApp/
├── app.R                # Main Shiny application
└── retirement_app_guide.qmd  # This tutorial

2.3 Step 3: Creating the UI

Here’s how we structure the user interface using fluidPage in Shiny:

ui <- fluidPage(
  titlePanel("Retirement Contribution Analysis App"),
  sidebarLayout(
    sidebarPanel(
      numericInput("current_age", "Current Age", value = 37, min = 18, max = 100),
      numericInput("retirement_age", "Retirement Age", value = 65, min = 30, max = 100),
      numericInput("annual_salary", "Annual Salary ($)", value = 145000),
      sliderInput("employee_contribution_rate", "Employee Contribution Rate (%)", min = 0, max = 100, value = 6),
      sliderInput("employer_contribution_rate", "Employer Contribution Rate (%)", min = 0, max = 100, value = 6),
      sliderInput("return_rate_before_retirement", "Return Rate Before Retirement (%)", min = 0, max = 15, value = 4),
      sliderInput("return_rate_after_retirement", "Return Rate After Retirement (%)", min = 0, max = 15, value = 3),
      numericInput("current_savings", "Current Savings ($)", value = 259000),
      numericInput("post_retirement_expenses", "Post-Retirement Expenses ($)", value = 90000)
    ),
    mainPanel(
      plotlyOutput("plot1"),
      DTOutput("dataTable")
    )
  )
)

2.4 Step 4: Server Logic

Define your core computation and output rendering in the server function: Sample Calculations

server <- function(input, output) {
  results <- reactive({
    annual_savings <- input$annual_salary * ((input$employee_contribution_rate + input$employer_contribution_rate) / 100)
    future_value <- numeric()
    total <- input$current_savings
    for (year in input$current_age:(input$retirement_age - 1)) {
      total <- total * (1 + input$return_rate_before_retirement / 100) + annual_savings
      future_value <- c(future_value, total)
    }
    data.frame(Age = input$current_age:(input$retirement_age - 1), Balance = future_value)
  })

  output$plot1 <- renderPlotly({
    df <- results()
    ggplotly(ggplot(df, aes(x = Age, y = Balance)) +
               geom_line(color = "blue") +
               scale_y_continuous(labels = scales::dollar_format()) +
               theme_minimal())
  })

  output$dataTable <- renderDT({
    datatable(results(), options = list(pageLength = 10))
  })
}

2.5 Step 5: Running the App

At the bottom of your app.R file, run:

shinyApp(ui = ui, server = server)

Now your Retirement Calculator is live and ready for testing.

2.6 Step 5.5: Advanced Features Overview

Before diving into optional features, here’s what this app includes beyond the basics:

  • 🌗 Dark Mode toggle
  • 🎙️ Voice narration using browser speech synthesis
  • 🔥 FIRE (Financial Independence Retire Early) calculator
  • 📤 Export to PDF and CSV
  • 📊 Realistic tax bracket modeling
  • 💡 Life expectancy and healthcare cost estimators
  • 📈 Simulated investment growth under uncertainty

Each feature will be added incrementally in the steps that follow.

2.7 Step 6: Advanced Features Overview

This app includes several advanced features: - Dark Mode toggle with custom CSS - Speech synthesis for accessibility - FIRE (Financial Independence Retire Early) calculator - PDF and CSV export of results - Real tax bracket modeling - Life expectancy adjustments based on lifestyle factors

We’ll walk through how to add each of these next.

2.8 Step 6: Enable Dark Mode (Optional)

Add the following code in the tags$head() section:

tags$style(HTML("body.dark-mode { background-color: #121212; color: white; }"))

And enable toggle with:

actionButton("toggle_dark_mode", "🌙 Toggle Dark Mode")

2.9 Step 7: Add Voice Narration (Optional)

Insert this JavaScript inside a tags$script() block to add voice support:

var isMuted = false;
function readTextAloud() {
  if (isMuted) return;
  var text = document.getElementById("finalMessageText").innerText;
  var speech = new SpeechSynthesisUtterance();
  speech.text = text;
  window.speechSynthesis.speak(speech);
}

Trigger this using:

$(document).on('click', '#calculate', function() {
  setTimeout(readTextAloud, 1000);
});

💡 Tip:
To preview available voices in your browser, try running this in the browser console:

speechSynthesis.getVoices()

You can create a voice selector in the UI with selectInput() and assign it dynamically in JS if desired.

2.10 Step 8: Export to PDF

Use this code to render results into a PDF report:

pdf("summary.pdf")
grid.text("Summary of Results")
grid.table(head(results()))
dev.off()

2.11 Example Screen shot of App

Trine Logo

3 Appendix


3.1 Why Teach Shiny at Trine University?

This tutorial is designed for Trine University undergraduate and graduate students who are learning R and want to apply it to real-world problems. Whether you’re studying Data Analytics, Engineering, Business, or Education, Shiny empowers you to:

  • Build interactive tools for your projects or capstones
  • Communicate results through dynamic visualizations
  • Practice reactive programming, a skill relevant to many modern tech jobs
  • Create web-based apps that can be shared with supervisors, faculty, or employers
  • Prototype tools without needing web development experience

Learning Shiny helps bridge the gap between raw analysis and real-world impact

At Trine University, we believe students should learn skills that go beyond the classroom. Teaching Shiny empowers students to:

Apply real-world data science in a practical, engaging way.

Build interactive tools for research, capstone projects, and internships.

Demonstrate coding and communication skills in portfolios or job interviews.

Work with reproducible, open-source tools that scale from small projects to full applications.

By learning Shiny, students gain an edge—not just in R programming, but in the ability to tell data-driven stories that matter.


3.2 Why Use Shiny?

  • 🔧 Interactivity: Let users explore your data and models in real time
  • 📊 Visualization: Turn static plots into clickable, zoomable graphics
  • 🧩 Modularity: Build custom dashboards with user controls
  • 🌐 Accessibility: Share your work online, as a live app
  • 📈 Integration: Easily combine with packages like ggplot2, plotly, DT, and dplyr
#### Platform Notes & Environment Information
🧪 Tested Environment:
- R version ≥ 4.3.0
- RStudio ≥ 2023.09+
- Chrome or Edge browser (for voice synthesis compatibility)
⚙️ Functionality Notes:
- PDF export uses the Cairo package for improved font rendering.
- Voice narration uses browser-native JavaScript APIs (speechSynthesis).
- The app generates interactive plotly charts and downloadable reports.
- Some features (e.g., dark mode styling or narration) may not render the same in every browser.
🖥️ This tutorial and app were developed and tested using Windows 11 and RStudio (latest version).
- All features, including PDF export, speech synthesis, and dark mode, work smoothly on Windows. - For best results, run this project inside RStudio Desktop, not the R GUI or terminal. - The app should also run on macOS and Linux, but some features (like voice narration or Cairo PDF rendering) may behave differently depending on your system configuration.
> ✅ Tip: If you’re on Windows and get an error with PDF generation, make sure the Cairo package is installed and that your system has proper font support. You can also install Ghostscript for advanced PDF rendering if needed. ## Case Study: Eight Client Retirement Profiles
This Shiny App models and visualizes retirement savings growth and depletion based on a user’s input. It simulates pre- and post-retirement scenarios, FIRE planning, and investment comparisons. Tax calculations in the model are based on the IRS 2024 tax brackets (U.S. Internal Revenue Service, 2024). Also safe withdrawal rate principles, such as the 4% rule, are based on past market simulations (Bengen, 1994; Cooley et al., 1998).
Retirement age and planning factors reflect Social Security policies (U.S. Social Security Administration, 2024).
Healthcare and lifestyle costs use average spending data from the U.S. Bureau of Labor Statistics (U.S. Bureau of Labor Statistics, 2023).
Users can: - Estimate savings growth with salary increases and investment returns - Visualize when savings will run out post-retirement - Simulate FIRE scenarios - Compare 401(k) vs Roth IRA investments - Enable dark mode - Export data as PDF or CSV - Get spoken feedback via voice narration

3.2.1 🗣️ 3. UI

3.2.1.2 Sever

  • Total Contribution: Savings over time
  • Annual Savings: Yearly contributions
  • Post-Retirement Depletion: When savings run out
  • Comparison: Pre vs post-retirement side-by-side
  • Heatmap Analysis: Sensitivity on expenses and return rate
  • Savings vs Expenses: Line plot comparing savings vs costs
  • Contribution Breakdown: Pie chart of employer/employee contributions
  • Savings Longevity: Bar chart if savings last year-by-year
  • Life Expectancy vs Depletion: Life vs financial survival
  • Data Table: Combined raw data
  • FIRE Calculator: FIRE number + years to retirement
  • 401(k) vs Roth IRA: Simulated growth by account type

3.2.2 🗣️ 3. Speech Narration

3.2.2.1 results() Reactive Block

Calculates savings and depletion based on inputs:

3.2.2.1.1 Pre-Retirement
  • Computes salary with raise
  • Calculates annual savings
  • Compounds savings with return rate
3.2.2.1.2 Post-Retirement
  • Withdraws inflation-adjusted, tax-adjusted expenses
  • Accounts for healthcare and lifestyle
  • Applies U.S. tax brackets to simulate withdrawals

Output:

  • data: Pre-retirement details
  • post_retirement_data: Post-retirement data
  • combined_data: Full dataset
  • final_age: Depletion age
  • life_expectancy: Lifestyle-adjusted expectancy

3.2.3 🗣️ 3. Speech Narration

  • Adds narration using browser’s SpeechSynthesis API
  • Reads summary aloud when user clicks “Calculate”
  • Can mute/unmute voice
  • Replaces phrases to sound conversational
  • Customizes voice, pitch, and rate

3.2.4 🌙 4. Dark Mode

  • Adds custom CSS for dark styling
  • Button toggles dark-mode class
  • Affects all UI elements, buttons, and outputs

3.2.5 🖨️ 5. PDF Export

Uses Cairo, grid, and gridExtra to render a multipage PDF:

  • Page 1: Text summary with savings & depletion
  • Page 2: Top rows of data table
  • Page 3+: Embedded PNG plots

Text includes:

  • Projected retirement balance
  • Depletion age
  • Personalized recommendations

3.2.6 💾 6. CSV Export

Enables full download of combined data table as CSV.


3.2.7 🔥 7. FIRE Calculator

This module uses safe withdrawal rate principles, such as the 4% rule, based on past market simulations (Bengen, 1994; Cooley et al., 1998)

  • Calculates FIRE number: Expenses ÷ Withdrawal Rate
  • Calculates years to FIRE: FIRE ÷ Annual Savings
  • Displays growth plot and summary
  • Alerts if FIRE is realistic or not

3.2.8 📊 8. Investment Growth Comparison

Simulates account balances for:

  • 401(k)
  • Roth 401(k)
  • Roth IRA
  • Traditional IRA

Simulates market fluctuation using rnorm().
Considers 20% tax on pre-tax accounts.


3.2.9 ✅ 9. Final Summary Message

Dynamic message panel displays:

  • Projected savings
  • Depletion age
  • Life expectancy
  • Warnings if you outlive savings
  • “On track” or “Not on track” feedback
  • Detailed breakdown of each output tab
  • Smart recommendations based on inputs:
    • Delay retirement?
    • Save more?
    • Consider Roth conversion?
    • Reduce expenses?
    • Increase returns?
    • Avoid high-risk portfolios?

3.2.10 🛡️ 10. Resilience and Handling

  • Uses req() to prevent plot errors
  • Handles NA/null inputs
  • Uses observeEvent() for efficiency
  • Uses Sys.sleep() for smoother UX

3.2.11 ✅ Summary

This app provides a comprehensive, interactive retirement planning tool. It’s designed for realism and customization, including:

  • Lifecycle-based financial planning
  • Optional FIRE and tax modeling
  • Real-time feedback with narration
  • Exportable, user-friendly outputs

It’s ideal for educators, financial advisors, or anyone curious about retirement readinessChallenge: Modify the growth_projection() function to compare different contribution strategies (e.g., consistent vs. front-loaded savings).

3.2.12 Done!

You now have a fully functional Retirement Contribution Analysis App built in Shiny with optional features like dark mode, narration, and export tools. 🎉

Contact: joshualizardi@Gmail.com

3.2.13 Next Steps and Challenges

Here are a few ideas to extend your app:

  • 📈 Add Monte Carlo simulations for investment variability
  • 🧾 Generate customized PDF reports with rmarkdown::render()
  • 🧮 Add Social Security or pension calculators
  • 🔒 Create login-based access to save/load user profiles
  • 🇺🇸 Add state-specific tax brackets

3.2.13.1 💪 Challenge: Add Roth IRA Tax-Free Projections

Try modifying the growth_projection() function to simulate Roth IRA growth assuming: - No taxes on withdrawals - Different contribution limits

3.3 Full Code (+)

# ========================================================
# 🚀 Retirement Calculator PLUS 🔥
# Copyright (c) 2025 Joshua Lizardi & Joshua Maier
# All rights reserved.
#
# This software is provided "as is", without warranty of any kind, 
# express or implied. Redistribution or commercial use is prohibited 
# without explicit permission from the author.
#
# Created by Joshua Lizardi
# For licensing inquiries, contact: joshua.lizardi@email.com
# ========================================================

# Load required libraries
library(shiny)
library(ggplot2)
library(plotly)
library(DT)
library(dplyr)
library(scales)  # For currency formatting
library(ggplot2)
library(gridExtra)
library(grid)
library(Cairo)
library(png)


# --- Define UI ---
ui <- fluidPage(
  titlePanel("Retirement Contribution Analysis App"),
  
  tags$head(
    tags$style(HTML("
      body.dark-mode { background-color: #121212; color: white; }
      .dark-mode .well, .dark-mode .panel { background-color: #333 !important; color: white; }
      .dark-mode input, .dark-mode select, .dark-mode .btn { background-color: #555 !important; color: white !important; }
      .dark-mode .btn-primary { background-color: #007bff !important; }
      .dark-mode .tab-pane { background-color: #222 !important; }
      .dark-mode .shiny-output-error { color: red !important; } 
    ")),
    tags$script(HTML("
      $(document).on('click', '#toggle_dark_mode', function() {
        $('body').toggleClass('dark-mode');
      });
    "))
  ),
  
  actionButton("toggle_dark_mode", "🌙 Toggle Dark Mode", class = "btn btn-dark"),
  
  
  fluidRow(
    column(12, 
           div(style = "text-align: Left; margin-bottom: 15px;",
               actionButton("calculate", "🚀 Calculate", class = "btn btn-primary"),
               actionButton("mute_audio", "🔇 Mute Audio", class = "btn btn-secondary"),  # ✅ Mute Button
               tags$script(HTML("
  var isMuted = false;  
  var speechInstance = null;  

  function readTextAloud() {
    if (isMuted) return;

    var text = document.getElementById('finalMessageText').innerText;
    if (text.trim() === '') return;

    window.speechSynthesis.cancel();

    speechInstance = new SpeechSynthesisUtterance();
    
    // 🎙️ Dynamic Speech Enhancements
    var voices = window.speechSynthesis.getVoices();
    speechInstance.voice = voices.find(v => v.name.includes('Google UK English Male')) || voices[1];  
    speechInstance.rate = 0.95;  
    speechInstance.pitch = 1.1;  

    // 🎭 Add natural speech patterns
    text = text.replace('Summary of Results', 'Hello, My name is Ella, Im here to help, Alright, here is the breakdown.');
    text = text.replace('Projected savings at retirement:', 'So by the time you retire, you should have around');
    text = text.replace('Estimated depletion age:', 'But here is the catch, your savings might last until');
    text = text.replace('Final Recommendations:', 'Now, let me give you some advice.');
    
    // ✨ Add strategic pauses
    text = text.replace(/\\n/g, '... ');  

    speechInstance.text = text;

    if (!isMuted) {
      window.speechSynthesis.speak(speechInstance);
    }
  }

  // ✅ Trigger reading when 'Calculate' is clicked
  $(document).on('click', '#calculate', function() {
    setTimeout(readTextAloud, 2000);
  });

  // ✅ Toggle mute state when 'Mute Audio' button is clicked
  $(document).on('click', '#mute_audio', function() {
    isMuted = !isMuted;
    if (isMuted) {
      window.speechSynthesis.cancel();
    }
    var btnText = isMuted ? '🔊 Unmute Audio' : '🔇 Mute Audio';
    $('#mute_audio').text(btnText);
  });
"))
               
           )
    )
  )
  ,
  sidebarLayout(
    sidebarPanel(
      h3("Retirement Data"),
      tags$hr(),  # Adds a horizontal line
      downloadButton("downloadData", "Download Results"),
      numericInput("current_age", "Current Age", value = 37, min = 18, max = 100),
      numericInput("retirement_age", "Retirement Age", value = 65, min = 30, max = 100),
      numericInput("current_savings", "Current Savings ($)", value = 259000, min = 0),
      numericInput("annual_salary", "Annual Salary ($)", value = 145000, min = 0),
      tags$hr(),  # Separates sections
      sliderInput("salary_increase_rate", "Salary Increase Rate (%)", min = 0, max = 10, value = 2, step = 0.1),
      sliderInput("employee_contribution_rate", "Employee Contribution Rate (%)", min = 0, max = 100, value = 6),
      sliderInput("employer_contribution_rate", "Employer Contribution Rate (%)", min = 0, max = 100, value = 6),
      numericInput("pre_tax_contribution", "Pre-Tax Contribution ($)", value = 6000, min = 0),
      sliderInput("return_rate_before_retirement", "Return Rate Before Retirement (%)", min = 0, max = 15, value = 4),
      sliderInput("return_rate_after_retirement", "Return Rate After Retirement (%)", min = 0, max = 15, value = 3),
      numericInput("post_retirement_expenses", "Post-Retirement Expenses ($)", value = 90000, min = 0),
      sliderInput("post_retirement_tax_rate", "Post-Retirement Tax Rate (%)", min = 0, max = 50, value = 15),
      sliderInput("inflation_rate", "Inflation Rate (%)", min = 0, max = 10, value = 2),
      sliderInput("healthcare_cost", "Estimated Annual Healthcare Costs ($)", min = 2000, max = 20000, value = 5000, step = 500),
      sliderInput("health_risk_factor", "Health Risk Factor (0 = Low, 1 = High)", min = 0, max = 1, value = 0.5, step = 0.1),
      sliderInput("lifestyle_factor", "Lifestyle & Longevity Score (0 = Unhealthy, 10 = Very Healthy)", 
                  min = 0, max = 10, value = 5, step = 1)
    ),
    
    mainPanel(
      tabsetPanel(
        tabPanel("Total Contribution", plotlyOutput("plot1")),
        tabPanel("Annual Savings", plotlyOutput("plot2")),
        tabPanel("Post-Retirement Depletion", plotlyOutput("plot3")),
        tabPanel("Comparison", plotlyOutput("plot4")),
        tabPanel("Heatmap Analysis", plotlyOutput("heatmap")),
        tabPanel("Savings vs. Expenses", plotlyOutput("savings_vs_expenses")),
        tabPanel("Contribution Breakdown", plotlyOutput("contribution_pie")),
        tabPanel("Savings Longevity", plotlyOutput("savings_longevity")),
        tabPanel("Life Expectancy vs. Depletion", plotlyOutput("life_vs_depletion")),
        tabPanel("Data Table", 
                 downloadButton("downloadTableCSV", "📥 Download Data Table (CSV)"),
                 DTOutput("dataTable")),
        tabPanel("🔥 FIRE Calculator",  
                 fluidRow(
                   column(4,
                          div(
                            h3("FIRE Inputs"),
                            numericInput("fire_annual_expenses", "Annual Expenses ($)", value = 50000, min = 0),
                            sliderInput("fire_withdrawal_rate", "Withdrawal Rate (%)", min = 3, max = 5, value = 4, step = 0.1),
                            sliderInput("fire_savings_rate", "Savings Rate (%)", min = 0, max = 100, value = 30),
                            actionButton("calculate_fire", "🔥 Calculate FIRE", class = "btn btn-danger btn-lg w-100")
                          )
                   ),
                   column(8,
                          div(
                            h3("🔥 FIRE Number & Projections"),
                            verbatimTextOutput("fire_summary"),
                            plotOutput("fire_plot")
                          )
                   )
                 )
        ),
        tabPanel("401(k) vs Roth IRA Growth", 
                 actionButton("calculate_growth", "📈 Simulate Growth", class = "btn btn-primary"),
                 plotOutput("investmentPlot"),
                 verbatimTextOutput("summaryOutput"))
        
        
      ),
      div(id = "finalMessageText", uiOutput("final_message")),
      # ✅ Add this right below
      div(class = "footer", HTML("&copy; 2025 Joshua Lizardi & Joshua Maier"))
      
      
      
    )
  )
)


# --- Define Server Logic ---
server <- function(input, output) {
  
  results <- reactive({
    input$calculate  
    isolate({
      salary_increase_rate <- input$salary_increase_rate / 100
      employee_contribution_rate <- input$employee_contribution_rate / 100
      employer_contribution_rate <- input$employer_contribution_rate / 100
      return_rate_before_retirement <- input$return_rate_before_retirement / 100
      return_rate_after_retirement <- input$return_rate_after_retirement / 100
      post_retirement_tax_rate <- input$post_retirement_tax_rate / 100
      inflation_rate <- input$inflation_rate / 100
      # Base life expectancy (actuarial average)
      base_life_expectancy <- 85
      
      # Adjust based on lifestyle factor (every point adds or subtracts ~1.5 years)
      life_expectancy <- round(base_life_expectancy + ((input$lifestyle_factor - 5) * 1.5))
      
      # Ensure it doesn't go below 65 or above 110
      life_expectancy <- max(65, min(life_expectancy, 110))
      
      
      # --- Pre-Retirement Calculations ---
      age_seq <- seq(input$current_age, input$retirement_age)
      annual_savings_list <- numeric(length(age_seq))
      total_contribution_list <- numeric(length(age_seq))
      
      # Initialize starting contribution correctly
      total_contribution <- input$current_savings  
      
      for (i in seq_along(age_seq)) {
        n <- i  # Keeps the correct step count
        annual_savings_list[i] <- round((employee_contribution_rate * (input$annual_salary * ((1 + salary_increase_rate)^(n - 1)))) * 2)
        # First-year contribution remains as initial savings
        if (i == 1) {
          total_contribution_list[i] <- total_contribution  
        } else {
          # Correct formula: Apply growth to previous contribution first, then add new savings
          total_contribution <- total_contribution * (1 + return_rate_before_retirement) + annual_savings_list[i]
          total_contribution_list[i] <- round(total_contribution)
        }
      }
      
      # Create dataframe with correct values
      data <- tibble(Age = age_seq, Annual_Savings = annual_savings_list, Total_Contribution = total_contribution_list, Phase = "Pre-Retirement")
      
      # --- Post-Retirement Calculations ---
      total_contribution <- last(data$Total_Contribution)
      post_retirement_expenses <- (input$post_retirement_expenses * (1 + post_retirement_tax_rate)) + 
        (input$healthcare_cost * (1 + input$health_risk_factor))
      
      post_retirement_data <- tibble(Age = integer(), Total_Contribution = numeric(), Expenses = numeric(), Phase = character())  # ✅ Ensure "Expenses" column is included
      
      current_age <- input$retirement_age
      # Function to calculate tax using progressive brackets
      calculate_tax <- function(income, brackets) {
        tax_due <- 0
        for (i in 1:nrow(brackets)) {
          if (income > brackets$Upper[i]) {
            tax_due <- tax_due + (brackets$Upper[i] - brackets$Lower[i]) * brackets$Rate[i]
          } else {
            tax_due <- tax_due + (income - brackets$Lower[i]) * brackets$Rate[i]
            break
          }
        }
        return(tax_due)
      }
      
      # Define tax brackets for single filers
      tax_brackets <- data.frame(
        Lower = c(0, 11600, 47150, 100525, 191950, 243725, 609350),
        Upper = c(11600, 47150, 100525, 191950, 243725, 609350, Inf),
        Rate = c(0.10, 0.12, 0.22, 0.24, 0.32, 0.35, 0.37)
      )
      
      # Update post-retirement calculations
      post_retirement_data <- tibble(Age = integer(), Total_Contribution = numeric(), Expenses = numeric(), Phase = character())
      
      current_age <- input$retirement_age
      while (total_contribution > 0 && current_age <= 120) {
        post_retirement_expenses_inflated <- post_retirement_expenses * ((1 + inflation_rate)^(current_age - input$retirement_age))
        
        # Apply progressive tax to withdrawals
        tax_due <- calculate_tax(post_retirement_expenses_inflated, tax_brackets)
        post_retirement_expenses_after_tax <- post_retirement_expenses_inflated - tax_due
        
        total_contribution <- (total_contribution - post_retirement_expenses_after_tax) * (1 + return_rate_after_retirement)
        
        post_retirement_data <- add_row(
          post_retirement_data, 
          Age = current_age, 
          Total_Contribution = round(total_contribution), 
          Expenses = round(post_retirement_expenses_after_tax),  
          Phase = "Post-Retirement"
        )
        
        current_age <- current_age + 1
      }
      
      
      combined_data <- bind_rows(data, post_retirement_data)
      
      
      return(list(data = data, post_retirement_data = post_retirement_data, combined_data = combined_data, final_age = current_age,  life_expectancy = life_expectancy  # ✅ Add this!
))
    })
  })
  
  
  # ✅ Data Table
  # ✅ Updated Data Table to Include Post-Retirement Data
  output$dataTable <- renderDT({
    req(results()$combined_data)  # Ensure data is available
    
    # Create a combined table
    datatable(
      results()$combined_data, 
      options = list(pageLength = 15, scrollX = TRUE),  # Adds scrolling & pagination
      rownames = FALSE
    )
  })
  
  
  # ✅ Download Data
  output$downloadData <- downloadHandler(
    filename = function() {
      paste("Retirement_Analysis_Report_", Sys.Date(), ".pdf", sep = "")
    },
    content = function(file) {
      temp_dir <- tempdir()
      
      # Generate ggplot objects from the data
      plot1 <- ggplot(results()$data, aes(x = Age, y = Total_Contribution)) +
        geom_line(color = "blue") +
        ggtitle("Total Contribution Over Time") +
        theme_minimal()
      
      plot2 <- ggplot(results()$data, aes(x = Age, y = Annual_Savings)) +
        geom_col(fill = "red") +
        ggtitle("Annual Savings Breakdown") +
        theme_minimal()
      
      plot3 <- ggplot(results()$post_retirement_data, aes(x = Age, y = Total_Contribution)) +
        geom_line(color = "blue") +
        ggtitle("Post-Retirement Fund Depletion") +
        theme_minimal()
      
      # Save plots as images
      ggsave(filename = file.path(temp_dir, "plot1.png"), plot = plot1, width = 8, height = 5)
      ggsave(filename = file.path(temp_dir, "plot2.png"), plot = plot2, width = 8, height = 5)
      ggsave(filename = file.path(temp_dir, "plot3.png"), plot = plot3, width = 8, height = 5)
      
      # Create an empty vector to store recommendations
      recommendations <- c()
      
      # 1️⃣ Check if savings last long enough
      if (results()$final_age < 85) {
        recommendations <- c(recommendations, "🔺 Increase your savings rate or consider delaying retirement to extend your savings.")
      } else {
        recommendations <- c(recommendations, "✅ Your savings plan looks solid! Consider optimizing your investments for growth.")
      }
      
      # 2️⃣ Analyze contribution rate
      total_contribution_rate <- input$employee_contribution_rate + input$employer_contribution_rate
      if (total_contribution_rate < 10) {
        recommendations <- c(recommendations, "📌 Consider increasing your retirement contributions to at least 10-15% of your salary.")
      }
      
      # 3️⃣ Post-retirement expenses warning
      expense_ratio <- input$post_retirement_expenses / (last(results()$data$Total_Contribution) / (120 - input$retirement_age))
      if (expense_ratio > 0.07) {
        recommendations <- c(recommendations, "⚠️ Your post-retirement expenses are high relative to savings. Consider adjusting spending habits.")
      }
      
      # 4️⃣ Investment strategy
      if (input$return_rate_before_retirement < 5) {
        recommendations <- c(recommendations, "📈 Your return rate before retirement is low. Consider diversifying your investments for better growth.")
      }
      
      # Convert recommendations to formatted text
      final_recommendations <- paste(recommendations, collapse = "\n")
      
      # Final message with recommendations
      final_message_text <- paste(
        "Summary of Results\n",
        "Projected savings at retirement: $", formatC(last(results()$data$Total_Contribution), format = "f", big.mark = ",", digits = 2), "\n",
        "Estimated depletion age: ", results()$final_age, "\n\n",
        "Final Recommendations:\n", final_recommendations
      )
      
      
      
      # Get final message text
      final_message_text <- paste(
        "Summary of Results\n",
        "Projected savings at retirement: $", formatC(last(results()$data$Total_Contribution), format = "f", big.mark = ",", digits = 2), "\n",
        "Estimated depletion age: ", results()$final_age, "\n\n",
        "Final Recommendations:\n",
        ifelse(results()$final_age >= 90, "Your plan looks good!", "Consider increasing contributions!")
      )
      
      # Start PDF generation
      pdf(file, width = 8.5, height = 11, family = "sans", pointsize = 12)
      
      # Add summary text
      grid::grid.newpage()
      grid::grid.text(final_message_text, x = 0.1, y = 0.9, just = "left", gp = grid::gpar(fontsize = 12))
      
      # Add a new page for the table
      grid::grid.newpage()
      
      # Convert data table to matrix for proper rendering (limit to first 10 rows)
      table_data <- results()$data %>%
        head(100) %>%
        as.matrix()
      
      # Render the table in the PDF
      gridExtra::grid.table(table_data)
      
      # Add a new page for plots
      grid::grid.newpage()
      
      # Add plots to the PDF
      img_paths <- list.files(temp_dir, pattern = "plot.*\\.png", full.names = TRUE)
      for (img in img_paths) {
        grid::grid.newpage()
        img_raster <- png::readPNG(img)
        grid::grid.raster(img_raster)
      }
      
      # ✅ Ensure the PDF is closed properly
      dev.off()
      
      # ✅ Double-check that the PDF file is accessible before returning it
      Sys.sleep(1)  # Pause for 1 second to allow file system to release lock
    }
  )
  # ✅ Download Just the Data Table as CSV
  output$downloadTableCSV <- downloadHandler(
    filename = function() {
      paste("Retirement_Data_Table_", Sys.Date(), ".csv", sep = "")
    },
    content = function(file) {
      write.csv(results()$combined_data, file, row.names = FALSE)
    }
  )
  
  
  
  # ✅ Plots with formatted currency
  output$plot1 <- renderPlotly({ 
    req(results()$data) 
    ggplotly(ggplot(results()$data, aes(x = Age, y = Total_Contribution)) + 
               geom_line() + 
               scale_y_continuous(labels = dollar_format(prefix = "$")) + 
               theme_minimal()) 
  })
  
  output$plot2 <- renderPlotly({ 
    req(results()$data) 
    ggplotly(ggplot(results()$data, aes(x = Age, y = Annual_Savings)) + 
               geom_col(fill = "red") + 
               scale_y_continuous(labels = dollar_format(prefix = "$")) + 
               theme_minimal()) 
  })
  
  output$plot3 <- renderPlotly({ 
    req(results()$post_retirement_data) 
    ggplotly(ggplot(results()$post_retirement_data, aes(x = Age, y = Total_Contribution)) + 
               geom_line(color = "blue") + 
               scale_y_continuous(labels = dollar_format(prefix = "$")) + 
               theme_minimal()) 
  })
  output$plot4 <- renderPlotly({ 
    req(results()$combined_data)  # Ensure data exists before using
    
    # Create a local copy of the data before modifying
    combined_data <- results()$combined_data
    
    # Ensure factor order
    combined_data$Phase <- factor(combined_data$Phase, levels = c("Pre-Retirement", "Post-Retirement"))
    
    ggplotly(
      ggplot(combined_data, aes(x = Age, y = Total_Contribution)) + 
        geom_line(color = "blue") + 
        facet_wrap(~Phase, scales = "fixed") +  
        scale_y_continuous(labels = scales::dollar_format(prefix = "$")) +
        theme_minimal()
    ) 
  })
  
  output$heatmap <- renderPlotly({
    req(results())  # Ensure results() is available
    
    expenses_seq <- seq(90000, 150000, by = 15000)  # Optimized for performance
    returns_seq <- seq(0.02, 0.05, by = 0.005)
    
    # Ensure total_contribution is correctly retrieved once
    total_contribution_start <- last(results()$data$Total_Contribution)
    
    # Expand grid for combinations
    heatmap_data <- expand.grid(PostRetirementExpenses = expenses_seq, ReturnRate = returns_seq)
    
    # Compute depletion age for each row
    heatmap_data$DepletionAge <- mapply(function(exp, ret_rate) {
      total_contribution <- total_contribution_start
      age <- input$retirement_age
      
      while (total_contribution > 0 && age <= 120) {
        total_contribution <- (total_contribution - exp) * (1 + ret_rate)
        age <- age + 1
      }
      
      return(min(age, 120))  # Cap depletion age at 120
    }, heatmap_data$PostRetirementExpenses, heatmap_data$ReturnRate)
    
    # Plot heatmap
    ggplotly(
      ggplot(heatmap_data, aes(x = PostRetirementExpenses, y = ReturnRate, fill = DepletionAge)) + 
        geom_tile() + 
        scale_fill_gradient(low = "yellow", high = "red") + 
        theme_minimal() +
        labs(x = "Post-Retirement Expenses ($)", y = "Return Rate", fill = "Depletion Age")
    )
  })
  
  # ✅ Savings vs. Expenses Line Chart
  output$savings_vs_expenses <- renderPlotly({
    req(results()$post_retirement_data)
    
    ggplotly(
      ggplot(results()$post_retirement_data, aes(x = Age)) + 
        geom_line(aes(y = Total_Contribution, color = "Savings"), size = 1.2) + 
        geom_line(aes(y = Expenses, color = "Expenses"), size = 1.2, linetype = "dashed") +  # ✅ Ensure Expenses is referenced correctly
        labs(title = "Projected Savings vs. Retirement Expenses",
             x = "Age",
             y = "Amount ($)",
             color = "Legend") +
        scale_color_manual(values = c("Savings" = "blue", "Expenses" = "red")) +
        theme_minimal()
    )
  })
  
  # ✅ Contribution Breakdown Pie Chart
  output$contribution_pie <- renderPlotly({
    data_pie <- data.frame(
      Source = c("Employee Contribution", "Employer Contribution"),
      Amount = c(input$employee_contribution_rate, input$employer_contribution_rate)
    )
    
    plot_ly(data_pie, labels = ~Source, values = ~Amount, type = "pie",
            marker = list(colors = c("blue", "green")),
            textinfo = "label+percent",
            hoverinfo = "text") %>%
      layout(title = "Retirement Contribution Breakdown")
  })
  
  # ✅ Savings Longevity Bar Chart
  output$savings_longevity <- renderPlotly({
    age_seq <- seq(input$retirement_age, input$retirement_age + 30)
    depletion_points <- sapply(age_seq, function(x) {
      ifelse(x <= results()$final_age, "Savings Left", "Depleted")
    })
    
    data_longevity <- data.frame(Age = age_seq, Status = depletion_points)
    
    ggplotly(
      ggplot(data_longevity, aes(x = Age, fill = Status)) + 
        geom_bar() + 
        scale_fill_manual(values = c("Savings Left" = "blue", "Depleted" = "red")) +
        labs(title = "Savings Longevity After Retirement",
             x = "Age",
             y = "Savings Status",
             fill = "Legend") +
        theme_minimal()
    )
  })
  output$life_vs_depletion <- renderPlotly({
    req(results()$final_age, results()$life_expectancy)  # ✅ Correct
    
    # Create a dataframe for visualization
    df_life_vs_depletion <- data.frame(
      Category = c("Savings Depleted", "Estimated Life Expectancy"),
      Age = c(results()$final_age, results()$life_expectancy)  # ✅ Use results()
    )
    
    
    # Generate the bar chart
    ggplotly(
      ggplot(df_life_vs_depletion, aes(x = Category, y = Age, fill = Category)) +
        geom_bar(stat = "identity", width = 0.6) +
        scale_fill_manual(values = c("Savings Depleted" = "red", "Estimated Life Expectancy" = "blue")) +
        geom_text(aes(label = Age), vjust = -0.5, size = 5) +  # Add text labels
        labs(
          title = "Life Expectancy vs. Savings Depletion",
          x = "Category",
          y = "Age"
        ) +
        theme_minimal()
    )
  })
  
  
  # ✅ Display Final Age Message
  output$final_message <- renderUI({
    req(results()$final_age)
    final_age <- results()$final_age
    total_at_retirement <- last(results()$data$Total_Contribution)
    # ✅ Base life expectancy (actuarial average)
    base_life_expectancy <- 85
    
    # ✅ Adjust based on lifestyle factor (every point adds or subtracts ~1.5 years)
    life_expectancy <- round(base_life_expectancy + ((input$lifestyle_factor - 5) * 1.5))
    
    # ✅ Ensure it doesn't go below 65 or above 110
    life_expectancy <- max(65, min(life_expectancy, 110))
    # ✅ Check if savings deplete before life expectancy
    depletion_warning <- if (final_age < life_expectancy) {
      tags$p("⚠️ Warning: Your estimated life expectancy exceeds your savings depletion age! 
          You may run out of money before you pass away.", 
             style = "color: red; font-weight: bold;")
    } else {
      tags$p("✅ Your savings are projected to last through your expected lifespan.", 
             style = "color: green; font-weight: bold;")
    }
    
    # ✅ Dynamic "On Track" vs. "Not On Track" Message
    savings_status <- if (final_age < 85) {
      tags$p("⚠️ Your savings may not last long enough! Consider increasing contributions or adjusting expenses.", 
             style = "color: red; font-weight: bold;")
    } else {
      tags$p("✅ Your savings plan is on track.", 
             style = "color: green; font-weight: bold;")
    }
    # ✅ Function to compute investment growth with random fluctuations
    growth_projection <- function(initial, annual_contrib, rate, years) {
      # ✅ Ensure all values are properly initialized
      if (is.null(initial) || is.null(annual_contrib) || is.null(rate) || is.null(years) || is.na(years)) {
        return(rep(NA, 1))  # Return NA if any input is missing
      }
      
      years <- as.integer(years)  # ✅ Convert to integer to avoid length errors
      
      if (years <= 0) {
        return(rep(NA, 1))  # ✅ Handle invalid cases (e.g., negative or zero years)
      }
      
      balance <- numeric(years + 1)
      balance[1] <- initial
      
      for (i in 2:(years + 1)) {
        random_rate <- rnorm(1, mean = rate, sd = 2)  # Simulates market fluctuations
        balance[i] <- balance[i - 1] * (1 + random_rate / 100) + annual_contrib
      }
      
      return(balance)
    }
    observeEvent(input$calculate_fire, {
      # FIRE Number Calculation 
      showNotification("🔥 Calculating FIRE... Please wait!", type = "message", duration = 2)
      
      Sys.sleep(2)  # Simulating processing delay (you can remove this in real calculations)
      
      fire_number <- input$fire_annual_expenses / (input$fire_withdrawal_rate / 100)
      
      # Compute years to FIRE based on savings rate
      savings_rate <- input$fire_savings_rate / 100
      annual_savings <- input$annual_salary * savings_rate
      years_to_fire <- ifelse(annual_savings > 0, fire_number / annual_savings, Inf)
      
      # Generate a projection plot for savings growth
      fire_savings <- cumsum(rep(annual_savings, round(years_to_fire)))
      fire_years <- seq(1, length(fire_savings))
      
      # Display results
      output$fire_summary <- renderText({
        paste0(
          "🔥 FIRE Number: $", formatC(fire_number, format = "f", big.mark = ",", digits = 2), "\n",
          "💰 Estimated Years to FIRE: ", round(years_to_fire, 1), " years\n",
          ifelse(years_to_fire < 40, "✅ You can retire early! 🚀", "⚠️ You may need to save more to retire early.")
        )
      })
      
      output$fire_plot <- renderPlot({
        plot(fire_years, fire_savings, type = "l", col = "red", lwd = 2,
             xlab = "Years", ylab = "Total Savings ($)",
             main = "🔥 FIRE Savings Growth Over Time")
        abline(h = fire_number, col = "blue", lty = 2)
        legend("bottomright", legend = c("Savings Growth", "FIRE Goal"),
               col = c("red", "blue"), lty = c(1, 2), lwd = 2)
      })
    })
    
    # ✅ Simulating Investment Growth
    observeEvent(input$calculate_growth, {
      # Handle missing inputs with default values
      years <- ifelse(is.null(input$years) || is.na(input$years) || input$years <= 0, 30, input$years)
      rate <- ifelse(is.null(input$return_rate_before_retirement) || is.na(input$return_rate_before_retirement), 7, input$return_rate_before_retirement)
      contrib <- ifelse(is.null(input$pre_tax_contribution) || is.na(input$pre_tax_contribution), 6000, input$pre_tax_contribution)
      match_amt <- ifelse(is.null(input$employer_contribution_rate) || is.na(input$employer_contribution_rate), 5, input$employer_contribution_rate) / 100 * contrib
      
      # Ensure valid numbers before running calculations
      years <- max(1, as.integer(years))  # Minimum of 1 year
      rate <- as.numeric(rate)
      contrib <- as.numeric(contrib)
      match_amt <- as.numeric(match_amt)
      
      if (any(is.na(c(years, rate, contrib, match_amt)))) {
        return(NULL)  # Prevent errors if any values are still NA
      }
      
      # Run projections
      pre_tax_401k <- growth_projection(0, contrib + match_amt, rate, years)
      roth_401k <- growth_projection(0, contrib + match_amt, rate, years)
      roth_ira <- growth_projection(0, contrib, rate, years)
      traditional_ira <- growth_projection(0, contrib, rate, years)
      
      # Apply 20% tax on withdrawals for pre-tax accounts
      pre_tax_401k_after_tax <- pre_tax_401k * 0.8
      traditional_ira_after_tax <- traditional_ira * 0.8
      
      # Render the investment growth plot
      output$investmentPlot <- renderPlot({
        years_seq <- 0:years
        plot(years_seq, pre_tax_401k_after_tax, type = "l", col = "blue", lwd = 2, ylim = c(0, max(pre_tax_401k)),
             xlab = "Years", ylab = "Balance ($)", main = "Investment Growth Comparison")
        lines(years_seq, roth_401k, col = "red", lwd = 2)
        lines(years_seq, roth_ira, col = "green", lwd = 2)
        lines(years_seq, traditional_ira_after_tax, col = "purple", lwd = 2)
        legend("topleft", legend = c("401(k) (After Tax)", "Roth 401(k)", "Roth IRA", "Traditional IRA (After Tax)"),
               col = c("blue", "red", "green", "purple"), lty = 1, lwd = 2)
      })
      
      # Render the summary output
      output$summaryOutput <- renderText({
        paste(
          "Summary of Investment Growth:\n",
          "- 401(k) and Traditional IRA contributions are pre-tax, but withdrawals are taxed (assumed 20% tax).\n",
          "- Roth 401(k) and Roth IRA contributions are after-tax, so withdrawals are tax-free.\n",
          "- Employer matching (only in 401(k) plans) significantly boosts the final balance.\n",
          "- Random market fluctuations are included to reflect real-world investment conditions.\n",
          "- Over ", years, " years, the highest projected growth is in Roth accounts due to tax-free withdrawals."
        )
      })
    })
    
    
    
    # ✅ Create dynamic recommendations
    recommendations <- c()
    
    # 1️⃣ Check if retirement is too soon
    if ((input$retirement_age - input$current_age) < 15) {
      recommendations <- c(recommendations, "🕒 Consider delaying retirement to allow more time for savings growth.")
    }
    
    # 2️⃣ Check if savings are too low for retirement
    if (total_at_retirement < (input$post_retirement_expenses * 20)) {
      recommendations <- c(recommendations, "💰 Your projected savings may not be enough. Consider increasing your contributions or working longer.")
    }
    
    # 3️⃣ Annual salary vs savings
    savings_percentage <- (total_at_retirement / (input$annual_salary * (input$retirement_age - input$current_age))) * 100
    if (savings_percentage < 50) {
      recommendations <- c(recommendations, "📊 Your savings rate is low compared to your salary. Aim for 10-15% of your salary towards retirement.")
    }
    
    # 4️⃣ Salary increase rate effect
    if (input$salary_increase_rate < 2) {
      recommendations <- c(recommendations, "📈 Your salary growth is low. Consider investing in career development to increase earnings.")
    }
    
    # 5️⃣ Contribution rates check
    total_contribution_rate <- input$employee_contribution_rate + input$employer_contribution_rate
    if (total_contribution_rate < 10) {
      recommendations <- c(recommendations, "📌 Consider increasing your retirement contributions to at least 10-15% of your salary.")
    } else if (input$employer_contribution_rate < 5) {
      recommendations <- c(recommendations, "🏢 Your employer contribution is low. Check if you're maximizing the company match.")
    }
    
    # 6️⃣ Investment strategy check
    if (input$return_rate_before_retirement < 5) {
      recommendations <- c(recommendations, "📈 Your return rate before retirement is low. Consider diversifying your portfolio to improve growth.")
    }
    if (input$return_rate_after_retirement > 6) {
      recommendations <- c(recommendations, "⚠️ Your post-retirement return rate is high, which may mean your investments are too risky.")
    }
    
    # 7️⃣ Post-retirement expenses check
    expense_ratio <- input$post_retirement_expenses / (total_at_retirement / (120 - input$retirement_age))
    if (expense_ratio > 0.07) {
      recommendations <- c(recommendations, "⚠️ Your post-retirement expenses are high relative to your savings. Consider reducing discretionary spending.")
    }
    
    # 8️⃣ Inflation rate check
    if (input$inflation_rate > 3) {
      recommendations <- c(recommendations, "🌍 Inflation is high. Invest in inflation-protected assets like TIPS or diversified funds.")
    }
    
    # 9️⃣ Tax efficiency recommendation
    if (input$post_retirement_tax_rate > 20) {
      recommendations <- c(recommendations, "📝 Your tax rate is high. Consider Roth conversions or tax-efficient withdrawal strategies.")
    }
    
    # Convert recommendations to formatted text
    final_recommendations <- if (length(recommendations) > 0) {
      tags$ul(lapply(recommendations, tags$li))
    } else {
      tags$p("✅ Your retirement plan looks well-balanced.")
    }
    
    shortfall <- if (total_at_retirement <= 0) {
      tags$p("⚠️ Your savings will not be sufficient. Consider increasing contributions or adjusting expenses.", 
             style = "color: red; font-weight: bold;")
    } else {
      tags$p("✅ Your savings plan is on track.", 
             style = "color: green; font-weight: bold;")
    }
    
    # ✅ Render final UI with summary and recommendations
    tagList(
      tags$div(style = "border: 1px solid #ddd; padding: 15px; border-radius: 8px; background-color: #f9f9f9; margin-top: 15px;",
               tags$h3("📊 Summary of Results", style = "color: #2c3e50;"),
               tags$p(
                 tags$b("Projected savings at retirement: "), 
                 tags$span(style = "font-weight: bold; color: #007bff;", 
                           paste0("$", formatC(total_at_retirement, format = "f", big.mark = ",", digits = 2))),
                 tags$b(" | Estimated depletion age: "), 
                 tags$span(style = "font-weight: bold; color: #007bff;", final_age)
               ),
               tags$p(
                 tags$b("Estimated Life Expectancy: "), 
                 tags$span(style = "font-weight: bold; color: #007bff;", paste(life_expectancy, "years"))
               ),
               depletion_warning  # ✅ Show the warning here!
      ),

               
               tags$h4("📈 Explanation of Visuals", style = "color: #007bff; margin-top: 10px;"),
               tags$ul(
                 tags$li(tags$b("'Total Contribution'"), ": Tracks savings growth over time."),
                 tags$li(tags$b("'Annual Savings'"), ": Displays yearly contributions."),
                 tags$li(tags$b("'Post-Retirement Depletion'"), ": Predicts when savings run out."),
                 tags$li(tags$b("'Comparison'"), ": Breaks down savings before & after retirement."),
                 tags$li(tags$b("'Heatmap Analysis'"), ": Shows different savings scenarios."),
                 tags$li(tags$b("'Savings vs. Expenses'"), ": Compares your projected savings balance with post-retirement expenses to highlight when you might run out of money."),
                 tags$li(tags$b("'Contribution Breakdown'"), ": A pie chart showing the percentage of contributions from you vs. your employer."),
                 tags$li(tags$b("'Savings Longevity'"), ": A bar chart visualizing how long your savings are expected to last after retirement."),
                 tags$li(tags$b("'Life Expectancy vs. Depletion'"), ": A warning if your projected savings run out before your estimated lifespan."),
                 tags$li(tags$b("'Healthcare Cost Estimator (Coming Soon)'"), ": Will help factor in estimated medical costs."),
                 tags$li(tags$b("'Life Expectancy Calculator (Coming Soon)'"), ": Will refine predictions based on lifestyle and health factors."),
                 tags$h4("💰 Tax Considerations in Projections", style = "color: #2c3e50; margin-top: 10px;"),
                 tags$ul(
                   tags$li("📝 Withdrawals from pre-tax retirement accounts (e.g., 401(k), Traditional IRA) are taxed using real U.S. tax brackets."),
                   tags$li("📉 The model applies a progressive tax system to post-retirement withdrawals before deducting expenses."),
                   tags$li("📊 Higher withdrawal amounts could push you into a higher tax bracket, leading to increased tax liability."),
                   tags$li("⚖️ Lowering withdrawal rates, delaying retirement, or using Roth accounts could help minimize taxes."),
                   tags$li("📢 Consider working with a financial planner to optimize tax-efficient withdrawal strategies.")
                 ),
                 
               ),
               
               tags$h4("💡 Recommendations", style = "color: #2c3e50; margin-top: 10px;"),
               final_recommendations,
               savings_status  # ✅ Replaces the old shortfall check with a smarter one
               
      )
    
  })
  
  
  
}

# --- Run the Shiny App ---
shinyApp(ui = ui, server = server)

3.3.1 Optional: Deployment Tips

Once your app is complete, you can deploy it using:

  • Shinyapps.io After creating a free account at https://www.shinyapps.io/, to link your account with your RStudio application; In RStudio, open the Tools menu and select Global Options. Click on the Publishing tab on the left. Click on Connect on the right. Select shinyapps.io. Follow the instructions to input your token. After building a Shiny app, to publish it online, Open the app in RStudio. Click on the Publish icon () in the top-right of the Source pane, or the arrow next to it. Select files to be published: your app.R file and any other data files or images. Select your account. Provide a title for your app. Click Publish.

3.3.2 Be mindful of sensitive data if deploying publicly!

4 References

Bengen, W. P. (1994). Determining withdrawal rates using historical data. Journal of Financial Planning, 7(4), 171–180. https://www.onefpa.org/journal/Pages/Determining%20Withdrawal%20Rates%20Using%20Historical%20Data.aspx

Chang, W., Cheng, J., Allaire, J. J., Xie, Y., & McPherson, J. (2023). Shiny: Web application framework for R (R package version 1.8.0). https://CRAN.R-project.org/package=shiny

Cooley, P. L., Hubbard, C. M., & Walz, D. T. (1998). Retirement savings: Choosing a sustainable withdrawal rate. AAII Journal. https://www.aaii.com/journal/article/retirement-savings-choosing-a-sustainable-withdrawal-rate

Evans, J. R. (2020). Business analytics: Methods, models, and decisions (3rd ed.). Pearson.

Posit, PBC. (2024). RStudio: Integrated development environment for R. https://posit.co/download/rstudio-desktop/

R Core Team. (2024). R: A language and environment for statistical computing (Version 4.3.1). R Foundation for Statistical Computing. https://www.r-project.org/

U.S. Bureau of Labor Statistics. (2023). Consumer Expenditures Survey. https://www.bls.gov/cex/

U.S. Internal Revenue Service. (2024). Tax brackets and rates for 2024. https://www.irs.gov/newsroom/irs-provides-tax-inflation-adjustments-for-tax-year-2024

U.S. Social Security Administration. (2024). Retirement benefits. https://www.ssa.gov/benefits/retirement/

Wickham, H., & Grolemund, G. (2017). R for data science: Import, tidy, transform, visualize, and model data. O’Reilly Media. https://r4ds.hadley.nz/

5 About Me

My name is Joshua Lizardi. For the past 7 years, I have worked for various institutions teaching a wide range of courses in Math, Statistics and Technology. These included Quantitative Reasoning, Calculus ,Applied Technical Mathematics, Remedial Mathematics, Statistics, Computers & Office Automation, Introductory College Algebra, Intermediate College Algebra, Remedial Mathematics, Business Statistics.

I hold a bachelor’s in mathematics (Mercy College), a master’s in applied mathematics (Purdue University), and a master’s in data analytics (Western Governors University).  I also hold a few certifications including  “SAS Certified Statistical Business Analyst SAS 9”, “SAS Certified Base Programmer SAS 9”, “Oracle Database SQL Certified Associate”. 

Subjects like mathematics, statistics, and computer science should not be taught as if they were spectator sports, the best way to learn these subjects is to perform them. Although understanding textbooks and lecture notes is valuable, the learning that comes from one’s own attempts at solving problems is the key to becoming competent in the subject overall. I have always been passionate about mathematics statistics and computer science, and I enjoy encouraging students to see the utility of these subjects. 

SPECIALTIES

Applied Mathematics Applied Statistics Data Analytics Data Science Machine Learning Artificial Intelligence

SKILLS

R Python SQL SAS MiniTab Tableau Power BI Microsoft Office

https://www.youracclaim.com/users/joshua-lizardi

BOOM!