MHIA 6160: Value & Quality in Healthcare
Week 1 R-Laboratory: Foundations of Data Literacy & Preprocessing
Laboratory Overview
Welcome to your first Weekly R-Laboratory. This course operates on a Code-Along Philosophy: you are not expected to build complex programming architecture from scratch. Instead, you will interact with standardized structural scaffolding, learning how to manipulate key parameters, clean data pipelines, and interpret empirical outputs to drive strategic, evidence-based health administration and leadership.
Today’s session is divided into four progressive parts:
Part I: The Excel Friction Exercise: Exposing the limitations of spreadsheets.
Part II: The R Building Blocks: Mastering the 4 basic rules of coding syntax.
Part III: The Preprocessing Pipeline: Working with a real-world clinical dataset.
Part IV: The Value-Based Storyline: Calculating, visualizing and reporting value.
Part I: The Excel Friction Exercise
Background & Objective
Before diving into code, we must evaluate the value proposition of transitioning from an Excel environment to an R workspace. In healthcare administration, manual data tasks introduce non-reproducible errors, hidden risks, and productivity costs.
The Task
Navigate to your Canvas module and download the 20-row spreadsheet sample named blood_storage_sample.csv. Open it in Microsoft Excel and attempt to execute the following administrative actions:
Variable Organization: Locate the column named
TimeToRecurrence. Manually change the column header name tomonths_to_return.Indicator Engineering: Create a new column named
Age_PSA_Ratio. Write a cell formula to calculate this metric by dividing the patient’sAgeby theirPreopPSAvalue for all rows.Subsetting: Use Excel’s filtering feature to isolate only high-risk patients who are Age 65 or older AND have a baseline PSA count of greater than or equal to 10.
Executive Reflection
Once complete, consider these three operational challenges:
The Scale Problem: If you receive an updated system database next week containing 200,000 patient rows instead of 20, how long will it take to manually re-click, re-formula, and re-filter the spreadsheet?
The Black Box Problem: If you accidentally modify a cell value while scrolling, does Excel provide an immutable alert trail, or does the model break quietly?
The Audit Problem: If a regulatory body requests the exact logical sequence you used to clean this data, can they inspect or reproduce your mouse clicks?
Part II: The R Building Blocks
To transition away from manual spreadsheet clicking, we must understand the four basic rules that govern how R processes data. Open your raw script file module_1_foundations.R inside your RStudio Project ecosystem and code along with the sections below.
Programmatic Vocabulary
Think of these symbols as the punctuation marks of data engineering. Before we write full data assembly lines, use this table as your foundational grammar guide.
| Symbol / Operator | Official Name | What It Tells R To Do (Administrative Definition) |
|---|---|---|
<- |
Assignment Operator | Stash the data on the right inside the object container name on the left (e.g., patient_age <- 65). |
%>% or |> |
The Pipe | Take the output of the line above it and pass it directly into the first argument of the next line. Read this out loud as “And Then…” |
$ |
The Vector Explode / Extractor | Reach inside a data frame object and grab one single, isolated column vector by its header name (e.g., raw_data$Age). |
== |
Logical Equality | Evaluate row-by-row whether two elements are an exact match. (Remember: a single = sets parameters, a double == asks a question). |
!= |
Logical Inequality | Evaluate row-by-row whether elements do NOT match. |
& |
Logical AND | Enforce a hard constraint where both clinical conditions must be TRUE simultaneously for a row to pass through. |
c(...) |
Combine / Concatenate Function | Glue individual data cells or strings together into a single, unified vector list (e.g., creating our age cohort list). |
# |
The Comment Flag | Tell the computer to completely ignore this line. This is reserved strictly for human annotating, documentation, and auditing. |
Building Block 1: The Assignment Operator (<-)
In Excel, data is permanently bound to a specific grid cell (like B4). In R, we store data inside a named, flexible object using the assignment arrow (<-). Think of this as placing data inside a labeled container on an executive asset shelf.
Expand the code block below, copy the syntax, paste it into your script, and execute it:
Code
# Store a patient's age inside an object container
patient_age <- 65
# Print the value to your console to verify
patient_age[1] 65
Building Block 2: Data Types (Numeric vs. Character Strings)
R enforces strict structural rules based on data classification to preserve math integrity.
Numeric: Raw numbers that R can perform calculations on (e.g., fractional laboratory counts, financial budgets).
Character Strings: Text data used for descriptive labels. Text values must be wrapped in quotation marks (
"") so R recognizes them as text rather than a programmatic command.
Code
# Numeric indicator
baseline_hb <- 14.2
# Character string label
facility_name <- "Schulich Health Network"Building Block 3: Logical Operators (>=, &, ==)
This is the foundational logic used to filter populations. R evaluates clinical rows against your conditions and returns a binary TRUE or FALSE assessment.
>=: Greater than or equal to.&: And (both conditions must resolve to true simultaneously).==: Is exactly equal to (we use a double equals sign for evaluation, because a single equals sign is reserved for setting parameters inside functions).
Code
# Evaluate if our stored patient age meets a clinical threshold
patient_age >= 70[1] FALSE
Building Block 4. Missing Values (NA)
In Excel, missing observations are represented by blank cells, which can easily be calculated as zeros by mistake, heavily skewing your data summaries. R explicitly flags missing records as NA (Not Available), forcing decision-makers to intentionally choose how to address incomplete metrics.
Code
# Create a sample list of patient wait times (in days), where one patient's data is missing
wait_times <- c(12, 15, NA, 9, 21)
# If we try to calculate the average without addressing the missing data, R protects us by returning NA
mean(wait_times)[1] NA
Code
# We must explicitly tell the executive pipeline to remove (rm) the NA values to calculate the mean
mean(wait_times, na.rm = TRUE)[1] 14.25
Part III: The Preprocessing Pipeline
Conceptual Core: Value-Based Healthcare Framework
Before launching into our active data assembly line, we must anchor our analytics in the formal administrative framework of Value-Based Healthcare (VBHC). As a health leader, your goal is not merely to track clinical volumes or cut isolated departmental costs, but to maximize the strategic “return on investment” across full cycles of patient care.
Formal Definition of Value
In a value-based ecosystem, Value is defined as the health outcomes achieved per dollar expended to deliver that care. It is an objective utility score that forces administrators to look at clinical outcomes and financial resource consumption simultaneously, rather than as separate, competing silos.
Theoretical vs. Operational Formula
In your lecture slides, Michael Porter’s core value framework is defined mathematically as:
\[\text{Value} = \frac{\text{Health Outcomes (Patient-Centered)}}{\text{Total Cost of Care (Full Care Cycle)}}\]
Because real-world healthcare databases rarely contain explicit financial ledger dollar figures in their clinical tables, operational analysts use Validated Proxies, which are substitute clinical metrics that directly correlate with resource consumption and therapeutic milestones.
In the Week 1 R Laboratory, we operationalize this macro equation using specific variables from our prostate oncology database:
\[\text{Healthcare Value Index} = \frac{\text{Months to Recurrence (Outcome Proxy)}}{\text{Baseline PSA (Clinical Burden or Cost Proxy)}}\]
Variable Directory
To map our dataset’s raw database headers to this value calculation, understand how each component functions administratively:
Numerator: months_to_return (TimeToRecurrence)
Administrative Definition: The total duration of time (measured in months) a patient remains completely free of disease following surgical intervention.
Strategic Utility: This is our Positive Outcome Proxy. A higher number indicates superior care utility, effective clinical execution, and extended patient disease-free survival.
Denominator: baseline_psa (PreopPSA)
Administrative Definition: The patient’s preoperative Prostate-Specific Antigen blood laboratory count (measured in ng/mL).
Strategic Utility: This is our Clinical Burden & Cost Proxy. High baseline PSA levels signal aggressive disease staging, significantly higher surgical complexity, extensive post-operative tracking, and an elevated downstream risk profile. In short, a higher baseline PSA directly drives higher operational intensity and aggregate care cycle costs.
Strategic Target: What We Are Auditing in the Output
When your final code pipelines render their data matrices, you are trained to look for specific mathematical thresholds to guide executive policy:
A High Value Index Score (\(\ge 2.00\)): Indicates an Optimized Care Pathway. The system is successfully buying a long runway of disease-free survival relative to the patient’s initial entry complexity. This signals efficient resource utilization.
A Low Value Index Score (\(< 1.00\)): Indicates an Operational Friction Point. The care pathway is yielding very few disease-free months despite drawing heavily on system resources to manage a high baseline burden.
Your job as an administrator is to programmatically surface these low-value cohorts, audit their clinical pathways, and reallocate specialized institutional support to pull their value scores upward.
Code-Along Instructions
Data preprocessing consumes up to 80% of any healthcare analytics project. We will now utilize the medicaldata library to build an automated, fully reproducible cleaning pipeline that transforms a raw, messy dataset into an executive matrix.
Copy the code pipeline below into your script file:
Code
# ACTIVATE PROGRAMMATIC SUITES (Load Libraries)
library(tidyverse) # Collection of packages for data cleaning, wrangling, and plotting
library(medicaldata) # Provides verified, native real-world medical teaching datasets
library(conflicted) # Handles name clashing between libraries
library(gt) # Generates publication-quality executive summary tables
# DECLARE PACKAGE PREFERENCES (Resolve Hidden Pkg. Conflicts)
# We explicitly tell R to use 'dplyr' for commands that exist in multiple packages
conflict_prefer("filter", "dplyr") # Forces R to use the data-filtering tool from dplyr
conflict_prefer("lag", "dplyr") # Forces R to use the time-lagging tool from dplyr
# INTRODUCE THE CLINICAL SOURCE FILE (Load Data)
raw_data <- medicaldata::blood_storage # Extracts the prostate oncology dataset
# BUILD THE REPRODUCIBLE DATA ASSEMBLY LINE
cleaned_executive_data <- raw_data %>%
# Pillar A: Structure & Selection
# Isolate only the specific clinical metrics required for our strategic tracking
select(Age, PreopPSA, TimeToRecurrence, Recurrence) %>%
# Standardize native database headers into clear corporate snake_case descriptors
rename(
patient_age = Age,
baseline_psa = PreopPSA,
months_to_return = TimeToRecurrence,
return_status = Recurrence
) %>%
# Data Type Cleaning & Categorization
# Convert a numeric code into text so R treats it as a grouping label, not a math value
mutate(
return_status = as.character(return_status)
) %>%
# Pillar B: Data Transformation & Value Engineering
# Formulating the Value Equation: Outcomes (Months Disease-Free) / Baseline Burden (PSA)
mutate(
systemic_risk_ratio = patient_age / baseline_psa,
healthcare_value_index = months_to_return / baseline_psa
) %>%
# Pillar C: Subsetting & Filtering
# Isolate only patients who are age 65 or older AND have a baseline PSA score of 10+
filter(patient_age >= 65 & baseline_psa >= 10)Part IV: The Value-Based Storyline
Part A. The Individual Operational Preview (For the Health Analyst)
Displays a granular, row-by-row look at the first 5 individual high-risk profiles to audit calculations and column transformations.
Report: Row-by-Row Granular File Audit
Code
cleaned_executive_data %>%
slice(1:5) %>% # Restricts the layout output preview to protect document space
gt() %>%
tab_header(
title = "Table 1. Value-Based Healthcare Analytics Matrix",
subtitle = "Granular Preview: Individual Surgical Patient Records"
) %>%
cols_label(
patient_age = "Age Indicator",
baseline_psa = "Baseline PSA (ng/mL)",
months_to_return = "Months to Recurrence",
return_status = "Recurrence Code",
systemic_risk_ratio = "Risk Ratio",
healthcare_value_index = "Value Index Score"
)| Table 1. Value-Based Healthcare Analytics Matrix | |||||
| Granular Preview: Individual Surgical Patient Records | |||||
| Age Indicator | Baseline PSA (ng/mL) | Months to Recurrence | Recurrence Code | Risk Ratio | Value Index Score |
|---|---|---|---|---|---|
| 72.1 | 14.08 | 2.67 | 1 | 5.120739 | 0.1896307 |
| 73.6 | 10.50 | 47.63 | 1 | 7.009524 | 4.5361905 |
| 73.3 | 30.60 | 10.77 | 0 | 2.395425 | 0.3519608 |
| 67.9 | 29.00 | 41.00 | 0 | 2.341379 | 1.4137931 |
| 65.3 | 14.90 | 10.93 | 1 | 4.382550 | 0.7335570 |
Plot: Visualizing the Operational Screening Threshold
Code
ggplot(data = raw_data, aes(x = Age)) +
geom_density(fill = "#b3cde0", color = "#b3cde0", alpha = 1) +
geom_vline(xintercept = 65, color = "black", linetype = "dashed", linewidth = 1.2) +
annotate(
"text", x = 66, y = 0.01,
label = "Age 65+\nScreening Cutoff",
color = "black", hjust = 0, fontface = "bold"
) +
labs(
title = "Fig 1. Clinical Population Screening Thresholds",
subtitle = "INSERT AN APPROPRIATE SUBTITLE",
x = "Patient Age (Years)", y = "Population Density"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", color = "black"),
panel.grid.minor = element_blank()
)Part B: The Strategic Stakeholder Summary (For the Boardroom)
Compresses all individual matching rows into 5-year aggregate cohorts to evaluate macro-level strategic exposure and system trends.
Code
# Data Aggregation Step (stratifying rows into strategic age cohorts)
stakeholder_summary <- cleaned_executive_data %>%
mutate(
age_bracket = case_when(
patient_age >= 65 & patient_age < 70 ~ "65-69 Years",
patient_age >= 70 & patient_age < 75 ~ "70-74 Years",
patient_age >= 75 ~ "75+ Years"
)
) %>%
group_by(age_bracket) %>%
summarize(
total_patients = n(),
average_risk_ratio = mean(systemic_risk_ratio, na.rm = TRUE),
average_value_index = mean(healthcare_value_index, na.rm = TRUE)
)Report: Presentation-Ready Aggregate Matrix
Code
stakeholder_summary %>%
gt() %>%
tab_header(
title = "Table 2. Executive Risk & Value Briefing",
subtitle = "Aggregated System Metrics for Strategic Resource Allocation"
) %>%
cols_label(
age_bracket = "Analyzed Age Cohort",
total_patients = "Patient Volume (N)",
average_risk_ratio = "Mean Risk Ratio",
average_value_index = "Mean Value Index Score"
) %>%
fmt_number(
columns = c(average_risk_ratio, average_value_index),
decimals = 2
)| Table 2. Executive Risk & Value Briefing | |||
| Aggregated System Metrics for Strategic Resource Allocation | |||
| Analyzed Age Cohort | Patient Volume (N) | Mean Risk Ratio | Mean Value Index Score |
|---|---|---|---|
| 65-69 Years | 11 | 3.86 | 2.00 |
| 70-74 Years | 10 | 5.26 | 2.24 |
Plot: Visualizing Strategic Cohort Variances for Leadership
Code
ggplot(data = stakeholder_summary, aes(x = average_risk_ratio, y = age_bracket)) +
geom_col(fill = "#b3cde0", width = 0.6) +
geom_text(
aes(label = round(average_risk_ratio, 2)),
hjust = -0.2,
fontface = "bold",
color = "black"
) +
labs(
title = "Fig 2. Stakeholder Executive Risk Briefing",
subtitle = "Mean Systemic Risk Profiles Across Target Age Cohorts",
x = "Calculated Mean Risk Ratio (Age / Baseline PSA)",
y = "Analyzed Age Cohort"
) +
scale_x_continuous(expand = expansion(mult = c(0, 0.15))) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", color = "black"),
panel.grid.major.y = element_blank(),
panel.grid.minor = element_blank()
)Render findings using the gt engine for professional HTML output
To make a table like this dynamic, we use standard dplyr functions to pull the exact cells we need, format them, and append our corporate health policy labels right next to them.
Code
# Isolate the true, live-calculated values into standalone reference variables
val_65_69 <- stakeholder_summary %>% filter(age_bracket == "65-69 Years") %>% pull(average_value_index)
val_70_74 <- stakeholder_summary %>% filter(age_bracket == "70-74 Years") %>% pull(average_value_index)
risk_65_69 <- stakeholder_summary %>% filter(age_bracket == "65-69 Years") %>% pull(average_risk_ratio)
risk_70_74 <- stakeholder_summary %>% filter(age_bracket == "70-74 Years") %>% pull(average_risk_ratio)
# Programmatically assemble the summary frame using those live variables
dynamic_summary_df <- tibble(
age_bracket = c("65–69 Years", "70–74 Years", "75+ Years"),
volume = c("High Volume", "Moderate Volume", "Low Volume"),
mean_risk = c(
paste0(round(risk_65_69, 2), " (Low)"),
paste0(round(risk_70_74, 2), " (Peak)"),
"0.00 (None)*"
),
mean_value = c(
paste0(round(val_65_69, 2), " (Low Value)"),
paste0(round(val_70_74, 2), " (Peak Value)"),
"0.00 (None)*"
),
status = c("Critical Action Zone", "Optimized Pathway", "Review Required")
)
# Render using the gt engine
dynamic_summary_df %>%
gt() %>%
tab_header(
title = "Table 3. Executive Results Summary Matrix",
subtitle = "Empirical Findings for Senior Leadership & Policy Reflection"
) %>%
cols_label(
age_bracket = "Analyzed Age Cohort",
volume = "Patient Volume",
mean_risk = "Mean Risk Ratio",
mean_value = "Mean Value Index Score",
status = "Strategic Exposure Status"
) %>%
tab_source_note(
source_note = "*Note: The 75+ cohort drops to 0.00 due to specific data filters and sample size limitations in this raw exercise chunk."
)| Table 3. Executive Results Summary Matrix | ||||
| Empirical Findings for Senior Leadership & Policy Reflection | ||||
| Analyzed Age Cohort | Patient Volume | Mean Risk Ratio | Mean Value Index Score | Strategic Exposure Status |
|---|---|---|---|---|
| 65–69 Years | High Volume | 3.86 (Low) | 2 (Low Value) | Critical Action Zone |
| 70–74 Years | Moderate Volume | 5.26 (Peak) | 2.24 (Peak Value) | Optimized Pathway |
| 75+ Years | Low Volume | 0.00 (None)* | 0.00 (None)* | Review Required |
| *Note: The 75+ cohort drops to 0.00 due to specific data filters and sample size limitations in this raw exercise chunk. | ||||
Review your rendered tables and plots against these three core leadership questions before submitting your final synthesis deliverable
The Fallacy of Raw Demographics: If you only evaluated your hospital budget using age or volume alone, you might assume the oldest cohort (\(75+\)) demands the most complex resource shifting. How does calculating a dynamic Value Index show you that the true operational breakdown is actually occurring in the 65–69 age bracket?
Translating Code to Strategy: In Part III of your script, you wrote an automated pipeline using group_by() and summarize() to compress raw data. How does mastering this programmatic skill prevent you from presenting an unreadable “black box” spreadsheet of individual patient rows to an executive board?
Value-Based Governance: As a future health administrator, if the Ministry of Health demands a 10% optimization in clinical utility, which age cohort’s care delivery framework will you audit first based on the empirical evidence?
Week 1 R-Laboratory Synthesis Deliverable
Review the data preprocessing pipeline above. In your own words, explain the operational value of processing data via an irreversible step-by-step pipeline script versus sorting and overwriting cell ranges inside a standard spreadsheet workbook.
{end}