1 Data import and cleaning

Raw data are read directly from the tab-delimited export (row 1 = QA comments, row 2 = variable labels, data from row 3 onward).

## Edit this path to wherever you've saved the file
raw <- read_delim(
  "~/Documents/R & Stats/Analisis/GI_Ethnicity/Robert_abstract.txt", 
  delim = "\t", skip = 2, col_names = FALSE,
  quote = '"', escape_double = TRUE, trim_ws = TRUE,
  col_types = cols(.default = "c")   
  # read everything as text first; convert below
)

var_names <- c(
  "id", "hospital_number", "dob", "date_death", "deceased", "date_last_fup",
  "event_os", "ethnicity_code", "ethnicity_detail", "sex", "date_diag",
  "age_at_diag", "age_at_met_diag", "date_met_diag", "comment",
  "disease_stage", "tumour_location", "sidedness", "synchronicity",
  "age_at_met", "date_last_fup2", "kras_detail", "kras_mut", "kras_specify",
  "nras_mut", "nras_specify", "braf_mut", "braf_specify", "tp53_mut",
  "tp53_specify", "pik3ca_mut", "pik3ca_specify", "mmr_text", "mmrd"
)
raw <- raw[, 1:34]
colnames(raw) <- var_names
parse_mixed_date <- function(x) {
  x <- str_trim(x)
  if (is.na(x) || x == "" || x %in% c("?", "unknown", "Unknown")) return(as.Date(NA))
  d <- suppressWarnings(dmy(x))
  if (is.na(d)) d <- suppressWarnings(as.Date(as.numeric(x), origin = "1899-12-30"))
  d
}
parse_date_col <- function(col) as.Date(vapply(col, parse_mixed_date, numeric(1)), origin = "1970-01-01")

clean_binary_mut <- function(x) {
  x <- str_trim(tolower(x))
  case_when(
    is.na(x) | x %in% c("", "na", "?", "???", "not tested", "not performed", "not done") ~ NA_real_,
    str_detect(x, "and") ~ as.numeric(str_detect(x, "1")),
    x == "1" ~ 1,
    x == "0" ~ 0,
    TRUE ~ NA_real_
  )
}

clean_side <- function(x) {
  x <- str_trim(tolower(x))
  case_when(
    x == "l" ~ "Left",
    x == "r" ~ "Right",
    x %in% c("r and l", "l and r") ~ "Bilateral/both",
    TRUE ~ NA_character_
  )
}
df <- raw %>%
  mutate(
    dob            = parse_date_col(dob),
    date_death     = parse_date_col(date_death),
    date_last_fup  = parse_date_col(date_last_fup),
    date_diag      = parse_date_col(date_diag),
    date_met_diag  = parse_date_col(date_met_diag),
    date_last_fup2 = parse_date_col(date_last_fup2),

    sex = str_trim(tolower(sex)) %>%
      recode("female" = "Female", "male" = "Male", .default = NA_character_) %>%
      factor(levels = c("Female", "Male")),

    ethnicity_code = factor(
      as.integer(ethnicity_code), levels = 1:6,
      labels = c("Asian", "Black", "White", "Other", "Mixed", "No data")
    ),

    disease_stage = factor(as.integer(disease_stage), levels = c(1, 2),
                            labels = c("Early", "Metastatic")),

    sidedness     = clean_side(sidedness),
    synchronicity = str_trim(tolower(synchronicity)) %>%
      na_if("?") %>%
      str_to_sentence() %>%
      factor(levels = c("Synchronous", "Metachronous")),

    deceased = as.numeric(deceased),
    event_os = as.numeric(event_os),
    age_at_diag     = as.numeric(age_at_diag),
    age_at_met_diag = as.numeric(age_at_met_diag),

    kras_mut   = clean_binary_mut(kras_mut),
    nras_mut   = clean_binary_mut(nras_mut),
    braf_mut   = clean_binary_mut(braf_mut),
    tp53_mut   = clean_binary_mut(tp53_mut),
    pik3ca_mut = clean_binary_mut(pik3ca_mut),
    mmrd       = clean_binary_mut(mmrd),

    mmr_status = case_when(
      str_detect(str_to_lower(mmr_text), "mmrd") ~ "dMMR",
      str_detect(str_to_lower(mmr_text), "mmrp") ~ "pMMR",
      TRUE ~ NA_character_
    ) %>% factor(levels = c("pMMR", "dMMR")),

    os_time_months = as.numeric(
      difftime(if_else(deceased == 1, date_death, date_last_fup), date_diag, units = "days")
    ) / 30.4375
  ) %>%
  mutate(across(c(kras_mut, nras_mut, braf_mut, tp53_mut, pik3ca_mut, mmrd),
                ~ factor(.x, levels = c(0, 1), labels = c("Wild-type/negative", "Mutant/positive")),
                .names = "{.col}_f")) %>%
  relocate(id, hospital_number, .before = 1)

