---
title: "Breast Cancer Molecular Subtypes & IHC Distribution — Kenyan Sites"
author: "Stellamaries Syombua"
date: "`r Sys.Date()`"
format:
html:
toc: true
toc-depth: 3
toc-location: left
number-sections: true
theme: flatly
self-contained: true
code-fold: true
code-tools: true
fig-width: 9
fig-height: 6
editor: visual
---
```{r setup, echo=FALSE, include=FALSE, message=FALSE, warning=FALSE}
# Load libraries
library(dplyr)
library(tidyr)
library(stringr)
library(ggplot2)
library(gt)
library(gtsummary)
library(scales)
library(forcats)
library(patchwork)
library(RColorBrewer)
```
# Load Data
```{r load-data, message=FALSE, warning=FALSE, include=FALSE, echo=FALSE}
# LOAD the data
setwd("G:/Shared drives/Data & Statistical Analysis Team/Study Specific Analysis/TACA Study/Rscripts")
knitr::purl("TACA_Data_Profiler_Data_Rscript_extract.qmd",
output = "tmp.R", documentation = 0)
source("tmp.R", local = TRUE)
unlink("tmp.R")
# Unpack the key datasets
taca_registration <- all_objects$data$taca_registration
breast_cancer_dsq_data <- all_objects$data$breast_cancer_dsq_data
Breast_IHC_data <- all_objects$data$Breast_IHC_data
multiquestions_data_wide <- all_objects$data$multiquestions_data_wide
current_date <- all_objects$current_date
cat("Data date:", current_date, "\n")
cat("Total breast cancer DSQ rows:", nrow(breast_cancer_dsq_data), "\n")
cat("Total IHC rows:", nrow(Breast_IHC_data), "\n")
```
# Filter to Kenyan Sites
```{r filter-kenya, message=FALSE, warning=FALSE}
# Kenyan Study sites
kenyan_sites <- c("Kenya-MERU", "Kenya-KTRH", "Kenya-NTRH")
# Breast cancer DSQ (Kenyan only)
breast_kenya <- breast_cancer_dsq_data %>%
filter(`Study Center` %in% kenyan_sites)
cat("Kenyan breast cancer participants (DSQ):", n_distinct(breast_kenya$`Participant Code`), "\n")
breast_kenya %>%
summarise(n = n_distinct(`Participant Code`), .by = `Study Center`)
```
# IHC Data Preparation
```{r ihc-prep, message=FALSE, warning=FALSE}
# Merge IHC markers with registration to get Study Center
ihc_kenya <- Breast_IHC_data %>%
left_join(
taca_registration %>%
dplyr::select(`Participant Code`, `Study Center`, `Study Country`),
by = "Participant Code"
) %>%
filter(`Study Center` %in% kenyan_sites)
cat("Kenyan IHC records:", nrow(ihc_kenya), "\n")
# Harmonise IHC result values to Positive / Negative
harmonise_ihc <- function(x) {
x_clean <- str_trim(tolower(as.character(x)))
case_when(
x_clean %in% c("positive", "pos", "yes", "1", "2", "3",
"2+", "3+", "1+", "reactive", "detected") ~ "Positive",
x_clean %in% c("negative", "neg", "no", "0", "0+",
"non-reactive", "not detected") ~ "Negative",
x_clean %in% c("equivocal", "borderline", "2+ equivocal") ~ "Equivocal",
TRUE ~ NA_character_
)
}
ihc_kenya <- ihc_kenya %>%
mutate(
ER_status = harmonise_ihc(ER),
PR_status = harmonise_ihc(PR),
HER2_status = harmonise_ihc(HER2)
)
# Quick check of harmonised values
ihc_kenya %>%
summarise(
ER_n = sum(!is.na(ER_status)),
PR_n = sum(!is.na(PR_status)),
HER2_n = sum(!is.na(HER2_status)),
.by = `Study Center`
)
```
# Molecular Subtype Classification
```{r subtype-classify, message=FALSE, warning=FALSE}
# Surrogate molecular subtypes
ihc_kenya <- ihc_kenya %>%
mutate(
HR_positive = ER_status == "Positive" | PR_status == "Positive",
Subtype = case_when(
# Need all three markers to classify
is.na(ER_status) | is.na(PR_status) | is.na(HER2_status) ~ NA_character_,
# HER2-enriched equivocal — treat as indeterminate
HER2_status == "Equivocal" ~ "Indeterminate",
# Luminal A : HR+, HER2-
HR_positive & HER2_status == "Negative" ~ "Luminal A",
# Luminal B : HR+, HER2+
HR_positive & HER2_status == "Positive" ~ "Luminal B",
# HER2-enriched : HR-, HER2+
!HR_positive & HER2_status == "Positive" ~ "HER2-enriched",
# TNBC : HR-, HER2-
!HR_positive & HER2_status == "Negative" ~ "Triple Negative",
TRUE ~ NA_character_
),
Subtype = factor(
Subtype,
levels = c("Luminal A", "Luminal B", "HER2-enriched", "Triple Negative", "Indeterminate")
)
)
cat("Subtype classification summary:\n")
print(table(ihc_kenya$Subtype, useNA = "ifany"))
```
# Results
## Overall Molecular Subtype Distribution
```{r subtype-overall-table, message=FALSE, warning=FALSE}
# Table — overall subtype counts & percentages
subtype_overall <- ihc_kenya %>%
filter(!is.na(Subtype)) %>%
count(Subtype, name = "n") %>%
mutate(
Percentage = round(100 * n / sum(n), 1),
`n (%)` = paste0(n, " (", Percentage, "%)")
)
subtype_overall %>%
dplyr::select(Subtype, n, `n (%)`) %>%
gt() %>%
tab_header(
title = "Molecular Subtype Distribution",
subtitle = "Kenyan sites combined"
) %>%
cols_label(
Subtype = "Molecular Subtype",
n = "Count",
`n (%)` = "n (%)"
) %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_labels()
) %>%
grand_summary_rows(
columns = n,
fns = list(Total = ~sum(.)),
fmt = ~ fmt_integer(.)
) %>%
opt_stylize(style = 6, color = "blue")
```
```{r subtype-overall-plot, message=FALSE, warning=FALSE, fig.cap="Overall molecular subtype distribution across all Kenyan sites"}
# Plot — overall subtype bar chart
subtype_colours <- c(
"Luminal A" = "#2196F3",
"Luminal B" = "#4CAF50",
"HER2-enriched" = "#FF9800",
"Triple Negative" = "#F44336",
"Indeterminate" = "#9E9E9E"
)
p_overall <- subtype_overall %>%
ggplot(aes(x = fct_reorder(Subtype, n), y = n, fill = Subtype)) +
geom_col(width = 0.65, show.legend = FALSE) +
geom_text(aes(label = paste0(n, "\n(", Percentage, "%)")),
hjust = -0.1, size = 3.5, lineheight = 0.9) +
scale_fill_manual(values = subtype_colours) +
scale_y_continuous(expand = expansion(mult = c(0, 0.2))) +
coord_flip() +
labs(
title = "Overall Molecular Subtype Distribution",
subtitle = paste0("n = ", sum(subtype_overall$n), " classified patients"),
x = NULL,
y = "Number of Patients"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold"),
plot.subtitle = element_text(colour = "grey40"),
panel.grid = element_blank(),
panel.grid.major = element_blank()
)
p_overall
```
## Subtype Distribution by Kenyan Site
```{r subtype-by-site-table, message=FALSE, warning=FALSE}
# Cross-tabulation: Subtype × Site
subtype_site <- ihc_kenya %>%
filter(!is.na(Subtype)) %>%
count(`Study Center`, Subtype) %>%
group_by(`Study Center`) %>%
mutate(
site_total = sum(n),
Pct = round(100 * n / site_total, 1),
`n (%)` = paste0(n, " (", Pct, "%)")
) %>%
ungroup()
subtype_site %>%
dplyr::select(`Study Center`, Subtype, n, `n (%)`) %>%
gt(groupname_col = "Study Center") %>%
tab_header(
title = "Molecular Subtype Distribution by Kenyan Site"
) %>%
cols_label(
Subtype = "Molecular Subtype",
n = "Count",
`n (%)` = "n (%)"
) %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_row_groups()
) %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_labels()
) %>%
opt_stylize(style = 6, color = "blue")
```
```{r subtype-by-site-plot, message=FALSE, warning=FALSE, fig.cap="Molecular subtype distribution stratified by Kenyan study site"}
# Stacked bar — proportion per site
p_site_stacked <- subtype_site %>%
ggplot(aes(x = `Study Center`, y = Pct, fill = Subtype)) +
geom_col(position = "stack", width = 0.6) +
geom_text(
aes(label = ifelse(Pct >= 5, paste0(Pct, "%"), "")),
position = position_stack(vjust = 0.5),
size = 3.2, colour = "white", fontface = "bold"
) +
scale_fill_manual(values = subtype_colours, name = "Molecular Subtype") +
scale_y_continuous(labels = label_percent(scale = 1)) +
labs(
title = "Molecular Subtype by Study Site",
x = NULL,
y = "Percentage (%)"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold"),
plot.subtitle = element_text(colour = "grey40"),
legend.position = "bottom",
legend.title = element_text(face = "bold"),
panel.grid = element_blank(),
panel.grid.major = element_blank()
) +
guides(fill = guide_legend(nrow = 2))
p_site_stacked
```
```{r subtype-by-site-facet, message=FALSE, warning=FALSE, fig.cap="Molecular subtype counts per site — faceted view", fig.height=5}
# counts per site
p_site_facet <- subtype_site %>%
ggplot(aes(x = Subtype, y = n, fill = Subtype)) +
geom_col(show.legend = FALSE, width = 0.65) +
geom_text(aes(label = n), vjust = -0.5, size = 3) +
scale_fill_manual(values = subtype_colours) +
scale_y_continuous(expand = expansion(mult = c(0, 0.18))) +
facet_wrap(~`Study Center`, scales = "free_y") +
labs(
title = "Molecular Subtype Counts",
x = NULL,
y = "Number of Patients"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 30, hjust = 1),
panel.grid = element_blank(),
panel.grid.major = element_blank(),
strip.text = element_text(face = "bold", size = 11)
)
p_site_facet
```
## IHC Marker Distribution by Kenyan Site
```{r ihc-marker-prep, message=FALSE, warning=FALSE}
# Reshape IHC data to long format for per-marker plots
ihc_long <- ihc_kenya %>%
dplyr::select(`Participant Code`, `Study Center`, ER_status, PR_status, HER2_status) %>%
pivot_longer(
cols = c(ER_status, PR_status, HER2_status),
names_to = "Marker",
values_to = "Result"
) %>%
mutate(
Marker = recode(Marker,
"ER_status" = "ER",
"PR_status" = "PR",
"HER2_status" = "HER2"
),
Marker = factor(Marker, levels = c("ER", "PR", "HER2")),
Result = factor(Result, levels = c("Positive", "Negative", "Equivocal"))
) %>%
filter(!is.na(Result))
```
```{r ihc-marker-table, message=FALSE, warning=FALSE}
# Table — IHC marker results by site
ihc_marker_summary <- ihc_long %>%
count(`Study Center`, Marker, Result) %>%
group_by(`Study Center`, Marker) %>%
mutate(
marker_total = sum(n),
Pct = round(100 * n / marker_total, 1),
`n (%)` = paste0(n, " (", Pct, "%)")
) %>%
ungroup()
ihc_marker_summary %>%
dplyr::select(`Study Center`, Marker, Result, n, `n (%)`) %>%
arrange(`Study Center`, Marker, Result) %>%
gt(groupname_col = "Study Center") %>%
tab_header(
title = "IHC Marker Distribution by Study Site"
) %>%
cols_label(
Marker = "Marker",
Result = "Result",
n = "Count",
`n (%)` = "n (%)"
) %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_row_groups()
) %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_labels()
) %>%
tab_style(
style = cell_fill(color = "#E8F5E9"),
locations = cells_body(
columns = Result,
rows = Result == "Positive"
)
) %>%
tab_style(
style = cell_fill(color = "#FFEBEE"),
locations = cells_body(
columns = Result,
rows = Result == "Negative"
)
) %>%
opt_stylize(style = 6, color = "blue")
```
```{r ihc-marker-plot, message=FALSE, warning=FALSE, fig.cap="IHC marker positivity rates by Kenyan study site", fig.height=7}
# positivity rate per marker per site
ihc_pos_rate <- ihc_marker_summary %>%
filter(Result == "Positive") %>%
dplyr::select(`Study Center`, Marker, n, Pct, marker_total)
marker_colours <- c("ER" = "#1565C0", "PR" = "#2E7D32", "HER2" = "#C62828")
p_marker_pos <- ihc_pos_rate %>%
ggplot(aes(x = `Study Center`, y = Pct, fill = Marker)) +
geom_col(position = position_dodge(width = 0.7), width = 0.65) +
geom_errorbar(
aes(
ymin = Pct - 1.96 * sqrt(Pct * (100 - Pct) / marker_total),
ymax = Pct + 1.96 * sqrt(Pct * (100 - Pct) / marker_total)
),
position = position_dodge(width = 0.7),
width = 0.25, colour = "grey30"
) +
geom_text(
aes(label = paste0(Pct, "%")),
position = position_dodge(width = 0.7),
vjust = -0.7, size = 3
) +
scale_fill_manual(values = marker_colours, name = "IHC Marker") +
scale_y_continuous(
limits = c(0, 105),
labels = label_percent(scale = 1)
) +
labs(
title = "IHC Marker Positivity Rates by Site",
x = NULL,
y = "Positivity Rate (%)"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold"),
plot.subtitle = element_text(colour = "grey40"),
legend.position = "top",
legend.title = element_text(face = "bold"),
panel.grid = element_blank()
)
p_marker_pos
```
```{r ihc-marker-facet, message=FALSE, warning=FALSE, fig.cap="Full IHC result distribution (Positive / Negative / Equivocal) faceted by marker", fig.height=8}
# full result distribution per marker
result_colours <- c(
"Positive" = "#1B5E20",
"Negative" = "#B71C1C",
"Equivocal" = "#F57F17"
)
p_marker_full <- ihc_marker_summary %>%
ggplot(aes(x = `Study Center`, y = Pct, fill = Result)) +
geom_col(position = "stack", width = 0.6) +
geom_text(
aes(label = ifelse(Pct >= 5, paste0(Pct, "%"), "")),
position = position_stack(vjust = 0.5),
size = 3, colour = "white", fontface = "bold"
) +
scale_fill_manual(values = result_colours, name = "IHC Result") +
scale_y_continuous(labels = label_percent(scale = 1)) +
facet_wrap(~Marker, ncol = 1) +
labs(
title = "IHC Marker Result Distribution by Kenyan Site",
x = NULL,
y = "Percentage (%)",
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
plot.subtitle = element_text(colour = "grey40"),
legend.position = "top",
legend.title = element_text(face = "bold"),
strip.text = element_text(face = "bold", size = 12),
panel.grid = element_blank()
)
p_marker_full
```
# Summary Statistics
```{r summary-stats, message=FALSE, warning=FALSE}
# overall characteristics table
ihc_kenya %>%
filter(!is.na(Subtype)) %>%
dplyr::select(`Study Center`, Subtype, ER_status, PR_status, HER2_status) %>%
tbl_summary(
by = `Study Center`,
label = list(
Subtype ~ "Molecular Subtype",
ER_status ~ "ER Status",
PR_status ~ "PR Status",
HER2_status ~ "HER2 Status"
),
statistic = all_categorical() ~ "{n} ({p}%)",
missing = "no"
) %>%
add_overall(last = FALSE) %>%
bold_labels() %>%
modify_header(label = "**Characteristic**") %>%
modify_caption("**Table 1. IHC and Subtype Summary — Kenyan Sites**")
```
# Data Completeness
```{r completeness, message=FALSE, warning=FALSE}
# Report missing IHC data per site
ihc_kenya %>%
group_by(`Study Center`) %>%
summarise(
Total_patients = n(),
ER_available = sum(!is.na(ER_status)),
PR_available = sum(!is.na(PR_status)),
HER2_available = sum(!is.na(HER2_status)),
All_3_available = sum(!is.na(ER_status) & !is.na(PR_status) & !is.na(HER2_status)),
Subtype_classified = sum(!is.na(Subtype)),
.groups = "drop"
) %>%
mutate(
Completeness_pct = round(100 * Subtype_classified / Total_patients, 1)
) %>%
gt() %>%
tab_header(
title = "IHC Data Completeness by Site",
subtitle = "Number of patients with available marker data"
) %>%
cols_label(
`Study Center` = "Site",
Total_patients = "Total Enrolled",
ER_available = "ER Available",
PR_available = "PR Available",
HER2_available = "HER2 Available",
All_3_available = "All 3 Available",
Subtype_classified = "Subtype Classified",
Completeness_pct = "Completeness (%)"
) %>%
data_color(
columns = Completeness_pct,
palette = "Blues"
) %>%
opt_stylize(style = 6, color = "blue")
```