First and foremost, this report is intended to be a comprehensive record and guide for the development of the What-If Analysis Dashboard project. It is structured to guide readers through each stage of the project, including data preprocessing, model development, User Interface (UI) design, and the implementation of interactive features using R and the Shiny framework.
In general, What-If Analysis is a simulation method used in economics that allows users to explore potential outcomes of various scenarios by adjusting key indicators. This approach helps users understand how changes in one variable may influence others, predict possible outcomes under different assumptions, and support evidence-based decision-making in uncertain or dynamic environments.
As a practical application, the What-If Analysis Dashboard is an interactive web-based tool developed to simulate, compare, and analyze key economic indicators across Malaysian states. It leverages predictive modeling to generate real-time forecasts and scenario-based insights, allowing users to explore how changes in variables such as CPI, inflation, and GDP may affect broader economic outcomes. The dashboard may serve as a practical and dependable tool for researchers, policymakers, and analysts alike.
The dashboard is structured into three main tabs:
Scenario Simulation: Allows users to run simulations by adjusting key input values and observing predicted outputs instantly.
Scenario Comparison: Enables side-by-side comparisons of multiple user-defined scenarios across different states and economic indicators.
Analysis Results: Presents full data set, descriptive trends, correlation insights, predictive visuals and time series forecasts for more in-depth analysis.
This section outlines the data preparation process conducted prior to the development of the What-If Analysis Dashboard. It includes loading required libraries, importing and cleaning raw data sets, merging relevant data, constructing reference tables, fitting predictive models, and labeling variables for later use in the user interface and visualizations.
Each preprocessing step is critical to ensure data integrity, modeling accuracy, and seamless integration with the Shiny dashboard’s components.
We begin by loading all the necessary R packages required for the application. These include packages for Shiny UI, data wrangling, visualization, spatial analysis, forecasting, and interactivity.
# Core Shiny & UI components
library(shiny)
library(shinyWidgets)
# Visualization & Plotting
library(ggplot2)
library(ggthemes)
library(plotly)
# Data manipulation
library(dplyr)
library(tidyr)
library(purrr)
library(scales)
library(tibble)
# Time series handling
library(tsibble)
library(lubridate)
library(forecast)
# Interactive tables
library(DT)
library(sf)
# Mapping & Geospatial data sources
library(leaflet)
library(rnaturalearth)
library(rnaturalearthdata)
library(rnaturalearthhires)
# Color palettes
library(RColorBrewer)
All input CSV files are located in the data directory.
The code below loads each file into a list for batch processing and
inspection.
# Define folder path and list all CSV files in data frames
folder_path = "data"
file_list = list.files(path = folder_path, pattern = "*.csv", full.names = TRUE)
data_list = lapply(file_list, read.csv)
# List of data sets
file_list
## [1] "data/cpi_yearly_state_division.csv"
## [2] "data/gdp_yearly_state_sector.csv"
## [3] "data/inflation_yearly_state_division.csv"
## [4] "data/wages_yearly_state.csv"
## [5] "data/zReference_1.csv"
## [6] "data/zReference_2.csv"
## [7] "data/zReference_3.csv"
# The structure of each data sets
str(data_list)
## List of 7
## $ :'data.frame': 2142 obs. of 4 variables:
## ..$ year : int [1:2142] 2015 2016 2017 2018 2019 2020 2021 2022 2023 2015 ...
## ..$ state : chr [1:2142] "Malaysia" "Malaysia" "Malaysia" "Malaysia" ...
## ..$ division: chr [1:2142] "D00" "D00" "D00" "D00" ...
## ..$ index : num [1:2142] 113 115 120 121 121 ...
## $ :'data.frame': 1071 obs. of 5 variables:
## ..$ year : int [1:1071] 2015 2016 2017 2018 2019 2020 2021 2022 2023 2015 ...
## ..$ state : chr [1:1071] "Malaysia" "Malaysia" "Malaysia" "Malaysia" ...
## ..$ sector : chr [1:1071] "S0" "S0" "S0" "S0" ...
## ..$ gdp : num [1:1071] 1176941 1229312 1300769 1363766 1423952 ...
## ..$ gdp_capita: num [1:1071] 37739 38861 40620 42115 43783 ...
## $ :'data.frame': 2142 obs. of 4 variables:
## ..$ year : int [1:2142] 2015 2016 2017 2018 2019 2020 2021 2022 2023 2015 ...
## ..$ state : chr [1:2142] "Malaysia" "Malaysia" "Malaysia" "Malaysia" ...
## ..$ division : chr [1:2142] "D00" "D00" "D00" "D00" ...
## ..$ inflation: num [1:2142] 2.104 2.076 3.799 0.969 0.663 ...
## $ :'data.frame': 153 obs. of 4 variables:
## ..$ year : int [1:153] 2015 2016 2017 2018 2019 2020 2021 2022 2023 2015 ...
## ..$ state : chr [1:153] "Malaysia" "Malaysia" "Malaysia" "Malaysia" ...
## ..$ mean_wages : int [1:153] 2487 2657 2879 3087 3224 2954 3049 3219 3441 2549 ...
## ..$ median_wages: int [1:153] 1942 2000 2160 2308 2442 2076 2256 2429 2602 2200 ...
## $ :'data.frame': 153 obs. of 6 variables:
## ..$ year : int [1:153] 2015 2016 2017 2018 2019 2020 2021 2022 2023 2015 ...
## ..$ state : chr [1:153] "Malaysia" "Malaysia" "Malaysia" "Malaysia" ...
## ..$ index : num [1:153] 113 115 120 121 122 ...
## ..$ inflation : num [1:153] 2.1 2.1 3.8 1 0.7 -1.1 2.5 3.4 2.5 2.8 ...
## ..$ gdp : int [1:153] 1176900 1229300 1300800 1363800 1424000 1346200 1390900 1514100 1568000 110000 ...
## ..$ gdp_capita: int [1:153] 37700 38900 40600 42100 43800 41500 42700 46300 46900 29500 ...
## $ :'data.frame': 153 obs. of 6 variables:
## ..$ year : int [1:153] 2015 2016 2017 2018 2019 2020 2021 2022 2023 2015 ...
## ..$ state : chr [1:153] "Malaysia" "Malaysia" "Malaysia" "Malaysia" ...
## ..$ gdp : int [1:153] 1176900 1229300 1300800 1363800 1424000 1346200 1390900 1514100 1568000 110000 ...
## ..$ gdp_capita : int [1:153] 37700 38900 40600 42100 43800 41500 42700 46300 46900 29500 ...
## ..$ mean_wages : int [1:153] 2490 2660 2880 3090 3220 2950 3050 3220 3440 2550 ...
## ..$ median_wages: int [1:153] 1940 2000 2160 2310 2440 2080 2260 2430 2600 2200 ...
## $ :'data.frame': 153 obs. of 6 variables:
## ..$ year : int [1:153] 2015 2016 2017 2018 2019 2020 2021 2022 2023 2015 ...
## ..$ state : chr [1:153] "Malaysia" "Malaysia" "Malaysia" "Malaysia" ...
## ..$ index : num [1:153] 113 115 120 121 122 ...
## ..$ inflation : num [1:153] 2.1 2.1 3.8 1 0.7 -1.1 2.5 3.4 2.5 2.8 ...
## ..$ mean_wages : int [1:153] 2490 2660 2880 3090 3220 2950 3050 3220 3440 2550 ...
## ..$ median_wages: int [1:153] 1940 2000 2160 2310 2440 2080 2260 2430 2600 2200 ...
Each data set is cleaned by removing rows with missing values, converting character columns to factors, and standardizing state levels to ensure “Malaysia” always appears first.
# Remove missing values from each data set
data_list = lapply(data_list, function(df) {
df = na.omit(df)
df
})
# Convert character columns to factors
data_list = lapply(data_list, function(df) {
df[] = lapply(df, function(col) {
if (is.character(col)) factor(col)
else col})
df
})
# Reorder states: Malaysia appears first, followed by other states alphabetically
data_list = lapply(data_list, function(df) {
states = sort(setdiff(unique(as.character(df$state)), "Malaysia"))
df$state = factor(df$state, levels = c("Malaysia", states))
df
})
Relevant subsets are extracted from individual data sets (CPI,
Inflation, Wages, GDP) and merged into a single merged_df
data set for modeling and visualization.
# Extract CPI data
cpi_df = data_list[[1]] %>%
filter(division == "D00") %>%
select(year, state, cpi = index)
# Extract Inflation data
inflation_df = data_list[[3]] %>%
filter(division == "D00") %>%
select(year, state, inflation)
# Extract GDP and GDP per capita data
gdp_df = data_list[[2]] %>%
filter(sector == "S0") %>%
select(year, state, gdp, gdp_capita)
# Extract wages data
wages_df = data_list[[4]] %>%
select(year, state, mean_wages, median_wages)
# Merge all data by year and state
merged_df = reduce(list(wages_df, cpi_df, inflation_df, gdp_df),
left_join, by = c("year", "state"))
# The preview of the merged data set
head(merged_df)
## year state mean_wages median_wages cpi inflation gdp gdp_capita
## 1 2015 Malaysia 2487 1942 112.8083 2.1043898 1176941 37739
## 2 2016 Malaysia 2657 2000 115.1500 2.0757923 1229312 38861
## 3 2017 Malaysia 2879 2160 119.5250 3.7993921 1300769 40620
## 4 2018 Malaysia 3087 2308 120.6833 0.9691139 1363766 42115
## 5 2019 Malaysia 3224 2442 121.4833 0.6628919 1423952 43783
## 6 2020 Malaysia 2954 2076 120.1000 -1.1387022 1346249 41490
# The summary of the merged data set
summary(merged_df)
## year state mean_wages median_wages
## Min. :2015 Malaysia : 9 Min. :2024 Min. :1200
## 1st Qu.:2017 Johor : 9 1st Qu.:2507 1st Qu.:1757
## Median :2019 Kedah : 9 Median :2840 Median :2065
## Mean :2019 Kelantan : 9 Mean :2943 Mean :2179
## 3rd Qu.:2021 Melaka : 9 3rd Qu.:3218 3rd Qu.:2429
## Max. :2023 Negeri Sembilan: 9 Max. :4858 Max. :4443
## (Other) :99
## cpi inflation gdp gdp_capita
## Min. :109.3 Min. :-2.1167 Min. : 5353 Min. : 12075
## 1st Qu.:115.5 1st Qu.: 0.6629 1st Qu.: 39550 1st Qu.: 27268
## Median :119.4 Median : 1.9750 Median : 73776 Median : 41254
## Mean :119.8 Mean : 1.7023 Mean : 171096 Mean : 47597
## 3rd Qu.:123.2 3rd Qu.: 2.7833 3rd Qu.: 142352 3rd Qu.: 54725
## Max. :138.3 Max. : 7.2833 Max. :1567974 Max. :129472
##
Separate reference tables are created for use in the dashboard’s data tables.
# Reference table 1: CPI, inflation, and GDP
ref1_df = data_list[[5]] %>% select(year, state, cpi = index, inflation, gdp, gdp_capita)
# Reference table 2: GDP and wages
ref2_df = data_list[[6]] %>% select(year, state, gdp, gdp_capita, mean_wages, median_wages)
# Reference table 3: CPI, inflation and wages
ref3_df = data_list[[7]] %>% select(year, state, cpi = index, inflation, mean_wages, median_wages)
# The preview for each table
head(ref1_df)
## year state cpi inflation gdp gdp_capita
## 1 2015 Malaysia 112.8 2.1 1176900 37700
## 2 2016 Malaysia 115.2 2.1 1229300 38900
## 3 2017 Malaysia 119.5 3.8 1300800 40600
## 4 2018 Malaysia 120.7 1.0 1363800 42100
## 5 2019 Malaysia 121.5 0.7 1424000 43800
## 6 2020 Malaysia 120.1 -1.1 1346200 41500
head(ref2_df)
## year state gdp gdp_capita mean_wages median_wages
## 1 2015 Malaysia 1176900 37700 2490 1940
## 2 2016 Malaysia 1229300 38900 2660 2000
## 3 2017 Malaysia 1300800 40600 2880 2160
## 4 2018 Malaysia 1363800 42100 3090 2310
## 5 2019 Malaysia 1424000 43800 3220 2440
## 6 2020 Malaysia 1346200 41500 2950 2080
head(ref3_df)
## year state cpi inflation mean_wages median_wages
## 1 2015 Malaysia 112.8 2.1 2490 1940
## 2 2016 Malaysia 115.2 2.1 2660 2000
## 3 2017 Malaysia 119.5 3.8 2880 2160
## 4 2018 Malaysia 120.7 1.0 3090 2310
## 5 2019 Malaysia 121.5 0.7 3220 2440
## 6 2020 Malaysia 120.1 -1.1 2950 2080
Multiple Linear Regression models are trained using the merged data set to predict various economic indicators. These models are later used in the What-If Simulation feature of the dashboard.
# Wages prediction models
mean_model = lm(mean_wages ~ cpi + inflation + gdp + gdp_capita + state, data = merged_df)
median_model = lm(median_wages ~ cpi + inflation + gdp + gdp_capita + state, data = merged_df)
# CPI and Inflation prediction model
cpi_model = lm(cpi ~ gdp + gdp_capita + state, data = merged_df)
inf_model = lm(inflation ~ gdp + gdp_capita + state, data = merged_df)
# GDP and GDP per capita prediction model
gdp_model = lm(gdp ~ cpi + inflation + state, data = merged_df)
capita_model = lm(gdp_capita ~ cpi + inflation + state, data = merged_df)
A named list is created to store readable variable titles and y-axis labels. These labels are dynamically used in charts and visual outputs in the dashboard.
# Easy-to-read labels for charts and outputs
variable_labels = list(
mean_wages = list(title = "Mean Wages", y_title = "Wages (MYR)"),
median_wages = list(title = "Median Wages", y_title = "Wages (MYR)"),
gdp = list(title = "GDP", y_title = "GDP (MYR Million)"),
gdp_capita = list(title = "GDP per capita", y_title = "GDP per capita (MYR Million)"),
cpi = list(title = "CPI", y_title = "Consumer Price Index"),
inflation = list(title = "Inflation Rate", y_title = "Inflation Rate (%)")
)
The UI for this dashboard is constructed using the
dashboardPage() function from the
shinydashboard package, which combines the header, sidebar,
and body into a complete dashboard layout. This section outlines the
design and functionality of each component.
First and foremost, the overall dashboard page can be titled using
the title parameter,
# UI components structure
ui = dashboardPage(
title = "Economic Indicators What-If Analysis Dashboard",
dashboardHeader(...),
dashboardSidebar(...),
dashboardBody(...)
)
The dashboard header is defined using dashboardHeader()
function. It includes a logo linked to Media Smart Resources’ homepage
and a prominent dashboard title, styled with CSS for alignment and
spacing.
# Header with Media Smart Resources logo and custom title
dashboardHeader(
title = tags$div(
style = "display: flex; align-items: center; width: 100%;",
tags$a(href = "https://mediasmart.my/v2/main-page/", target = "_blank",
tags$img(src = "msrsb_logo.png", height = "50px",
style = "margin-left: -15px; margin-right: 15px;")),
tags$span("What-If Analysis Dashboard",
style = "font-size: 21px; font-weight: bold; flex-grow: 1;")),
titleWidth = 500
)
The main content is defined using the dashboardBody()
function. Within it, the tabItems() function manages three
individual tabItem() sections, each corresponding to a
sidebar menu item.
This tab allows users to choose a prediction mode, input parameters dynamically, and simulate results.
tabItem(tabName = "inputs",
fluidRow(
# Side panel: Prediction mode selection and inputs
column(width = 4,
box(title = "What-If Settings", width = 12,
status = "primary", solidHeader = TRUE,
radioButtons("predict_mode", "Prediction Mode",
choices = c("Salaries & Wages" = "p_wage",
"Price Indexes & Inflation" = "p_price",
"Gross Domestic Product" = "p_gdp"),
selected = "p_wage"),
uiOutput("dynamic_inputs"),
actionButton("run_sim", "Run Simulation", icon = icon("play")))
),
# Main panel: Prediction results and historical reference data
column(width = 8,
box(title = "What-If Predictions", width = 12,
status = "success", solidHeader = TRUE,
valueBoxOutput("predictionBox1", width = 12),
valueBoxOutput("predictionBox2", width = 12)),
box(title = "Reference Table", width = 12,
status = "info", solidHeader = TRUE,
DTOutput("ref_table1"))
)
)
)
This section enables the user to save and compare multiple scenarios under different economic hypotheticals.
tabItem(tabName = "scenario",
fluidRow(
# Side panel: Scenario input and saving
column(width = 4,
box(title = "Scenario Settings", width = 12,
status = "primary", solidHeader = TRUE,
radioButtons("scenario_mode", "Scenario Mode",
choices = c("Salaries & Wages" = "p_wage",
"Price Indexes & Inflation" = "p_price",
"Gross Domestic Product" = "p_gdp"),
selected = "p_wage"),
textInput("scenario_name", "Scenario Name"),
uiOutput("scenario_inputs"),
actionButton("save_scenario", "Save Scenario", icon = icon("save")),
br(), br(),
uiOutput("scenario_selector_ui"))
),
# Main panel: Comparison table and historical reference data
column(width = 8,
box(title = "Scenario Comparison Table", width = 12,
status = "success", solidHeader = TRUE,
DTOutput("comp_table")),
box(title = "Reference Table", width = 12,
status = "info", solidHeader = TRUE,
DTOutput("ref_table2"))
)
)
)
This comprehensive analysis results tab includes various subsections:
tabItem(tabName = "results",
fluidRow(
# Settings for variable, state, and year range
box(title = "Result Settings", width = 12,
status = "primary", solidHeader = TRUE,
fluidRow(
column(width = 6,
selectInput("selected_variable", "Select Variable",
choices = c("Mean Wages" = "mean_wages",
"Median Wages" = "median_wages",
"CPI" = "cpi",
"Inflation Rate" = "inflation",
"GDP" = "gdp",
"GDP per capita" = "gdp_capita"))),
column(width = 6,
selectInput("selected_state3", "Select State",
choices = unique(merged_df$state),
selected = "Malaysia"))),
fluidRow(
column(width = 12,
sliderInput("year_range", "Select Year Range",
min = 2015, max = 2023,
value = c(2015, 2023), step = 1, sep = "",
dragRange = TRUE))))
),
fluidRow(
# Full data table with data sources
box(title = "Full Data Table", width = 12,
status = "warning", solidHeader = TRUE,
DTOutput("table"),
tags$div(
style = "font-size: 12px; color: gray;",
HTML(
"Data Sources:<br>
<a href='https://open.dosm.gov.my/data-catalogue' target='_blank'
style='color: gray; text-decoration: underline;'>
Department of Statistics Malaysia (DOSM)</a>, 2015–2023<br>
<a href='https://www.ceicdata.com/en/malaysia/salaries--wages-survey-by-states'
target='_blank'
style='color: gray; text-decoration: underline;'>
Bank Negara Malaysia (BNM)</a>, 2015–2023")))
),
fluidRow(
# Trend analysis and correlation visualization
box(title = "Descriptive Analysis", width = 12,
status = "success", solidHeader = TRUE,
fluidRow(
column(width = 6, plotlyOutput("trend_plot")),
column(width = 6, plotlyOutput("correlation_heatmap"))))
),
fluidRow(
# Dynamic What-If analysis via bar and choropleth map
box(title = "What-If Analysis", width = 12,
status = "success", solidHeader = TRUE,
plotlyOutput("bar_plot", height = "510px"),
br(),
leafletOutput("map_plot"))
),
fluidRow(
# Forecast settings and time series plot
box(title = "Forecast Settings", width = 4,
status = "primary", solidHeader = TRUE,
selectInput("forecast_variable", "Select Variable",
choices = c("Mean Wages" = "mean_wages",
"Median Wages" = "median_wages",
"CPI" = "cpi",
"Inflation Rate" = "inflation",
"GDP" = "gdp",
"GDP per capita" = "gdp_capita")),
selectInput("forecast_state", "Select State",
choices = unique(merged_df$state), selected = "Malaysia"),
numericInput("forecast_horizon", "Forecast Horizon (Years)",
value = 5, min = 1, max = 30),
actionButton("run_forecast", "Run Forecast",
icon = icon("chart-line"))),
box(title = "Time Series Analysis", width = 8,
status = "success", solidHeader = TRUE,
plotlyOutput("forecast_plot", height = "470px"))
)
)
Custom CSS is added using tags$head() and
tags$style() to enhance the visual layout, improve user
experience, and maintain branding consistency. Key elements styled
include:
Fixed header and sidebar
Custom theme colors
Slider and dropdown component styling
Improved KPI box formatting
# CSS code for styling the layout of the dashboard
tags$head(
tags$style(HTML("
/* FIXED HEADER + LOGO */
.skin-blue .main-header .navbar {
background-color: #1A237E !important;
position: fixed;
top: 0;
height: 50px;
width: 100%;
z-index: 1030;
margin-bottom: 0;
}
.skin-blue .main-header .logo {
background-color: #1A237E !important;
color: white !important;
position: fixed;
padding: 10px 15px;
height: 50px;
z-index: 1040;
display: flex;
align-items: center;
justify-content: flex-start;
margin-bottom: 0;
}
.skin-blue .main-header .logo:hover {
background-color: #0D1B5F !important;
}
/* NAVBAR ITEMS */
.skin-blue .main-header .navbar .sidebar-toggle,
.skin-blue .main-header .navbar .navbar-custom-menu > .navbar-nav > li > a {
background-color: #1A237E !important;
color: white !important;
}
.skin-blue .main-header .navbar .sidebar-toggle:hover,
.skin-blue .main-header .navbar .navbar-custom-menu > .navbar-nav > li > a:hover {
background-color: #0D1B5F !important;
}
/* ACTIVE SIDEBAR MENU HIGHLIGHT */
.skin-blue .main-sidebar .sidebar-menu > li.active > a {
border-left: 3px solid #1565C0 !important;
}
/* SLIDER + DROPDOWN STYLES */
.irs-bar {
background-color: #1565C0 !important;
border-top: 1px solid #1565C0 !important;
border-bottom: 1px solid #1565C0 !important;
}
.irs-slider {
background: white !important;
border: 1px solid #1565C0 !important;
}
.irs-from, .irs-to, .irs-single {
background: #1565C0 !important;
color: white !important;
}
.selectize-dropdown .active {
background-color: #1565C0 !important;
color: white !important;
}
/* STICKY SIDEBAR */
.main-sidebar {
position: fixed;
top: 50px;
bottom: 0;
overflow-y: auto !important;
padding-top: 0;
margin-top: 0;
}
/* MAKE SIDEBAR CONTENT SCROLLABLE */
.main-sidebar .sidebar {
height: 100%;
overflow-y: auto !important;
padding-bottom: 240px;
}
/* CONTENT PADDING ADJUSTMENT */
.content-wrapper, .right-side {
margin-left: 230px;
padding-top: 50px !important;
margin-top: 0 !important;
}
/* TABLE COLUMN STYLE */
abbr[title] {
text-decoration: none;
border-bottom: none;
cursor: default;
}
/* KPIs BOXES ADJUSTMENT */
.small-box {
position: relative;
overflow: hidden;
}
.small-box .inner h3 {
font-size: 20px !important;
font-weight: bold !important;
margin: 0;
}
.small-box .inner p {
font-size: 16px !important;
font-weight: normal !important;
margin: 0;
}
.custom-icon {
font-size: 60px;
position: absolute;
top: 50%;
right: 22px;
transform: translateY(-112%);
}
.custom-icon2 {
font-size: 60px;
position: absolute;
top: 50%;
right: 15px;
transform: translateY(-112%);
}
"))
)
This section defines the backend functionality of the dashboard. All
server-side logic is encapsulated within the server
function, which dynamically responds to user inputs, performs necessary
calculations, and renders outputs such as prediction results, charts,
and tables.
Each subsection corresponds to a specific tab or component in the
dashboard interface. For clarity, the server function is
conceptually structured as shown below:
# The complete server logic
server = function(input, output) {
# Tab 1 Inputs: What-If Simulation
# Tab 2 Inputs: Scenario Comparison
# Tab 3 Inputs: Analysis Results
}
The following subsections defines and controls the input components and their behavior for the first tab of the dashboard. Based on user-specified inputs, the app dynamically renders the appropriate controls, displays model predictions with 95% confidence intervals, and presents a contextual reference table.
The inputs displayed here change based on the selected prediction mode. For example, if users choose to predict wages, they will be prompted to enter their own CPI, inflation rate, GDP, and GDP per capita values. If GDP or CPI-Inflation is selected instead, the relevant predictor inputs will appear accordingly.
# Render dynamic inputs for simulation
output$dynamic_inputs = renderUI({
# Common UI across all prediction modes: state selector
common_ui = list(
selectInput("selected_state1", "Select State", choices = unique(merged_df$state),
selected = "Malaysia")
)
# Inputs required to predict mean and median wages
wage_ui = list(
sliderInput("cpi", "Consumer Price Index (CPI)", min = 50, max = 200, value = 130.4, step = 0.1),
sliderInput("inflation", "Inflation Rate (%)", min = -15, max = 25, value = 2.5, step = 0.1),
numericInput("gdp", "GDP (MYR Million)", value = 1568000),
numericInput("gdp_capita", "GDP per capita (MYR Million)", value = 46900)
)
# Inputs required to predict GDP and GDP per capita
gdp_ui = list(
sliderInput("cpi", "Consumer Price Index (CPI)", min = 50, max = 200, value = 130.4, step = 0.1),
sliderInput("inflation", "Inflation Rate (%)", min = -15, max = 25, value = 2.5, step = 0.1),
numericInput("mean_wages", "Mean Wages (MYR)", value = 3440),
numericInput("median_wages", "Median Wages (MYR)", value = 2600)
)
# Inputs required to predict CPI and Inflation
price_ui = list(
numericInput("gdp", "GDP (MYR Million)", value = 1568000),
numericInput("gdp_capita", "GDP per capita (MYR Million)", value = 46900),
numericInput("mean_wages", "Mean Wages (MYR)", value = 3440),
numericInput("median_wages", "Median Wages (MYR)", value = 2600)
)
# Display the relevant inputs based on prediction mode
if (input$predict_mode == "p_wage") {
tagList(common_ui, wage_ui)
} else if (input$predict_mode == "p_gdp") {
tagList(common_ui, gdp_ui)
} else {
tagList(common_ui, price_ui)
}
})
This part of the code generates predicted values using the appropriate Multiple Linear Regression model based on user inputs. Once users click the Run Simulation button, the dashboard displays the predicted values, along with their 95% confidence intervals, in the value boxes.
# Reactive prediction triggered by simulation button
observeEvent(input$run_sim, {
# Convert selected state to factor to match model format
state_input = factor(input$selected_state1, levels = levels(merged_df$state))
if (input$predict_mode == "p_wage") {
# Prepare input data for wage prediction
df = data.frame(cpi = input$cpi, inflation = input$inflation,
gdp = input$gdp, gdp_capita = input$gdp_capita,
state = state_input)
# Predict mean and median wages
pred_mean = predict(mean_model, df, interval = "confidence", level = 0.95)
pred_median = predict(median_model, df, interval = "confidence", level = 0.95)
# Display predicted mean wage
output$predictionBox1 = renderValueBox({
valueBox(paste0("RM ", format(round(pred_mean[1], 2), big.mark = ",")),
HTML(paste0(
"Predicted Mean Wages in ", state_input,
"<br>(95% CI: RM ", format(round(pred_mean[2], 2), big.mark = ","),
" - RM ", format(round(pred_mean[3], 2), big.mark = ","), ")")),
icon = icon("dollar-sign", class = "custom-icon"),
color = "yellow")
})
# Display predicted median wage
output$predictionBox2 = renderValueBox({
valueBox(paste0("RM ", format(round(pred_median[1], 2), big.mark = ",")),
HTML(paste0(
"Predicted Median Wages in ", state_input,
"<br>(95% CI: RM ", format(round(pred_median[2], 2), big.mark = ","),
" - RM ", format(round(pred_median[3], 2), big.mark = ","), ")")),
icon = icon("dollar-sign", class = "custom-icon"),
color = "blue")
})
} else if (input$predict_mode == "p_gdp") {
# Prepare input data for GDP prediction
df = data.frame(cpi = input$cpi, inflation = input$inflation, state = state_input)
# Predict GDP and GDP per capita
pred_gdp = predict(gdp_model, df, interval = "confidence", level = 0.95)
pred_capita = predict(capita_model, df, interval = "confidence", level = 0.95)
# Display predicted GDP
output$predictionBox1 = renderValueBox({
valueBox(paste0("RM ", format(round(pred_gdp[1], 2), big.mark = ","), " Million"),
HTML(paste0(
"Predicted GDP in ", state_input,
"<br>(95% CI: RM ", format(round(pred_gdp[2], 2), big.mark = ","),
" Million - RM ", format(round(pred_gdp[3], 2), big.mark = ","), " Million)")),
icon = icon("landmark", class = "custom-icon"),
color = "yellow")
})
# Display predicted GDP per capita
output$predictionBox2 = renderValueBox({
valueBox(paste0("RM ", format(round(pred_capita[1], 2), big.mark = ","), " Million"),
HTML(paste0(
"Predicted GDP per capita in ", state_input,
"<br>(95% CI: RM ", format(round(pred_capita[2], 2), big.mark = ","),
" Million - RM ", format(round(pred_capita[3], 2), big.mark = ","), " Million)")),
icon = icon("users", class = "custom-icon2"),
color = "blue")
})
} else if (input$predict_mode == "p_price") {
# Prepare input data for CPI and inflation prediction
df = data.frame(gdp = input$gdp, gdp_capita = input$gdp_capita, state = state_input)
# Predict CPI and inflation
pred_cpi = predict(cpi_model, df, interval = "confidence", level = 0.95)
pred_inflation = predict(inf_model, df, interval = "confidence", level = 0.95)
# Display predicted CPI
output$predictionBox1 = renderValueBox({
valueBox(round(pred_cpi[1], 2),
HTML(paste0(
"Predicted CPI in ", state_input,
"<br>(95% CI: ", format(round(pred_cpi[2], 2), big.mark = ","),
" - ", format(round(pred_cpi[3], 2), big.mark = ","), ")")),
icon = icon("tags", class = "custom-icon"),
color = "yellow")
})
# Display predicted inflation
output$predictionBox2 = renderValueBox({
valueBox(paste0(round(pred_inflation[1], 2), "%"),
HTML(paste0(
"Predicted Inflation Rate in ", state_input,
"<br>(95% CI: ", format(round(pred_inflation[2], 2), big.mark = ","),
"% - ", format(round(pred_inflation[3], 2), big.mark = ","), "%)")),
icon = icon("line-chart", class = "custom-icon"),
color = "blue")
})
}
})
The following code renders a reference data table that displays historical records based on the selected prediction mode and state, serving as a guide for first-time users. The table’s content adapts accordingly, providing extra context for the predicted values.
# Render a reference data table based on selected state and prediction mode
output$ref_table1 = renderDT({
req(input$predict_mode, input$selected_state1)
# Select data and columns based on prediction mode
rt1 = if (input$predict_mode == "p_wage") {
ref1_df %>%
filter(state == input$selected_state1) %>%
select(
"Year" = year, "State" = state,
"CPI" = cpi, "Inflation" = inflation,
"GDP" = gdp, "GDP/capita" = gdp_capita)
} else if (input$predict_mode == "p_gdp") {
ref3_df %>%
filter(state == input$selected_state1) %>%
select(
"Year" = year, "State" = state,
"CPI" = cpi, "Inflation" = inflation,
"Mean Wages" = mean_wages, "Median Wages" = median_wages)
} else {
ref2_df %>%
filter(state == input$selected_state1) %>%
select(
"Year" = year, "State" = state,
"GDP" = gdp, "GDP/capita" = gdp_capita,
"Mean Wages" = mean_wages, "Median Wages" = median_wages)
}
# Tooltip labels using HTML <abbr> for additional info on hover
tooltips = switch(input$predict_mode,
"p_wage" = c(
as.character(tags$abbr(title = "Year", "Year")),
as.character(tags$abbr(title = "State Name", "State")),
as.character(tags$abbr(title = "Consumer Price Index", "CPI")),
as.character(tags$abbr(title = "Inflation Rate (%)", "Inflation")),
as.character(tags$abbr(title = "Gross Domestic Product (MYR Million)", "GDP")),
as.character(tags$abbr(title = "Gross Domestic Product per capita (MYR Million)",
"GDP/capita"))
),
"p_gdp" = c(
as.character(tags$abbr(title = "Year", "Year")),
as.character(tags$abbr(title = "State Name", "State")),
as.character(tags$abbr(title = "Consumer Price Index", "CPI")),
as.character(tags$abbr(title = "Inflation Rate (%)", "Inflation")),
as.character(tags$abbr(title = "Mean Wages (MYR)", "Mean Wages")),
as.character(tags$abbr(title = "Median Wages (MYR)", "Median Wages"))
),
c(
as.character(tags$abbr(title = "Year", "Year")),
as.character(tags$abbr(title = "State Name", "State")),
as.character(tags$abbr(title = "Gross Domestic Product (MYR Million)", "GDP")),
as.character(tags$abbr(title = "Gross Domestic Product per capita (MYR Million)",
"GDP/capita")),
as.character(tags$abbr(title = "Mean Wages (MYR)", "Mean Wages")),
as.character(tags$abbr(title = "Median Wages (MYR)", "Median Wages"))
)
)
# Render table with appropriate column headers and horizontal scrolling
datatable(rt1, options = list(scrollX = TRUE), escape = FALSE, colnames = tooltips)
})
Next, these subsections manage the user inputs, scenario creation, and comparison table outputs for the second tab of the dashboard. Users can define and save multiple hypothetical economic scenarios by adjusting key indicators and view predicted outcomes side by side.
Once again, the inputs displayed here change based on the selected scenario mode. It allows users to input their own key indicators values, select a state, and save the scenario for later comparison.
# Components needed to create a dynamic scenario inputs
# Reactive storage to store multiple user-defined scenarios
scenarios = reactiveValues(data = list())
# Render dynamic inputs for scenario comparison
output$scenario_inputs = renderUI({
# Common UI across all scenario modes: state selector
common_ui = list(
selectInput("selected_state2", "Select State", choices = unique(merged_df$state),
selected = "Malaysia")
)
# Inputs required to predict mean and median wages
wage_ui = list(
sliderInput("cpi", "Consumer Price Index (CPI)", min = 50, max = 200, value = 130.4, step = 0.1),
sliderInput("inflation", "Inflation Rate (%)", min = -15, max = 25, value = 2.5, step = 0.1),
numericInput("gdp", "GDP (MYR Million)", value = 1568000),
numericInput("gdp_capita", "GDP per capita (MYR Million)", value = 46900)
)
# Inputs required to predict GDP and GDP per capita
gdp_ui = list(
sliderInput("cpi", "Consumer Price Index (CPI)", min = 50, max = 200, value = 130.4, step = 0.1),
sliderInput("inflation", "Inflation Rate (%)", min = -15, max = 25, value = 2.5, step = 0.1),
numericInput("mean_wages", "Mean Wages (MYR)", value = 3440),
numericInput("median_wages", "Median Wages (MYR)", value = 2600)
)
# Inputs required to predict CPI and Inflation
price_ui = list(
numericInput("gdp", "GDP (MYR Million)", value = 1568000),
numericInput("gdp_capita", "GDP per capita (MYR Million)", value = 46900),
numericInput("mean_wages", "Mean Wages (MYR)", value = 3440),
numericInput("median_wages", "Median Wages (MYR)", value = 2600)
)
# Display the relevant inputs based on scenario mode
if (input$scenario_mode == "p_wage") {
tagList(common_ui, wage_ui)
} else if (input$scenario_mode == "p_gdp") {
tagList(common_ui, gdp_ui)
} else {
tagList(common_ui, price_ui)
}
})
# Save user-defined scenario and run prediction based on input values
observeEvent(input$save_scenario, {
req(input$scenario_name)
scenario = list(state = input$selected_state2)
selected_mode = input$scenario_mode
state_input = factor(input$selected_state2, levels = levels(merged_df$state))
if (selected_mode == "p_wage") {
scenario$cpi = input$cpi
scenario$inflation = input$inflation
scenario$gdp = input$gdp
scenario$gdp_capita = input$gdp_capita
df = data.frame(cpi = input$cpi, inflation = input$inflation,
gdp = input$gdp, gdp_capita = input$gdp_capita,
state = state_input)
scenario$pred_mean = round(predict(mean_model, df), 2)
scenario$pred_median = round(predict(median_model, df), 2)
} else if (selected_mode == "p_price") {
scenario$gdp = input$gdp
scenario$gdp_capita = input$gdp_capita
scenario$mean_wages = input$mean_wages
scenario$median_wages = input$median_wages
df = data.frame(gdp = input$gdp, gdp_capita = input$gdp_capita, state = state_input)
scenario$pred_cpi = round(predict(cpi_model, df), 2)
scenario$pred_inflation = round(predict(inf_model, df), 2)
} else if (selected_mode == "p_gdp") {
scenario$cpi = input$cpi
scenario$inflation = input$inflation
scenario$mean_wages = input$mean_wages
scenario$median_wages = input$median_wages
df = data.frame(cpi = input$cpi, inflation = input$inflation, state = state_input)
scenario$pred_gdp = round(predict(gdp_model, df), 2)
scenario$pred_capita = round(predict(capita_model, df), 2)
}
# Save scenario under user-defined name
scenarios$data[[input$scenario_name]] = scenario
# Notify user of successful save
showNotification(paste("Scenario", input$scenario_name, "successfully saved"), type = "message")
})
# Allow user to select one or more saved scenarios for comparison
output$scenario_selector_ui = renderUI({
if (length(scenarios$data) > 0) {
selectInput("selected_scenarios", "Select Scenarios to Compare",
choices = names(scenarios$data), multiple = TRUE)}
})
This part of the code renders a table comparing key inputs and predicted outputs across user-defined scenarios. The table adapts based on the active scenario mode and includes formatted tooltips for better interpretation.
# Render the Scenario Comparison Table
output$comp_table = renderDT({
req(input$selected_scenarios)
selected = input$selected_scenarios
if (length(selected) == 0) return(NULL)
# Combine selected scenarios into one data frame
scenario_df = do.call(rbind, lapply(scenarios$data[selected], as.data.frame))
scenario_df = data.frame(Scenario = selected, scenario_df,
check.names = FALSE, row.names = NULL)
# Mapping of column codes to display names
col_names = c(
Scenario = "Scenario",
state = "State",
cpi = "CPI",
inflation = "Inflation",
gdp = "GDP",
gdp_capita = "GDP/capita",
mean_wages = "Mean Wages",
median_wages = "Median Wages",
pred_mean = "Pred Mean Wages",
pred_median = "Pred Median Wages",
pred_cpi = "Pred CPI",
pred_inflation = "Pred Inflation",
pred_gdp = "Pred GDP",
pred_capita = "Pred GDP/capita")
scenario_df = scenario_df %>%
mutate(across(any_of(c("gdp", "gdp_capita", "mean_wages", "median_wages",
"pred_mean", "pred_median", "pred_gdp", "pred_capita")),
~ scales::comma(.x, accuracy = 0.01))) %>%
select(any_of(names(col_names))) %>%
rename_with(~ col_names[.x], .cols = everything())
# Dynamically assign column tooltips based on scenario mode
tooltips = switch(input$scenario_mode,
"p_wage" = c(
as.character(tags$abbr(title = "Scenario Name", "Scenario")),
as.character(tags$abbr(title = "State Name", "State")),
as.character(tags$abbr(title = "Consumer Price Index", "CPI")),
as.character(tags$abbr(title = "Inflation Rate (%)", "Inflation")),
as.character(tags$abbr(title = "Gross Domestic Product (MYR Million)", "GDP")),
as.character(tags$abbr(title = "Gross Domestic Product per capita (MYR Million)",
"GDP/capita")),
as.character(tags$abbr(title = "Predicted Mean Wages (MYR)",
"Pred Mean Wages")),
as.character(tags$abbr(title = "Predicted Median Wages (MYR)",
"Pred Median Wages"))
),
"p_gdp" = c(
as.character(tags$abbr(title = "Scenario Name", "Scenario")),
as.character(tags$abbr(title = "State Name", "State")),
as.character(tags$abbr(title = "Consumer Price Index", "CPI")),
as.character(tags$abbr(title = "Inflation Rate (%)", "Inflation")),
as.character(tags$abbr(title = "Mean Wages (MYR)", "Mean Wages")),
as.character(tags$abbr(title = "Median Wages (MYR)", "Median Wages")),
as.character(tags$abbr(title = "Predicted Gross Domestic Product (MYR Million)",
"Pred GDP")),
as.character(tags$abbr(title = "Predicted Gross Domestic Product
per capita (MYR Million)",
"Pred GDP/capita"))
),
c(
as.character(tags$abbr(title = "Scenario Name", "Scenario")),
as.character(tags$abbr(title = "State Name", "State")),
as.character(tags$abbr(title = "Gross Domestic Product (MYR Million)", "GDP")),
as.character(tags$abbr(title = "Gross Domestic Product per capita (MYR Million)",
"GDP/capita")),
as.character(tags$abbr(title = "Mean Wages (MYR)", "Mean Wages")),
as.character(tags$abbr(title = "Median Wages (MYR)", "Median Wages")),
as.character(tags$abbr(title = "Predicted Consumer Price Index", "Pred CPI")),
as.character(tags$abbr(title = "Predicted Inflation Rate (%)", "Pred Inflation"))
)
)
# Render comparison table with highlighted predicted output columns
datatable(scenario_df, options = list(pageLength = 6, scrollX = TRUE),
escape = FALSE, colnames = tooltips) %>%
formatStyle(columns = tail(colnames(scenario_df), 2),
backgroundColor = "#FFF3CD", fontWeight = "bold")
})
The following code renders a reference data table that displays historical records based on the selected scenario mode and state, serving as a guide for first-time users. The table’s content adapts accordingly, providing extra context for the hypotheticals scenarios.
# Render the reference table based on selected state and scenario mode
output$ref_table2 = renderDT({
req(input$scenario_mode, input$selected_state2)
# Select data and columns based on scenario mode
rt2 = if (input$scenario_mode == "p_wage") {
ref1_df %>%
filter(state == input$selected_state2) %>%
select(
"Year" = year, "State" = state,
"CPI" = cpi, "Inflation" = inflation,
"GDP" = gdp, "GDP/capita" = gdp_capita)
} else if (input$scenario_mode == "p_gdp") {
ref3_df %>%
filter(state == input$selected_state2) %>%
select(
"Year" = year, "State" = state,
"CPI" = cpi, "Inflation" = inflation,
"Mean Wages" = mean_wages, "Median Wages" = median_wages)
} else {
ref2_df %>%
filter(state == input$selected_state2) %>%
select(
"Year" = year, "State" = state,
"GDP" = gdp, "GDP/capita" = gdp_capita,
"Mean Wages" = mean_wages, "Median Wages" = median_wages)
}
# Tooltip labels using HTML <abbr> for additional info on hover
tooltips = switch(input$scenario_mode,
"p_wage" = c(
as.character(tags$abbr(title = "Year", "Year")),
as.character(tags$abbr(title = "State Name", "State")),
as.character(tags$abbr(title = "Consumer Price Index", "CPI")),
as.character(tags$abbr(title = "Inflation Rate (%)", "Inflation")),
as.character(tags$abbr(title = "Gross Domestic Product (MYR Million)", "GDP")),
as.character(tags$abbr(title = "Gross Domestic Product per capita (MYR Million)",
"GDP/capita"))
),
"p_gdp" = c(
as.character(tags$abbr(title = "Year", "Year")),
as.character(tags$abbr(title = "State Name", "State")),
as.character(tags$abbr(title = "Consumer Price Index", "CPI")),
as.character(tags$abbr(title = "Inflation Rate (%)", "Inflation")),
as.character(tags$abbr(title = "Mean Wages (MYR)", "Mean Wages")),
as.character(tags$abbr(title = "Median Wages (MYR)", "Median Wages"))
),
c(
as.character(tags$abbr(title = "Year", "Year")),
as.character(tags$abbr(title = "State Name", "State")),
as.character(tags$abbr(title = "Gross Domestic Product (MYR Million)", "GDP")),
as.character(tags$abbr(title = "Gross Domestic Product per capita (MYR Million)",
"GDP/capita")),
as.character(tags$abbr(title = "Mean Wages (MYR)", "Mean Wages")),
as.character(tags$abbr(title = "Median Wages (MYR)", "Median Wages"))
)
)
# Render table with appropriate column headers and horizontal scrolling
datatable(rt2, options = list(scrollX = TRUE), escape = FALSE, colnames = tooltips)
})
Finally, these concluding subsections provide a comprehensive overview of the results from several analyses included in the dashboard. The results are presented through various visualizations, such as line charts, bar charts, heatmaps, and tables for easy interpretation.
The full data table displays the merged data set containing CPI, inflation rate, GDP, GDP per capita, and wage indicators (mean and median) by state and year. It serves as a reference for users to explore the raw values before or after running any simulations.
# Render a comprehensive merged data table for all states and years
output$table = renderDT({
# Select and rename columns for clarity and readability
merged_df = merged_df %>%
select(
"Year" = year,
"State" = state,
"Mean Wages" = mean_wages,
"Median Wages" = median_wages,
"CPI" = cpi,
"Inflation Rate" = inflation,
"GDP" = gdp,
"GDP per capita" = gdp_capita)
# Tooltip labels using HTML <abbr> for additional info on hover
tooltips = c(
as.character(tags$abbr(title = "Year", "Year")),
as.character(tags$abbr(title = "State Name", "State")),
as.character(tags$abbr(title = "Mean Wages (MYR)", "Mean Wages")),
as.character(tags$abbr(title = "Median Wages (MYR)", "Median Wages")),
as.character(tags$abbr(title = "Consumer Price Index", "CPI")),
as.character(tags$abbr(title = "Inflation Rate (%)", "Inflation Rate")),
as.character(tags$abbr(title = "Gross Domestic Product (MYR Million)", "GDP")),
as.character(tags$abbr(title = "Gross Domestic Product per capita (MYR Million)",
"GDP per capita"))
)
# Display the interactive table with horizontal scroll and hover labels
datatable(merged_df, options = list(pageLength = 9, scrollX = TRUE),
escape = FALSE, colnames = tooltips)
})
This section offers exploratory insights through dynamic visualization and filtering. Users can interactively select a time range and state to observe historical trends and relationships between economic indicators. These tools provide intuitive understanding of underlying data patterns before diving into predictions or simulations.
# Reactive filter: filter data set based on selected year range using slider
filtered_data = reactive({
req(input$year_range)
merged_df %>%
filter(year >= input$year_range[1], year <= input$year_range[2])
})
The line chart below dynamically visualizes the trend of a selected economic indicator over time for a chosen state. The shaded area and interactive points help highlight patterns and outliers across the filtered years.
# Render line chart over time for selected state and indicator
output$trend_plot = renderPlotly({
req(input$selected_state3, input$selected_variable)
# Filter data based on selected state and year range
trend_data = filtered_data() %>%
filter(state == input$selected_state3)
selected_var = input$selected_variable
min_y = min(trend_data[[selected_var]], na.rm = TRUE)
max_y = max(trend_data[[selected_var]], na.rm = TRUE)
# Dynamic plot title based on selected state and indicator
title = variable_labels[[selected_var]]$title
y_title = variable_labels[[selected_var]]$y_title
plot_title = paste(title, "Trend over Time for", input$selected_state3)
# Create ggplot line chart
trend_line = ggplot(trend_data, aes(x = year)) +
geom_line(aes_string(y = selected_var), color = "#0073C2FF", linewidth = 1.2) +
geom_point(aes_string(y = selected_var), color = "#EFC000FF", size = 2) +
geom_ribbon(aes_string(ymin = min_y, ymax = selected_var), fill = "#0073C2FF", alpha = 0.3) +
geom_hline(yintercept = 0, color = "black", linetype = "solid", linewidth = 0.27) +
labs(title = plot_title, x = "Year", y = y_title) +
theme_economist_white(base_size = 14) +
theme(plot.title = element_text(size = 12.70, hjust = 0)) +
scale_x_continuous(
breaks = seq(min(trend_data$year), max(trend_data$year), by = 1),
labels = as.integer(seq(min(trend_data$year), max(trend_data$year), by = 1))) +
scale_y_continuous(
limits = c(min_y, max_y), labels = scales::comma)
# Convert ggplot to interactive Plotly chart
plotly_trend_plot = ggplotly(trend_line) %>%
layout(title = list(text = plot_title,
font = list(size = 17, family = "Arial Black", color = "black"),
x = 0, xanchor = "left", y = 0.95, yanchor = "top"))
return(plotly_trend_plot)
})
The correlation heatmap reveals pairwise relationships between key economic indicators within the selected state and year range. Users can quickly identify strong positive or negative relationships through color intensity.
# Render correlation heatmap for selected state and year range
output$correlation_heatmap = renderPlotly({
req(input$selected_state3)
# Filter numeric data for the selected state
numeric_data = filtered_data() %>%
filter(state == input$selected_state3) %>%
select(mean_wages, median_wages, cpi, inflation, gdp, gdp_capita)
# Compute correlation matrix and convert to long format
cor_matrix = round(cor(numeric_data, use = "complete.obs"), 2)
cor_long = as.data.frame(as.table(cor_matrix))
colnames(cor_long) = c("Var1", "Var2", "Correlation")
# Rename variables for readability in the plot
cor_long$Var1 = recode(cor_long$Var1, mean_wages = "Mean Wages", median_wages = "Median Wages",
cpi = "CPI", inflation = "Inflation Rate", gdp = "GDP",
gdp_capita = "GDP per Capita")
cor_long$Var2 = recode(cor_long$Var2, mean_wages = "Mean Wages", median_wages = "Median Wages",
cpi = "CPI", inflation = "Inflation Rate", gdp = "GDP",
gdp_capita = "GDP per Capita")
# Create ggplot heatmap
heatmap_plot = ggplot(cor_long,
aes(Var1, Var2, fill = Correlation, text = paste0("Corr: ", Correlation))) +
geom_tile(color = "white") +
scale_fill_gradient2(low = "springgreen", mid = "white", high = "tomato", midpoint = 0,
limit = c(-1,1), name = "Correlation", breaks = seq(-1, 1, 0.5),
labels = c("-1.0", "-0.5", " 0", " 0.5", " 1.0")) +
labs(title = paste("Economic Indicators Correlation Heatmap for", input$selected_state3, "from",
input$year_range[1], "to", input$year_range[2])) +
theme_economist_white(base_size = 14) +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1),
axis.title = element_blank(),
plot.title = element_text(size = 12.70, hjust = 0)) +
theme(
legend.position = "right", legend.title = element_text(size = 12),
legend.text = element_text(size = 9), legend.key.width = unit(0.5, "cm"),
legend.key.height = unit(2, "cm"))
# Convert ggplot to Plotly and handle insufficient data properly
plotly_heatmap = ggplotly(heatmap_plot, tooltip = "text")
if (nrow(numeric_data) < 2) {
plotly_heatmap = plotly_heatmap %>%
layout(title = list(text = paste0("Not Enough Data to Compute the<br>Correlation for ",
input$selected_state3, " in ", input$year_range[1]),
font = list(size = 17, family = "Arial Black", color = "black"),
x = 0.5, xanchor = "center", y = 0.95, yanchor = "top"),
autosize = TRUE)
} else {
plotly_heatmap = plotly_heatmap %>%
layout(title = list(text = paste0("Economic Indicators Correlation Heatmap<br>for ",
input$selected_state3, " from ",
input$year_range[1], " to ", input$year_range[2]),
font = list(size = 17, family = "Arial Black", color = "black"),
x = 0.5, xanchor = "center", y = 0.95, yanchor = "top"),
autosize = TRUE)
}
return(plotly_heatmap)
})
This section presents a visual summary of the What-If Simulation results defined in Tab 1. It allows users to see how certain predicted indicator outcomes would vary across all Malaysian states based on a hypothetical set of economic inputs. This enables quick comparison and interpretation of how different states may be affected under the same simulated scenario.
This interactive bar chart displays the predicted values of the selected indicator for each state. It allows users to easily identify the states with the highest and lowest projected values under the defined input scenario.
# Render predicted bar chart by state
output$bar_plot = renderPlotly({
req(input$predict_mode, input$selected_variable, input$run_sim)
# Filter out national level and set state factor levels
states = unique(merged_df$state)
states = states[states != "Malaysia"]
states = droplevels(states)
# Assign relevant predictors and model based on prediction mode
prediction_data = data.frame(state = factor(states, levels = levels(merged_df$state)))
if (input$predict_mode == "p_wage") {
prediction_data$cpi = input$cpi
prediction_data$inflation = input$inflation
prediction_data$gdp = input$gdp
prediction_data$gdp_capita = input$gdp_capita
model = if (input$selected_variable == "mean_wages") mean_model else median_model
} else if (input$predict_mode == "p_gdp") {
prediction_data$cpi = input$cpi
prediction_data$inflation = input$inflation
model = if (input$selected_variable == "gdp") gdp_model else capita_model
} else {
prediction_data$gdp = input$gdp
prediction_data$gdp_capita = input$gdp_capita
model = if (input$selected_variable == "cpi") cpi_model else inf_model
}
# Predict the selected economic indicator across states
prediction_data$prediction = predict(model, newdata = prediction_data)
# Dynamic plot title based on selected indicator
title = variable_labels[[input$selected_variable]]$title
y_title = variable_labels[[input$selected_variable]]$y_title
# Set gradient color scheme
base_colors = c("tomato", "#F0F700", "#66CC33")
if (input$selected_variable %in% c("cpi", "inflation")) {
base_colors = rev(base_colors)
}
# Create ggplot bar chart
bar_state = ggplot(prediction_data, aes(x = reorder(state, -prediction),
y = prediction, fill = prediction)) +
geom_bar(stat = "identity") +
geom_hline(yintercept = 0, color = "black", linetype = "solid", linewidth = 0.3) +
labs(title = paste("Predicted", title, "by State"), x = "State", y = y_title) +
scale_fill_gradientn(colors = base_colors) +
scale_y_continuous(labels = scales::comma) +
theme_economist_white(base_size = 14) +
theme(legend.position = "none",
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1))
# Convert ggplot to interactive Plotly chart
ggplotly(bar_state, tooltip = c("label", "x", "y"))
})
This interactive choropleth map visualizes the predicted values geographically using color gradients. It enables a quick glance at regional disparities and how each state is affected under the simulated economic scenario.
# Render Predicted Choropleth Map of Malaysia
output$map_plot = renderLeaflet({
req(input$predict_mode, input$selected_variable, input$run_sim)
# Filter out national level and set state factor levels
states = unique(merged_df$state)
states = states[states != "Malaysia"]
states = droplevels(states)
# Assign relevant predictors and model based on prediction mode
prediction_data = data.frame(state = factor(states, levels = levels(merged_df$state)))
if (input$predict_mode == "p_wage") {
prediction_data$cpi = input$cpi
prediction_data$inflation = input$inflation
prediction_data$gdp = input$gdp
prediction_data$gdp_capita = input$gdp_capita
model = if (input$selected_variable == "mean_wages") mean_model else median_model
} else if (input$predict_mode == "p_gdp") {
prediction_data$cpi = input$cpi
prediction_data$inflation = input$inflation
model = if (input$selected_variable == "gdp") gdp_model else capita_model
} else {
prediction_data$gdp = input$gdp
prediction_data$gdp_capita = input$gdp_capita
model = if (input$selected_variable == "cpi") cpi_model else inf_model
}
# Predict the selected economic indicator across states
prediction_data$prediction = predict(model, newdata = prediction_data)
# Load and match state geometry data from Natural Earth
malaysia_map = ne_states(country = "Malaysia", returnclass = "sf")
malaysia_map$name = as.character(malaysia_map$name)
# Adjust state naming to match data set
malaysia_map$name[malaysia_map$name == "Kuala Lumpur"] = "W.P. Kuala Lumpur"
malaysia_map$name[malaysia_map$name == "Labuan"] = "W.P. Labuan"
malaysia_map$name[malaysia_map$name == "Putrajaya"] = "W.P. Putrajaya"
malaysia_map$name = factor(malaysia_map$name, levels = levels(merged_df$state))
# Merge map with predictions
map_data = left_join(malaysia_map, prediction_data, by = c("name" = "state"))
# Generate color palette for bins
base_colors = c("#E60000", "#FFED00", "#006633")
if (input$selected_variable %in% c("cpi", "inflation")) {
base_colors = rev(base_colors)
}
state_colors = colorRampPalette(base_colors)(7)
pal = colorBin(palette = state_colors, domain = na.omit(map_data$prediction),
bins = 7, na.color = "transparent")
# Create interactive choropleth map
leaflet(data = map_data) %>%
addProviderTiles("OpenStreetMap") %>%
addPolygons(
fillColor = ~pal(prediction), weight = 1, opacity = 1, color = "black",
dashArray = "3", fillOpacity = 0.7, label = ~paste0(name, ": ", round(prediction, 2)),
highlightOptions = highlightOptions(
weight = 2, color = "red", fillOpacity = 0.9, bringToFront = TRUE),
labelOptions = labelOptions(direction = "auto")) %>%
addLegend(pal = pal, values = ~prediction, opacity = 0.7,
title = variable_labels[[input$selected_variable]]$y_title,
position = "bottomright") %>%
addControl(html = paste0("<div style='font-size:22px; font-weight:bold;
padding:6px; border-radius:6px;'>", "Predicted ",
variable_labels[[input$selected_variable]]$title,
" Choropleth Map of Malaysia</div>"),
position = "topright")
})
Finally, this section enables users to forecast future values of selected economic indicators for a chosen state. It uses historical annual data and fits an ARIMA model to project trends over a user-defined forecast horizon. The resulting visualization helps anticipate future economic outcomes, based on the assumption that past patterns will continue.
# Components needed to generate forecast using time series
# Reactive filter to extract time series data for the selected variable and state
ts_filtered_data = reactive({
req(input$forecast_variable, input$forecast_state)
merged_df %>%
filter(state == input$forecast_state) %>%
select(year, all_of(input$forecast_variable))
})
# Time Series Forecasting
observeEvent(input$run_forecast, {
req(ts_filtered_data(), input$forecast_horizon)
# Convert the filtered variable into a time series object
ts_data = ts(ts_filtered_data()[[input$forecast_variable]] +
rnorm(length(ts_filtered_data()[[input$forecast_variable]]),
mean = 0, sd = 0.5),
start = min(ts_filtered_data()$year), frequency = 1)
# Fit an ARIMA(2,1,2) model
fit = arima(ts_data, order = c(2, 1, 2))
# Forecast for user-specified horizon (in years)
forecast_result = forecast(fit, h = input$forecast_horizon)
# Create a forecast data frame with future years
forecast_df = data.frame(
year = seq(max(ts_filtered_data()$year) + 1,
max(ts_filtered_data()$year) + input$forecast_horizon, by = 1),
forecast = forecast_result$mean)
# Render the forecast time series plot using Plotly
output$forecast_plot = renderPlotly({
all_years = seq(min(ts_filtered_data()$year), max(forecast_df$year), by = 1)
plot_ly() %>%
add_trace(data = ts_filtered_data(), x = ~year,
y = as.formula(paste0("~", input$forecast_variable)),
type = "scatter", mode = "markers+lines", name = "Actual",
line = list(color = "#0073C2FF"), marker = list(size = 6)) %>%
add_trace(data = forecast_df, x = ~year, y = ~forecast,
type = "scatter", mode = "markers+lines", name = "Forecast",
line = list(color = "#E60000", dash = 'dot'), marker = list(size = 6)) %>%
layout(title = list(
text = paste(variable_labels[[input$forecast_variable]]$title,
"Time Series Forecast for", input$forecast_state),
font = list(size = 18, family = "Arial Black", color = "#333333"),
xanchor = "center", y = 0.99),
xaxis = list(
title = "Year", tickvals = all_years, ticktext = all_years,
showline = TRUE, linecolor = "#333333", gridcolor = "#D3D3D3"),
yaxis = list(
title = variable_labels[[input$forecast_variable]]$y_title,
tickformat = ",", showgrid = TRUE, showline = TRUE,
linecolor = "#333333", gridcolor = "#D3D3D3"),
legend = list(
orientation = "h", xanchor = "center", x = 0.5, y = -0.2),
shapes = list(
list(
type = "rect",
x0 = min(forecast_df$year), x1 = max(forecast_df$year),
y0 = 0, y1 = 1, xref = "x", yref = "paper",
fillcolor = "rgba(230, 0, 0, 0.2)", line = list(width = 0))),
hovermode = "x unified", paper_bgcolor = "#EBEBEB", plot_bgcolor = "#EBEBEB")
})
})
Finally, to bring the dashboard to life, the application is launched
using the shinyApp(ui, server) function, which binds
together the user interface and server logic into a functional Shiny
app.
Following this, the What-If Analysis Dashboard can be deployed online using shinyapps.io. For this project, it has been hosted there for public access. The dashboard enables users to perform interactive simulations, compare hypothetical scenarios, and analyse key economic indicators across Malaysian states. It uses built-in predictive models to generate real-time predictions based on user-defined inputs.
You can access the full live interactive dashboard here:
👉 Launch
What-If Analysis Dashboard
The image below provides a static preview of Tab 1 of the What-If Analysis Dashboard interface. To experience full functionality, please use the full-screen link above to access the live version.
Figure 1: Static Preview of Tab
1 — What-If Simulation
ℹ️ Note: This static preview is intended for offline or non-interactive viewing. For full interactivity and the best experience, visit the dashboard link provided above.
This report has outlined the full process of developing the What-If Analysis Dashboard, including data handling, model construction, and dynamic user interface design using Shiny. By integrating interactive features with predictive analytics, the dashboard provides a centralized platform for monitoring Malaysia’s economic performance through key indicators such as CPI, inflation rate, wages, and GDP across all states.
By enabling users to model potential outcomes and evaluate policy impacts under various hypothetical scenarios, the dashboard serves as a powerful decision-support tool for policymakers, analysts, and stakeholders. The final product is publicly accessible online and demonstrates how R and Shiny applications can transform complex data into user-friendly and dynamic dashboards.
Ultimately, this project not only exemplifies the real-world application of statistical knowledge but also contributes a valuable digital resource to support data-driven governance and strategic economic planning in Malaysia. Moving forward, the dashboard can be further enhanced by incorporating more granular data, expanding the range of economic indicators, and integrating real-time data sources to improve its responsiveness and relevance for long-term planning.
https://open.dosm.gov.my/data-catalogue
https://www.bnm.gov.my/national-summary-data-page-for-malaysia
https://www.ceicdata.com/en/malaysia/salaries–wages-survey-by-states
https://en.wikipedia.org/wiki/List_of_Malaysian_states_by_GDP
https://www.geeksforgeeks.org/r-language/leaflet-package-in-r/
https://www.simplilearn.com/tutorials/excel-tutorial/excel-what-if-analysis
https://www.prophix.com/blog/how-to-consider-the-what-ifs-in-times-of-uncertainty/
https://www.cubesoftware.com/blog/what-if-analysis
https://www.geeksforgeeks.org/r-language/shiny-r-dashboard/
https://www.appsilon.com/post/journey-from-basic-prototype-to-production-ready-shiny-dashboard
https://epirhandbook.com/en/new_pages/shiny_basics.html