## Flag and exclude implausible values (garbled dates in source)
bad_age <- !between(df$age_at_diag, 18, 100) & !is.na(df$age_at_diag)
bad_os  <- df$os_time_months < 0 & !is.na(df$os_time_months)
df$age_at_diag[bad_age]   <- NA
df$os_time_months[bad_os] <- NA

df <- df %>%
  mutate(age_group = case_when(
    is.na(age_at_diag) ~ NA_character_,
    age_at_diag < 50   ~ "<50",
    age_at_diag >= 50  ~ ">=50"
  ) %>% factor(levels = c("<50", ">=50")))

eth_groups <- c("Asian", "Black", "White")

8 rows with implausible age at diagnosis and 3 rows with negative survival time were excluded from the relevant analyses below (likely data-entry errors in the source — worth a query if these patients matter to your final numbers).

2 Mutation and dMMR frequency by ethnicity

2.1 All stages combined

sub_eth <- df %>%
  filter(
    ethnicity_code %in% eth_groups
  ) %>%
  mutate(
    ethnicity_code = fct_drop(ethnicity_code)
  )

tbl_eth <- sub_eth %>%
  select(
    ethnicity_code,
    kras_mut_f,
    braf_mut_f,
    tp53_mut_f,
    pik3ca_mut_f,
    mmr_status,
    sidedness
  ) %>%
  tbl_summary(
    by = ethnicity_code,
    missing = "no",
    statistic = all_categorical() ~ "{n} ({p}%)",
    label = list(
      kras_mut_f ~ "KRAS mutation",
      braf_mut_f ~ "BRAF mutation",
      tp53_mut_f ~ "TP53 mutation",
      pik3ca_mut_f ~ "PIK3CA mutation",
      mmr_status ~ "MMR status",
      sidedness ~ "Tumour sidedness"
    )
  ) %>%
  add_overall(last = TRUE) %>%
  add_p() %>%
  bold_labels() %>%
  modify_caption(
    "**Table 1. Molecular characteristics by ethnicity**"
  )

tbl_eth
Table 1. Molecular characteristics by ethnicity
Characteristic Asian
N = 135
1
Black
N = 99
1
White
N = 341
1
Overall
N = 575
1
p-value2
KRAS mutation



0.025
    Wild-type/negative 77 (60%) 41 (43%) 177 (56%) 295 (55%)
    Mutant/positive 51 (40%) 55 (57%) 138 (44%) 244 (45%)
BRAF mutation



<0.001
    Wild-type/negative 126 (98%) 91 (96%) 268 (86%) 485 (91%)
    Mutant/positive 3 (2.3%) 4 (4.2%) 43 (14%) 50 (9.3%)
TP53 mutation



0.5
    Wild-type/negative 37 (30%) 29 (31%) 80 (26%) 146 (28%)
    Mutant/positive 86 (70%) 64 (69%) 226 (74%) 376 (72%)
PIK3CA mutation



0.5
    Wild-type/negative 107 (86%) 74 (80%) 252 (82%) 433 (82%)
    Mutant/positive 18 (14%) 18 (20%) 56 (18%) 92 (18%)
MMR status



>0.9
    pMMR 117 (91%) 90 (92%) 305 (91%) 512 (91%)
    dMMR 12 (9.3%) 8 (8.2%) 29 (8.7%) 49 (8.7%)
Tumour sidedness



0.021
    Bilateral/both 1 (0.8%) 0 (0%) 3 (0.9%) 4 (0.7%)
    Left 93 (70%) 56 (57%) 243 (73%) 392 (70%)
    Right 38 (29%) 42 (43%) 86 (26%) 166 (30%)
1 n (%)
2 Pearson’s Chi-squared test; Fisher’s exact test

2.2 Early-stage disease only

sub_early <- df %>%
  filter(
    ethnicity_code %in% eth_groups,
    disease_stage == "Early"
  ) %>%
  mutate(
    ethnicity_code = fct_drop(ethnicity_code)
  )

tbl_early <- sub_early %>%
  select(
    ethnicity_code,
    kras_mut_f,
    braf_mut_f,
    tp53_mut_f,
    pik3ca_mut_f,
    mmr_status
  ) %>%
  tbl_summary(
    by = ethnicity_code,
    missing = "no",
    statistic = all_categorical() ~ "{n} ({p}%)",
    label = list(
      kras_mut_f ~ "KRAS mutation",
      braf_mut_f ~ "BRAF mutation",
      tp53_mut_f ~ "TP53 mutation",
      pik3ca_mut_f ~ "PIK3CA mutation",
      mmr_status ~ "MMR status"
    )
  ) %>%
  add_p() %>%
  bold_labels() %>%
  modify_caption(
    "**Table 2. Molecular characteristics by ethnicity in early disease**"
  )

tbl_early
Table 2. Molecular characteristics by ethnicity in early disease
Characteristic Asian
N = 51
1
Black
N = 35
1
White
N = 131
1
p-value2
KRAS mutation


0.8
    Wild-type/negative 29 (60%) 19 (54%) 65 (55%)
    Mutant/positive 19 (40%) 16 (46%) 53 (45%)
BRAF mutation


0.037
    Wild-type/negative 48 (98%) 33 (94%) 99 (86%)
    Mutant/positive 1 (2.0%) 2 (5.7%) 16 (14%)
TP53 mutation


0.12
    Wild-type/negative 15 (31%) 16 (46%) 32 (27%)
    Mutant/positive 33 (69%) 19 (54%) 85 (73%)
PIK3CA mutation


0.14
    Wild-type/negative 42 (88%) 25 (71%) 98 (84%)
    Mutant/positive 6 (13%) 10 (29%) 19 (16%)
MMR status


0.3
    pMMR 45 (94%) 29 (83%) 117 (90%)
    dMMR 3 (6.3%) 6 (17%) 13 (10%)
1 n (%)
2 Pearson’s Chi-squared test; Fisher’s exact test

2.3 Metastatic disease only

sub_met <- df %>%
  filter(
    ethnicity_code %in% eth_groups,
    disease_stage == "Metastatic"
  ) %>%
  mutate(
    ethnicity_code = fct_drop(ethnicity_code)
  )

tbl_met <- sub_met %>%
  select(
    ethnicity_code,
    kras_mut_f,
    braf_mut_f,
    tp53_mut_f,
    pik3ca_mut_f,
    mmr_status
  ) %>%
  tbl_summary(
    by = ethnicity_code,
    missing = "no",
    statistic = all_categorical() ~ "{n} ({p}%)",
    label = list(
      kras_mut_f ~ "KRAS mutation",
      braf_mut_f ~ "BRAF mutation",
      tp53_mut_f ~ "TP53 mutation",
      pik3ca_mut_f ~ "PIK3CA mutation",
      mmr_status ~ "MMR status"
    )
  ) %>%
  add_p() %>%
  bold_labels() %>%
  modify_caption(
    "**Table 3. Molecular characteristics by ethnicity in metastatic disease**"
  )

tbl_met
Table 3. Molecular characteristics by ethnicity in metastatic disease
Characteristic Asian
N = 82
1
Black
N = 64
1
White
N = 209
1
p-value2
KRAS mutation


0.008
    Wild-type/negative 48 (60%) 22 (36%) 112 (57%)
    Mutant/positive 32 (40%) 39 (64%) 85 (43%)
BRAF mutation


0.003
    Wild-type/negative 78 (98%) 58 (97%) 169 (86%)
    Mutant/positive 2 (2.5%) 2 (3.3%) 27 (14%)
TP53 mutation


0.7
    Wild-type/negative 22 (29%) 13 (22%) 48 (25%)
    Mutant/positive 53 (71%) 45 (78%) 141 (75%)
PIK3CA mutation


0.6
    Wild-type/negative 65 (84%) 49 (86%) 154 (81%)
    Mutant/positive 12 (16%) 8 (14%) 37 (19%)
MMR status


0.2
    pMMR 72 (89%) 61 (97%) 188 (92%)
    dMMR 9 (11%) 2 (3.2%) 16 (7.8%)
1 n (%)
2 Pearson’s Chi-squared test; Fisher’s exact test

3 Mutation and dMMR frequency by age at diagnosis (<50 vs ≥50)

3.1 Early-stage disease only

sub_age_early <- df %>%
  filter(
    !is.na(age_group),
    disease_stage == "Early"
  )

tbl_age_early <- sub_age_early %>%
  select(
    age_group,
    kras_mut_f,
    braf_mut_f,
    tp53_mut_f,
    pik3ca_mut_f,
    mmr_status
  ) %>%
  tbl_summary(
    by = age_group,
    missing = "no",
    statistic = all_categorical() ~ "{n} ({p}%)",
    label = list(
      kras_mut_f ~ "KRAS mutation",
      braf_mut_f ~ "BRAF mutation",
      tp53_mut_f ~ "TP53 mutation",
      pik3ca_mut_f ~ "PIK3CA mutation",
      mmr_status ~ "MMR status"
    )
  ) %>%
  add_p() %>%
  bold_labels() %>%
  modify_caption(
    "**Table 4. Molecular characteristics by age group in early disease**"
  )

tbl_age_early
Table 4. Molecular characteristics by age group in early disease
Characteristic <50
N = 44
1
>=50
N = 196
1
p-value2
KRAS mutation

0.2
    Wild-type/negative 27 (64%) 97 (53%)
    Mutant/positive 15 (36%) 86 (47%)
BRAF mutation

>0.9
    Wild-type/negative 38 (90%) 164 (91%)
    Mutant/positive 4 (9.5%) 16 (8.9%)
TP53 mutation

0.3
    Wild-type/negative 10 (24%) 58 (32%)
    Mutant/positive 32 (76%) 124 (68%)
PIK3CA mutation

0.060
    Wild-type/negative 39 (93%) 147 (81%)
    Mutant/positive 3 (7.1%) 35 (19%)
MMR status

0.6
    pMMR 38 (88%) 177 (92%)
    dMMR 5 (12%) 16 (8.3%)
1 n (%)
2 Pearson’s Chi-squared test; Fisher’s exact test

3.2 Metastatic disease only

sub_age_met <- df %>%
  filter(
    !is.na(age_group),
    disease_stage == "Metastatic"
  )

tbl_age_met <- sub_age_met %>%
  select(
    age_group,
    kras_mut_f,
    braf_mut_f,
    tp53_mut_f,
    pik3ca_mut_f,
    mmr_status
  ) %>%
  tbl_summary(
    by = age_group,
    missing = "no",
    statistic = all_categorical() ~ "{n} ({p}%)",
    label = list(
      kras_mut_f ~ "KRAS mutation",
      braf_mut_f ~ "BRAF mutation",
      tp53_mut_f ~ "TP53 mutation",
      pik3ca_mut_f ~ "PIK3CA mutation",
      mmr_status ~ "MMR status"
    )
  ) %>%
  add_p() %>%
  bold_labels() %>%
  modify_caption(
    "**Table 5. Molecular characteristics by age group in metastatic disease**"
  )

tbl_age_met
Table 5. Molecular characteristics by age group in metastatic disease
Characteristic <50
N = 82
1
>=50
N = 318
1
p-value2
KRAS mutation

0.2
    Wild-type/negative 49 (60%) 157 (53%)
    Mutant/positive 32 (40%) 142 (47%)
BRAF mutation

0.2
    Wild-type/negative 70 (86%) 271 (92%)
    Mutant/positive 11 (14%) 25 (8.4%)
TP53 mutation

0.6
    Wild-type/negative 18 (23%) 75 (26%)
    Mutant/positive 60 (77%) 210 (74%)
PIK3CA mutation

0.3
    Wild-type/negative 68 (86%) 231 (80%)
    Mutant/positive 11 (14%) 56 (20%)
MMR status

0.5
    pMMR 75 (91%) 292 (94%)
    dMMR 7 (8.5%) 20 (6.4%)
1 n (%)
2 Pearson’s Chi-squared test

4 Overall survival by ethnicity

4.1 All stages combined

sub_eth_os <- df %>%
  filter(
    ethnicity_code %in% eth_groups,
    !is.na(os_time_months),
    !is.na(event_os)
  ) %>%
  mutate(
    ethnicity_code = fct_drop(ethnicity_code)
  )

fit_eth <- survfit(
  Surv(os_time_months, event_os) ~ ethnicity_code,
  data = sub_eth_os
)

ggsurvplot(
  fit_eth,
  data = sub_eth_os,
  pval = TRUE,
  risk.table = TRUE,
  conf.int = FALSE,
  xlab = "Time from diagnosis (months)",
  ylab = "Overall survival probability",
  legend.title = "Ethnicity",
  risk.table.title = "Number at risk",
  break.time.by = 12,
  ggtheme = theme_minimal(base_size = 12)
)

4.2 Early-stage disease only

sub_early_os <- df %>%
  filter(
    ethnicity_code %in% eth_groups,
    disease_stage == "Early",
    !is.na(os_time_months),
    !is.na(event_os)
  ) %>%
  mutate(
    ethnicity_code = fct_drop(ethnicity_code)
  )

fit_early_eth <- survfit(
  Surv(os_time_months, event_os) ~ ethnicity_code,
  data = sub_early_os
)

ggsurvplot(
  fit_early_eth,
  data = sub_early_os,
  pval = TRUE,
  risk.table = TRUE,
  conf.int = FALSE,
  xlab = "Time from diagnosis (months)",
  ylab = "Overall survival probability",
  legend.title = "Ethnicity",
  risk.table.title = "Number at risk",
  break.time.by = 12,
  ggtheme = theme_minimal(base_size = 12)
)

4.3 Metastatic disease only

sub_met_os <- df %>%
  filter(
    ethnicity_code %in% eth_groups,
    disease_stage == "Metastatic",
    !is.na(os_time_months),
    !is.na(event_os)
  ) %>%
  mutate(
    ethnicity_code = fct_drop(ethnicity_code)
  )

fit_met_eth <- survfit(
  Surv(os_time_months, event_os) ~ ethnicity_code,
  data = sub_met_os
)

ggsurvplot(
  fit_met_eth,
  data = sub_met_os,
  pval = TRUE,
  risk.table = TRUE,
  conf.int = FALSE,
  xlab = "Time from diagnosis (months)",
  ylab = "Overall survival probability",
  legend.title = "Ethnicity",
  risk.table.title = "Number at risk",
  break.time.by = 12,
  ggtheme = theme_minimal(base_size = 12)
)

5 Overall survival by age at diagnosis (<50 vs ≥50)

5.1 All stages combined

sub_age_os <- df %>%
  filter(
    !is.na(age_group),
    !is.na(os_time_months),
    !is.na(event_os)
  )

fit_age_all <- survfit(
  Surv(os_time_months, event_os) ~ age_group,
  data = sub_age_os
)

ggsurvplot(
  fit_age_all,
  data = sub_age_os,
  pval = TRUE,
  risk.table = TRUE,
  conf.int = FALSE,
  xlab = "Time from diagnosis (months)",
  ylab = "Overall survival probability",
  legend.title = "Age group",
  risk.table.title = "Number at risk",
  break.time.by = 12,
  ggtheme = theme_minimal(base_size = 12)
)

5.2 Early-stage disease only

sub_age_early_os <- df %>%
  filter(
    !is.na(age_group),
    disease_stage == "Early",
    !is.na(os_time_months),
    !is.na(event_os)
  )

fit_age_early <- survfit(
  Surv(os_time_months, event_os) ~ age_group,
  data = sub_age_early_os
)

ggsurvplot(
  fit_age_early,
  data = sub_age_early_os,
  pval = TRUE,
  risk.table = TRUE,
  conf.int = FALSE,
  xlab = "Time from diagnosis (months)",
  ylab = "Overall survival probability",
  legend.title = "Age group",
  risk.table.title = "Number at risk",
  break.time.by = 12,
  ggtheme = theme_minimal(base_size = 12)
)

5.3 Metastatic disease only

sub_age_met_os <- df %>%
  filter(
    !is.na(age_group),
    disease_stage == "Metastatic",
    !is.na(os_time_months),
    !is.na(event_os)
  )

fit_age_met <- survfit(
  Surv(os_time_months, event_os) ~ age_group,
  data = sub_age_met_os
)

ggsurvplot(
  fit_age_met,
  data = sub_age_met_os,
  pval = TRUE,
  risk.table = TRUE,
  conf.int = FALSE,
  xlab = "Time from diagnosis (months)",
  ylab = "Overall survival probability",
  legend.title = "Age group",
  risk.table.title = "Number at risk",
  break.time.by = 12,
  ggtheme = theme_minimal(base_size = 12)
)

6 Multivariable Cox model — metastatic cohort

Adjusted for age group, sex, ethnicity and mutation/MMR status. Reference levels: age <50, ethnicity Asian, wild-type/negative for each marker, pMMR.

sub_met_cox <- df %>%
  filter(
    ethnicity_code %in% eth_groups,
    disease_stage == "Metastatic",
    !is.na(os_time_months),
    !is.na(event_os)
  ) %>%
  mutate(
    ethnicity_code = fct_drop(
      fct_relevel(ethnicity_code, "Asian")
    )
  )

cox_met <- coxph(
  Surv(os_time_months, event_os) ~
    age_group +
    sex +
    ethnicity_code +
    kras_mut_f +
    braf_mut_f +
    tp53_mut_f +
    pik3ca_mut_f +
    mmr_status,
  data = sub_met_cox
)

tbl_cox <- tbl_regression(
  cox_met,
  exponentiate = TRUE,
  label = list(
    age_group ~ "Age group",
    sex ~ "Sex",
    ethnicity_code ~ "Ethnicity",
    kras_mut_f ~ "KRAS mutation",
    braf_mut_f ~ "BRAF mutation",
    tp53_mut_f ~ "TP53 mutation",
    pik3ca_mut_f ~ "PIK3CA mutation",
    mmr_status ~ "MMR status"
  )
) %>%
  bold_labels() %>%
  modify_caption(
    "**Table 6. Multivariable Cox regression for overall survival in metastatic disease**"
  )

tbl_cox
Table 6. Multivariable Cox regression for overall survival in metastatic disease
Characteristic HR 95% CI p-value
Age group


    <50
    >=50 0.96 0.61, 1.52 0.9
Sex


    Female
    Male 1.19 0.80, 1.76 0.4
Ethnicity


    Asian
    Black 1.13 0.60, 2.13 0.7
    White 1.00 0.60, 1.68 >0.9
KRAS mutation


    Wild-type/negative
    Mutant/positive 1.34 0.86, 2.09 0.2
BRAF mutation


    Wild-type/negative
    Mutant/positive 3.17 1.74, 5.76 <0.001
TP53 mutation


    Wild-type/negative
    Mutant/positive 1.01 0.64, 1.60 >0.9
PIK3CA mutation


    Wild-type/negative
    Mutant/positive 0.72 0.41, 1.27 0.3
MMR status


    pMMR
    dMMR 0.48 0.20, 1.18 0.11
Abbreviations: CI = Confidence Interval, HR = Hazard Ratio

7 Supporting analyses for the age-based abstract

These tables provide the source figures for the claims made in the age <50 vs 26550 conference abstract that are not already covered by the tables and survival curves above.

7.1 Stage at presentation by age group

sub_stage_age <- df %>%
  filter(!is.na(age_group), !is.na(disease_stage))

tbl_stage_age <- sub_stage_age %>%
  select(age_group, disease_stage) %>%
  tbl_summary(
    by = age_group,
    missing = "no",
    statistic = all_categorical() ~ "{n} ({p}%)",
    label = list(disease_stage ~ "Disease stage at presentation")
  ) %>%
  add_p() %>%
  bold_labels() %>%
  modify_caption(
    "**Table 7. Disease stage at presentation by age group**"
  )

tbl_stage_age
Table 7. Disease stage at presentation by age group
Characteristic <50
N = 126
1
>=50
N = 514
1
p-value2
Disease stage at presentation

0.5
    Early 44 (35%) 196 (38%)
    Metastatic 82 (65%) 318 (62%)
1 n (%)
2 Pearson’s Chi-squared test

7.2 Sidedness by age group

sub_side_age <- df %>%
  filter(!is.na(age_group), sidedness %in% c("Left", "Right"))

tbl_side_age <- sub_side_age %>%
  select(age_group, sidedness) %>%
  tbl_summary(
    by = age_group,
    missing = "no",
    statistic = all_categorical() ~ "{n} ({p}%)",
    label = list(sidedness ~ "Tumour sidedness")
  ) %>%
  add_p() %>%
  bold_labels() %>%
  modify_caption(
    "**Table 8. Tumour sidedness by age group**"
  )

tbl_side_age
Table 8. Tumour sidedness by age group
Characteristic <50
N = 124
1
>=50
N = 505
1
p-value2
Tumour sidedness

0.2
    Left 94 (76%) 356 (70%)
    Right 30 (24%) 149 (30%)
1 n (%)
2 Pearson’s Chi-squared test

7.3 Molecular testing completion by age group

tbl_testing_age <- df %>%
  filter(!is.na(age_group)) %>%
  mutate(tested = factor(!is.na(kras_mut), levels = c(FALSE, TRUE),
                          labels = c("Not tested", "Tested"))) %>%
  select(age_group, tested) %>%
  tbl_summary(
    by = age_group,
    missing = "no",
    statistic = all_categorical() ~ "{n} ({p}%)",
    label = list(tested ~ "Molecular testing completion")
  ) %>%
  bold_labels() %>%
  modify_caption(
    "**Table 9. Molecular testing completion by age group**"
  )

tbl_testing_age
Table 9. Molecular testing completion by age group
Characteristic <50
N = 126
1
>=50
N = 514
1
Molecular testing completion

    Not tested 3 (2.4%) 32 (6.2%)
    Tested 123 (98%) 482 (94%)
1 n (%)

7.4 RAS/BRAF wild-type rate by age group

sub_wt_age <- df %>%
  filter(!is.na(age_group), !is.na(kras_mut), !is.na(braf_mut)) %>%
  mutate(
    ras_braf_wt = factor(kras_mut == 0 & braf_mut == 0,
                          levels = c(FALSE, TRUE),
                          labels = c("RAS and/or BRAF mutant", "RAS/BRAF wild-type"))
  )

tbl_wt_age <- sub_wt_age %>%
  select(age_group, ras_braf_wt) %>%
  tbl_summary(
    by = age_group,
    missing = "no",
    statistic = all_categorical() ~ "{n} ({p}%)",
    label = list(ras_braf_wt ~ "RAS/BRAF status")
  ) %>%
  add_p() %>%
  bold_labels() %>%
  modify_caption(
    "**Table 10. RAS/BRAF wild-type rate by age group**"
  )

tbl_wt_age
Table 10. RAS/BRAF wild-type rate by age group
Characteristic <50
N = 123
1
>=50
N = 475
1
p-value2
RAS/BRAF status

0.2
    RAS and/or BRAF mutant 59 (48%) 256 (54%)
    RAS/BRAF wild-type 64 (52%) 219 (46%)
1 n (%)
2 Pearson’s Chi-squared test

7.5 Ethnic composition by age group

sub_eth_age <- df %>%
  filter(!is.na(age_group), ethnicity_code %in% eth_groups) %>%
  mutate(ethnicity_code = fct_drop(ethnicity_code))

tbl_eth_age <- sub_eth_age %>%
  select(age_group, ethnicity_code) %>%
  tbl_summary(
    by = age_group,
    missing = "no",
    statistic = all_categorical() ~ "{n} ({p}%)",
    label = list(ethnicity_code ~ "Ethnicity")
  ) %>%
  add_p() %>%
  bold_labels() %>%
  modify_caption(
    "**Table 11. Ethnic composition by age group**"
  )

tbl_eth_age
Table 11. Ethnic composition by age group
Characteristic <50
N = 104
1
>=50
N = 462
1
p-value2
Ethnicity

0.077
    Asian 33 (32%) 99 (21%)
    Black 17 (16%) 81 (18%)
    White 54 (52%) 282 (61%)
1 n (%)
2 Pearson’s Chi-squared test

Age distribution within each ethnic group, shown the other way round for reference (same underlying 2x3 table, same p-value):

tbl_age_by_eth <- sub_eth_age %>%
  select(ethnicity_code, age_group) %>%
  tbl_summary(
    by = ethnicity_code,
    missing = "no",
    statistic = all_categorical() ~ "{n} ({p}%)",
    label = list(age_group ~ "Age at diagnosis")
  ) %>%
  add_p() %>%
  bold_labels() %>%
  modify_caption(
    "**Table 12. Age at diagnosis by ethnicity**"
  )

tbl_age_by_eth
Table 12. Age at diagnosis by ethnicity
Characteristic Asian
N = 132
1
Black
N = 98
1
White
N = 336
1
p-value2
Age at diagnosis


0.077
    <50 33 (25%) 17 (17%) 54 (16%)
    >=50 99 (75%) 81 (83%) 282 (84%)
1 n (%)
2 Pearson’s Chi-squared test

Notes and caveats

  • NRAS is almost entirely unpopulated in this dataset — check whether it is captured elsewhere before reporting it as a null result.
  • Mutation fields containing discordant results (e.g. “0 and 1” across two tests) are coded here as positive if any test was positive; see clean_binary_mut() in the setup code if you’d prefer discordant results coded as missing instead.
  • Implausible ages and negative survival times, flagged in the Data import section above, were excluded rather than corrected — worth confirming against source records before finalising any numbers for publication.