3389 lines
91 KiB
R
Executable file
3389 lines
91 KiB
R
Executable file
# pop <- haven::read_sas(here::here("E:/rawdata/709203/Population/pop_talos.sas7bdat"))
|
|
|
|
# sst <- list.files(here::here("E:/rawdata/709203/Eksterne data"), pattern = "*.sas7bdat", full.names = TRUE) |>
|
|
# purrr::map(haven::read_sas)
|
|
|
|
|
|
|
|
#' Read all sas files in folder to list
|
|
#'
|
|
#' @param path folder path
|
|
#'
|
|
#' @return list
|
|
sas2list <- function(path) {
|
|
ls <- list.files(here::here(path), pattern = "*.sas7bdat", full.names = TRUE) |>
|
|
purrr::map(haven::read_sas)
|
|
names(ls) <- list.files(here::here(path), pattern = "*.sas7bdat") |>
|
|
gsub(".sas7bdat", "", x = _) |>
|
|
toupper()
|
|
ls
|
|
}
|
|
|
|
|
|
#' Flatten multilevel list
|
|
#'
|
|
#' @param paths character vector of folder paths
|
|
#'
|
|
#' @return flattened list
|
|
flatmultiread <- function(paths) {
|
|
paths |>
|
|
purrr::map(sas2list) |>
|
|
purrr::list_flatten()
|
|
}
|
|
|
|
# ls <- targets::tar_read(reg_data)
|
|
|
|
# Vectors are kept for compatibility. Calling functions can be done from within other functions. So much easier!
|
|
|
|
date_cutter <- function() as.Date("2023-01-01")
|
|
date.cut <- date_cutter() # The earliest date will define the overall date cut
|
|
|
|
censor_cutter <- function() 8.5
|
|
censor.cut <- censor_cutter() # years of maximum follow up, due to small numbers
|
|
|
|
vasc.diags <- c("I21", "I61", "I63", "I64", "G45", "K28")
|
|
|
|
#' Extract deaths from Dødsårsagsregiseret
|
|
#'
|
|
#' @param ls
|
|
#' @param max.date
|
|
#' @param diags.vasc
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(reg_data) |> get_deaths()
|
|
get_deaths <- function(ls, max.date = date.cut, diags.vasc = vasc.diags) {
|
|
|
|
|
|
# vasc.death.tilg <- mapply(ls$DAR_T_DODSAARSAG_2[, "C_DODTILGRUNDL_ACME"],
|
|
# FUN = function(i) { # mapply inside apply call to handle rowvise matching in in matrix
|
|
# i_3 <- substr(i, 1, 3) # Substr only the 3 first characters to match by group
|
|
# i_3 %in% diags.vasc # Rowvise matching
|
|
# }
|
|
# )
|
|
|
|
vasc.death.tilg <- ls$DAR_T_DODSAARSAG_2[["C_DODTILGRUNDL_ACME"]] |> substr(1, 3) %in% diags.vasc
|
|
|
|
|
|
vasc.death.any <- apply(mapply(ls$DAR_T_DODSAARSAG_2[, c("C_DODTILGRUNDL_ACME", "C_DOD_1A", "C_DOD_1B", "C_DOD_1C", "C_DOD_1D")],
|
|
FUN = function(i) { # mapply inside apply call to handle rowvise matching in in matrix
|
|
i_3 <- substr(i, 1, 3) # Substr only the 3 first characters to match by group
|
|
i_3 %in% diags.vasc # Rowvise matching
|
|
}
|
|
), 1, any) # Simplify to TRUE if any
|
|
#
|
|
vasc.death.other <- apply(mapply(ls$DAR_T_DODSAARSAG_2[, c("C_DOD_1A", "C_DOD_1B", "C_DOD_1C", "C_DOD_1D")],
|
|
FUN = function(i) { # mapply inside apply call to handle rowvise matching in in matrix
|
|
i_3 <- substr(i, 1, 3) # Substr only the 3 first characters to match by group
|
|
i_3 %in% diags.vasc # Rowvise matching
|
|
}
|
|
), 1, any) # Simplify to TRUE if any
|
|
|
|
diag.either <- xor(vasc.death.other, vasc.death.tilg)
|
|
diag.both <- vasc.death.other & vasc.death.tilg
|
|
|
|
vasc.death.diags <-
|
|
apply(
|
|
mapply(
|
|
ls$DAR_T_DODSAARSAG_2[, c(
|
|
"C_DODTILGRUNDL_ACME",
|
|
"C_DOD_1A",
|
|
"C_DOD_1B",
|
|
"C_DOD_1C",
|
|
"C_DOD_1D"
|
|
)],
|
|
FUN = function(i) {
|
|
# mapply inside apply call to handle rowvise matching in in matrix
|
|
substr(i, 1, 3) # Substr only the 3 first characters to match by group
|
|
}
|
|
),
|
|
1,
|
|
paste,
|
|
collapse = ","
|
|
)
|
|
|
|
deaths.vasc <-
|
|
ls$DAR_T_DODSAARSAG_2 |>
|
|
dplyr::select(K_CPR, D_STATDATO) |>
|
|
dplyr::filter(vasc.death.tilg)
|
|
|
|
df.death.all <- ls$CPR3_T_PERSON |>
|
|
dplyr::filter(C_STATUS == 90) |> # People migrating are filtered (n ~ 1)
|
|
dplyr::select(c(
|
|
"V_PNR",
|
|
"D_STATUS_HEN_START"
|
|
)) |>
|
|
dplyr::left_join(deaths.vasc, by = c("V_PNR" = "K_CPR")) |>
|
|
dplyr::mutate(vasc_death = !is.na(D_STATDATO)) |>
|
|
dplyr::transmute(
|
|
PNR = V_PNR,
|
|
death_date = D_STATUS_HEN_START,
|
|
vasc_death = vasc_death
|
|
)
|
|
|
|
df.death.all |> dplyr::filter(death_date < max.date)
|
|
}
|
|
|
|
|
|
#' Title
|
|
#'
|
|
#' @param ls
|
|
#' @param max.date
|
|
#' @param diags.vasc
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' ls <- targets::tar_read(reg_list)
|
|
get_events <- function(ls, max.date = date.cut, diags.vasc = vasc.diags) {
|
|
ident.vars <- toupper(c("_recnum", "_cpr"))
|
|
|
|
df.vasc.events.lpr <- ls$LPR_T_DIAG |>
|
|
dplyr::mutate(dia.f = substr(C_DIAG, 2, 4)) |> # Subsets only 2:4 chars, to get main group
|
|
dplyr::filter(
|
|
dia.f %in% diags.vasc
|
|
# & # Filters to only include pre-defined diagnoses
|
|
# C_DIAGTYPE=="A"
|
|
) |> # Filters to only include if main diagnosis
|
|
dplyr::select(
|
|
ends_with(ident.vars),
|
|
"C_DIAG",
|
|
"C_DIAGTYPE"
|
|
) |>
|
|
dplyr::left_join(
|
|
ls$LPR_T_ADM |> dplyr::select(
|
|
tidyselect::ends_with(ident.vars),
|
|
"D_INDDTO",
|
|
"C_INDM",
|
|
"D_UDDTO",
|
|
"C_UDM",
|
|
"C_SGH",
|
|
"C_AFD",
|
|
"C_ADIAG"
|
|
),
|
|
by = c("V_RECNUM" = "K_RECNUM")
|
|
)
|
|
|
|
## LPR-F - LPR 3
|
|
|
|
ident.vars.lpr3 <- toupper(c("cpr", "_kontakt", "DW_EK_FORLOEB"))
|
|
|
|
df.vasc.events.lpr3 <- ls$LPR_F_DIAGNOSER |>
|
|
dplyr::mutate(dia.f = substr(DIAGNOSEKODE, 2, 4)) |> # Subsets only 2:4 chars, to get main group
|
|
dplyr::filter(
|
|
dia.f %in% diags.vasc
|
|
# & # Filters to only include pre-defined diagnoses
|
|
# DIAGNOSETYPE=="A"
|
|
) |> # Filters to only include if main diagnosis
|
|
dplyr::select(
|
|
ends_with(ident.vars.lpr3),
|
|
"DIAGNOSEKODE",
|
|
"DIAGNOSETYPE"
|
|
) |>
|
|
dplyr::left_join(
|
|
ls$LPR_F_KONTAKTER |> dplyr::select(
|
|
ends_with(ident.vars.lpr3),
|
|
"DATO_START",
|
|
"DATO_SLUT",
|
|
"PRIORITET"
|
|
),
|
|
by = c("DW_EK_KONTAKT")
|
|
) |>
|
|
dplyr::mutate(PRIORITET = as.character((PRIORITET == "ATA1") + 1)) # If ATA1 then 1, if not (ATA3) then 2, cowboy coding
|
|
|
|
df.vasc.events <- dplyr::full_join(df.vasc.events.lpr, df.vasc.events.lpr3, by = c(
|
|
"V_CPR" = "CPR",
|
|
"C_DIAG" = "DIAGNOSEKODE",
|
|
"C_DIAGTYPE" = "DIAGNOSETYPE",
|
|
"D_INDDTO" = "DATO_START",
|
|
"D_UDDTO" = "DATO_SLUT",
|
|
"C_INDM" = "PRIORITET"
|
|
)) |>
|
|
dplyr::mutate(date.event = D_INDDTO)
|
|
|
|
df.vasc.events |> dplyr::filter(date.event < max.date)
|
|
}
|
|
|
|
|
|
# deaths <- targets::tar_read(df_deaths)
|
|
# events <- targets::tar_read(df_events)
|
|
# clinical <- targets::tar_read(pop_df)
|
|
|
|
|
|
#' Filter only truly considered events
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return tibble
|
|
define_events <- function(data) {
|
|
data |> dplyr::filter(
|
|
C_DIAGTYPE == "A", # Primary diagnosis
|
|
C_INDM == "1", # Acutely admitted
|
|
difftime(date.event, rdate, units = "days") > 5 # More than five (5) days after randomisation/primary stroke
|
|
)
|
|
}
|
|
|
|
#' The big merger and filter of events
|
|
#'
|
|
#' @param ls list of events, deaths and clinical
|
|
#'
|
|
#' @return tibble
|
|
#'
|
|
#' @examples
|
|
#' ls <- list(events = targets::tar_read(df_events), deaths = targets::tar_read(df_deaths), clinical = targets::tar_read(pop_df))
|
|
#' ls |> all_events()
|
|
all_events <- function(ls) {
|
|
# ls <- list(events = targets::tar_read(df_events), deaths = targets::tar_read(df_deaths), clinical = targets::tar_read(pop_df))
|
|
df.events <- dplyr::full_join(
|
|
purrr::pluck(ls, "events"),
|
|
purrr::pluck(ls, "deaths") |>
|
|
dplyr::mutate(
|
|
# These are just added to ease later filtering
|
|
C_DIAGTYPE = "A",
|
|
C_INDM = "1"
|
|
), # Ads diagtype=A, C_INDM=1 for easier sorting later
|
|
by = c(
|
|
"V_CPR" = "PNR",
|
|
"date.event" = "death_date",
|
|
"C_DIAGTYPE",
|
|
"C_INDM"
|
|
)
|
|
) |>
|
|
dplyr::full_join(dplyr::select(purrr::pluck(ls, "clinical"), c("PNR", "rdate", "enddate")),
|
|
by = c("V_CPR" = "PNR")
|
|
) |>
|
|
dplyr::arrange(date.event) |> # Sort by event date
|
|
dplyr::mutate(event.type = dplyr::if_else(
|
|
is.na(C_DIAG),
|
|
dplyr::if_else(vasc_death, "death.vasc", "death.other"),
|
|
substr(C_DIAG, 1, 4)
|
|
))
|
|
|
|
df.events |>
|
|
dplyr::group_split(V_CPR) |> # Splits by CPR
|
|
purrr::map(define_events) |> # Custom function to specify criteria for events
|
|
purrr::discard(\(x) nrow(x) == 0) |> # Discard empty elements
|
|
purrr::map(dplyr::transmute, # Saving only relevant variables
|
|
CPR = V_CPR,
|
|
date.event,
|
|
event.type)
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' ls <- list(events = targets::tar_read(df_events), deaths = targets::tar_read(df_deaths), clinical = targets::tar_read(pop_df))
|
|
#' ls |> merge_events()
|
|
merge_events <- function(data){
|
|
data|>
|
|
all_events() |>
|
|
purrr::modify(\(x) x[1, ]) |> # Select first event
|
|
purrr::list_rbind()
|
|
}
|
|
|
|
|
|
#' Count number of prescriptions of given ATC group for each CPR
|
|
#'
|
|
#' @param atc.code atc group
|
|
#' @param data dataset from LMS
|
|
#'
|
|
#' @return tibble
|
|
count_treat <- function(atc.code, data) {
|
|
data |>
|
|
dplyr::filter(grepl(atc.code, ATC)) |>
|
|
dplyr::count(CPR) |>
|
|
dplyr::filter(n > 1)
|
|
}
|
|
|
|
|
|
#' Get LMS data
|
|
#'
|
|
#' @param ls list of datasets
|
|
#' @param max.date filter date for max inclusion
|
|
#' @param atc.tbl tibble of atc codes
|
|
#'
|
|
#' @return tibble
|
|
get_lms <- function(ls, max.date, atc.tbl) {
|
|
ls$LMS_EPIKUR |>
|
|
dplyr::filter(grepl(atc.tbl[1], ATC)) |>
|
|
dplyr::left_join(ls$LMS_LAEGEMIDDELOPLYSNINGER) |>
|
|
dplyr::filter(as.Date(ACTDATE) < max.date)
|
|
}
|
|
|
|
#' Get count of treated patients from LMS data
|
|
#'
|
|
#' @param ls list of datasets
|
|
#' @param max.date filter date for max inclusion
|
|
#' @param atc.tbl tibble of atc codes
|
|
#'
|
|
#' @return tibble
|
|
get_treated <- function(ls,
|
|
max.date = date.cut,
|
|
atc.tbl = c(
|
|
atc.antidep = "N06A",
|
|
atc.ssri = "N06AB"
|
|
)) {
|
|
df <- atc.tbl |>
|
|
purrr::map(count_treat, get_lms(ls, max.date, atc.tbl)) |>
|
|
purrr::reduce(dplyr::full_join, by = "CPR")
|
|
colnames(df) <- c("CPR", paste0("n.", names(atc.tbl)))
|
|
df
|
|
}
|
|
|
|
#' BMI calc, drops
|
|
#'
|
|
#' @param w weight in kg
|
|
#' @param h height in cm
|
|
#' @param data data set
|
|
#'
|
|
#' @return tibble
|
|
bmi_calc <- function(data, drop = TRUE) {
|
|
# After inspection, both h+w are missing if any is missing
|
|
out <- data |> dplyr::mutate(reg_bmi = suppressWarnings(as.numeric(dplyr::if_else(reg_vaegt == "NA", reg_vaegt_anslaaet, reg_vaegt)) / ((as.numeric(reg_hojde) / 100)^2)))
|
|
|
|
if (drop) {
|
|
out <- out |>
|
|
dplyr::select(-dplyr::all_of(c("reg_vaegt", "reg_vaegt_anslaaet", "reg_hojde")))
|
|
}
|
|
out
|
|
}
|
|
|
|
is_equal <- function(data, test) {
|
|
data == test
|
|
}
|
|
|
|
#' Load clinical population data
|
|
#'
|
|
#' @return tibble
|
|
#' @examples
|
|
#' get_clinical() |> colnames()
|
|
#'
|
|
get_clinical <- function() {
|
|
sas2list("E:/rawdata/709203/Population")[[2]] |>
|
|
correct_na() |>
|
|
dplyr::mutate(
|
|
reg_smoker = dplyr::case_match(
|
|
reg_rygning, "1" ~ TRUE,
|
|
c("2", "3", "4") ~ FALSE,
|
|
"9" ~ NA
|
|
),
|
|
# Living alone defined as not together with somebody
|
|
reg_alone = dplyr::case_match(
|
|
reg_civil, "1" ~ FALSE,
|
|
c("2", "3") ~ TRUE,
|
|
"9" ~ NA
|
|
),
|
|
reg_more_alc = dplyr::case_match(
|
|
reg_alkohol, "1" ~ FALSE,
|
|
"2" ~ TRUE,
|
|
"9" ~ NA
|
|
),
|
|
reg_female = sex == "Kvinde",
|
|
dplyr::across(
|
|
.cols = c(
|
|
"reg_hyperten",
|
|
"reg_diabetes",
|
|
"reg_atriefli",
|
|
"reg_perifer_arteriel",
|
|
"reg_tidl_tci",
|
|
"reg_ami"
|
|
),
|
|
~ dplyr::case_match(
|
|
.x, "1" ~ TRUE,
|
|
"2" ~ FALSE,
|
|
"9" ~ NA
|
|
)
|
|
),
|
|
dplyr::across(
|
|
.cols = c(
|
|
"reg_trombolyse",
|
|
"reg_trombektomi"
|
|
),
|
|
~ dplyr::case_match(
|
|
.x, "1" ~ TRUE,
|
|
c("3","4") ~ FALSE,
|
|
"9" ~ NA
|
|
)
|
|
),
|
|
reg_any_perf = reg_trombolyse | reg_trombektomi
|
|
) |>
|
|
bmi_calc()
|
|
}
|
|
|
|
# get_clinical() |> pragmatic_imputation() |> skimr::skim()
|
|
|
|
# get_clinical <- function() {
|
|
# sas2list("E:/rawdata/709203/Population")[[2]] |>
|
|
# dplyr::mutate(
|
|
# reg_smoker = reg_rygning == 1,
|
|
# reg_cohabiting = reg_civil == 1,
|
|
# reg_more_alc = reg_alkohol == 2,
|
|
# reg_female = sex == "Kvinde",
|
|
# dplyr::across(.cols = c("reg_hyperten", "reg_diabetes", "reg_atriefli", "reg_perifer_arteriel", "reg_tidl_tci", "reg_ami", "reg_trombolyse", "reg_trombektomi"), ~ .x == 1),
|
|
# reg_any_perf = reg_trombolyse | reg_trombektomi
|
|
# ) |>
|
|
# bmi_calc()
|
|
# }
|
|
|
|
pragmatic_imputation <- function(data, vec=c("reg_smoker","reg_cohabiting","reg_more_alc","reg_hyperten", "reg_diabetes", "reg_atriefli", "reg_perifer_arteriel", "reg_tidl_tci", "reg_ami", "reg_trombolyse", "reg_trombektomi","reg_any_perf")) {
|
|
# Assumes, if not TRUE, then FALSE (gets rid of NAs)
|
|
data |> dplyr::mutate(dplyr::across(.cols = tidyselect::any_of(vec), ~dplyr::if_else(.x,TRUE,FALSE,missing = FALSE)))
|
|
}
|
|
|
|
#' Load all registry tables to list
|
|
#'
|
|
#' @return list
|
|
get_reg_ls <- function() {
|
|
flatmultiread(c("E:/rawdata/709203/Eksterne data", "E:/rawdata/709203/Grunddata"))
|
|
}
|
|
|
|
|
|
#' Definition of relevant variables from DST tables
|
|
#'
|
|
#' @return
|
|
define_dst_vars <- function() {
|
|
list(
|
|
bef = c("PNR", "FAMILIE_ID"),
|
|
faik = c("FAMILIE_ID", "FAMAEKVIVADISP_13", "FAMSOCIOGRUP_13"),
|
|
ras = c("PNR", "SOC_STATUS_KODE"),
|
|
uddf = c("PNR", "HFAUDD")
|
|
)
|
|
}
|
|
|
|
#' Simple wrapper of dplyr::select
|
|
#'
|
|
#' @param data
|
|
#' @param vars
|
|
#'
|
|
#' @return
|
|
select_vars <- function(data, vars) {
|
|
data |> dplyr::select({{ vars }})
|
|
}
|
|
|
|
#' Subset DST tables to only include relvant variables.
|
|
#'
|
|
#' @param ls List of all registry tables
|
|
#'
|
|
#' @return
|
|
get_dst_tables <- function(ls) {
|
|
dst_vars <- define_dst_vars()
|
|
dst_tbl <- toupper(names(dst_vars))
|
|
ls.all <- purrr::map(seq_along(dst_vars), function(i) {
|
|
ls.reg <- ls[grepl(paste0("^", dst_tbl[i]), names(ls))] |> purrr::map(select_vars, vars = dst_vars[[i]])
|
|
names(ls.reg) <- paste0("y", stringr::str_extract(names(ls.reg), "[0-9]{4}"))
|
|
ls.reg
|
|
})
|
|
names(ls.all) <- dst_tbl
|
|
ls.all
|
|
}
|
|
|
|
#' Wrapper to generate string matching pattern for stringr::str_detect()
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
match_str <- function(data) {
|
|
paste0("[", paste0(data, collapse = ","), "]")
|
|
}
|
|
|
|
#' Wrapper to generate string matching pattern for grepl()
|
|
#'
|
|
#' @param data character vector
|
|
#'
|
|
#' @return
|
|
match_str_grepl <- function(data) {
|
|
paste0("(", paste0(data, collapse = "|"), ")")
|
|
}
|
|
|
|
# get_dst_tables(ls)
|
|
|
|
#' Generate sequence of previous N length
|
|
#'
|
|
#' @param data numeric vector of length 1
|
|
#'
|
|
#' @return
|
|
#'
|
|
#' @examples
|
|
#' last5y(10)
|
|
#' last5y(c(10, 6, 3))
|
|
lastNy <- function(data, n = 5) {
|
|
paste0("y", seq((data - n), data) - 1)
|
|
}
|
|
|
|
#' Filter PNR (cpr) across list elements
|
|
#'
|
|
#' @param data list of tibbles to pass through
|
|
#' @param index index number (PNR/CPR)
|
|
#'
|
|
#' @return tibble
|
|
filterCPRacross <- function(data, index) {
|
|
data |>
|
|
purrr::map(function(i) {
|
|
i[i$PNR == index, ]
|
|
}) |>
|
|
purrr::list_rbind()
|
|
}
|
|
|
|
#' Summarise data from last 5 years prior to inclusion
|
|
#'
|
|
#' @param data list with
|
|
#' @param v.median variables to get median
|
|
#' @param v.latest variables to get latest
|
|
#' @param v.mean variables to get mean
|
|
#'
|
|
#' @return tibble
|
|
previousNyears <- function(data, data.clin, n.years = 5, v.median = NULL, v.latest = c("FAMSOCIOGRUP_13", "SOC_STATUS_KODE"), v.mean = c("FAMAEKVIVADISP_13")) {
|
|
df.cpryear <- data.clin |> dplyr::transmute(
|
|
CPR = PNR,
|
|
year = as.numeric(format(as.Date(rdate), "%Y"))
|
|
)
|
|
|
|
seqs <- purrr::map(df.cpryear$year, lastNy, n = n.years)
|
|
|
|
seq_along(seqs) |>
|
|
purrr::map(function(i) {
|
|
df <- data[c(seqs[[i]])] |>
|
|
filterCPRacross(index = df.cpryear$CPR[i]) |>
|
|
dplyr::group_by(PNR) |>
|
|
dplyr::summarise(
|
|
dplyr::across(tidyselect::any_of(v.latest), \(x) tail(x, n = 1), .names = "{.col}.latest"),
|
|
dplyr::across(tidyselect::any_of(v.mean), \(x) mean(x, na.rm = TRUE), .names = "{.col}.{n.years}.mean"),
|
|
dplyr::across(tidyselect::any_of(v.median), \(x) median(x, na.rm = TRUE), .names = "{.col}.{n.years}.median")
|
|
)
|
|
}) |>
|
|
purrr::list_rbind()
|
|
}
|
|
|
|
|
|
#' Extract relevant and summarised data from BAF and FAIK
|
|
#'
|
|
#' @param data list of dst data tables
|
|
#'
|
|
#' @return tibble
|
|
#'
|
|
#' @examples
|
|
#' get_reg_ls() |>
|
|
#' get_dst_tables() |>
|
|
#' get_beffaikras()
|
|
get_beffaikras <- function(data, clin.data = get_clinical()) {
|
|
data <- data[stringr::str_detect(match_str(c("BEF", "FAIK", "RAS")), names(data))] |> purrr::list_flatten()
|
|
|
|
years <- stringr::str_extract(names(data), "y[0-9]{4}")
|
|
|
|
years[duplicated(years)] |>
|
|
purrr::map(grep, years) |>
|
|
purrr::map(function(i) {
|
|
data[c(i)] |> purrr::reduce(dplyr::full_join)
|
|
}) |>
|
|
purrr::set_names(years[duplicated(years)]) |>
|
|
previousNyears(data.clin = clin.data)
|
|
|
|
## BEF
|
|
## # Befolkningsoversigt. Data skal bruges for at kunne udtrække husstandsindkomst.
|
|
## FAIK
|
|
## # Familieindkomst. Familie id skal flættes med ID fra BEF for hvert år for at tage hensyn til evt skifte i status.
|
|
## FAMAEKVIVADISP_13 er relevante variabel for ækvivaleret indkomst
|
|
## Der findes også familiesocioøkonomisk status. Gør som Sine. Be done with it!
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- read_edu_level()
|
|
#' data |> dplyr::count(ISCED)
|
|
read_edu_level <- function() {
|
|
haven::read_dta("E:/Formater/SAS formater i Danmarks Statistik/STATA_datasaet/Disced/c_audd_level_l1l4_k.dta") |>
|
|
dplyr::transmute(
|
|
HFAUDD = start,
|
|
ISCED = AUDD_LEVEL_L1L4_K
|
|
)
|
|
}
|
|
|
|
haven::read_dta(
|
|
"E:/Formater/SAS formater i Danmarks Statistik/STATA_datasaet/Disced/c_audd_level_l1l3_k.dta") |>
|
|
dplyr::count(AUDD_LEVEL_L1L3_K)
|
|
|
|
# ls <- get_reg_ls() |> get_dst_tables()
|
|
|
|
get_uddf <- function(ls) {
|
|
ls |>
|
|
purrr::pluck("UDDF") |>
|
|
purrr::pluck(1) |>
|
|
dplyr::mutate(HFAUDD = as.character(HFAUDD)) |>
|
|
dplyr::left_join(read_edu_level()) |>
|
|
dplyr::group_by(PNR) |>
|
|
dplyr::summarise(ISCED = max(ISCED), .groups = "keep") |>
|
|
dplyr::mutate(
|
|
ISCED = as.numeric(ISCED),
|
|
ISCED_lvl = dplyr::case_match(ISCED, 0:2 ~ "low",
|
|
3:4 ~ "medium",
|
|
5:9 ~ "high",
|
|
.default = NA
|
|
),
|
|
ISCED_bin = dplyr::case_match(ISCED, 0:3 ~ "low",
|
|
4:9 ~ "high",
|
|
.default = NA
|
|
)
|
|
)
|
|
}
|
|
|
|
|
|
#' Collects all relevant variables from DST tables
|
|
#'
|
|
#' @param ls list of all registry tables
|
|
#' @param df.clin clinical data set
|
|
#'
|
|
#' @return tibble
|
|
#' @examples
|
|
#' get_reg_ls() |> get_dst(df.clin = get_clinical())
|
|
get_dst <- function(ls, df.clin) {
|
|
ls_dst <- get_dst_tables(ls)
|
|
ls_dst |>
|
|
## BEFxFAIKxRAS
|
|
get_beffaikras(clin.data = df.clin) |>
|
|
dplyr::full_join(
|
|
## UDDF
|
|
get_uddf(ls_dst)
|
|
)
|
|
}
|
|
|
|
#' Eases pipe renaming of columns
|
|
#'
|
|
#' @param data tibble, list or other object, for which names() makes sense. see ?setNames
|
|
#' @param prefix prefix to add
|
|
#' @param exclude names not to modify
|
|
#' @param new.names character vector of all new names
|
|
#'
|
|
#' @return object of same class as data
|
|
#'
|
|
#' @examples
|
|
#' set_colnames(data = mtcars, prefix = "WOW", exclude = "mpg")
|
|
set_colnames <- function(data, new.names = NULL, prefix = NULL, exclude = c("PNR", "CPR"), prefix.sep = "_") {
|
|
if (is.null(new.names)) {
|
|
nms <- names(data)
|
|
} else {
|
|
nms <- new.names
|
|
}
|
|
|
|
if (is.null(prefix)) {
|
|
nms.mod <- nms
|
|
} else {
|
|
nms.mod <- paste(prefix, nms, sep = prefix.sep)
|
|
}
|
|
|
|
setNames(
|
|
object = data,
|
|
nm = dplyr::if_else(stringr::str_detect(match_str(exclude), nms),
|
|
nms,
|
|
nms.mod
|
|
)
|
|
)
|
|
}
|
|
|
|
|
|
#' Store of variable names for data sub-setting
|
|
#'
|
|
#' @return list
|
|
#' @examples
|
|
#' define_variables()
|
|
define_variables <- function() {
|
|
list(
|
|
clin = c(
|
|
"age",
|
|
"reg_female",
|
|
"nihss_0",
|
|
"reg_trombolyse",
|
|
"reg_trombektomi",
|
|
# "rtreat",
|
|
"rtreat_placebo"),
|
|
lifestyle=c(
|
|
"pase_0",
|
|
"pase_4",
|
|
"reg_alone",
|
|
"reg_bmi",
|
|
# "reg_hojde",
|
|
# "reg_vaegt_alt",
|
|
"reg_smoker",
|
|
"reg_more_alc",
|
|
"reg_hyperten",
|
|
"reg_diabetes",
|
|
"reg_tidl_tci",
|
|
"reg_atriefli",
|
|
"reg_ami",
|
|
"reg_perifer_arteriel"),
|
|
lifestyle.events=c(
|
|
"pase_0",
|
|
"pase_4",
|
|
"reg_alone",
|
|
"reg_bmi",
|
|
# "reg_hojde",
|
|
# "reg_vaegt_alt",
|
|
"reg_smoker",
|
|
"reg_more_alc",
|
|
"reg_hyperten",
|
|
"reg_diabetes",
|
|
# "reg_tidl_tci",
|
|
"reg_atriefli",
|
|
# "reg_perifer_arteriel",
|
|
"reg_ami"),
|
|
ses=c(
|
|
# "soc_status",
|
|
# "soc_status_work",
|
|
"soc_status_nowork",
|
|
# "fam_indk",
|
|
"fam_indk_hl",
|
|
# "fam_indk_high",
|
|
# "fam_indk_low",
|
|
# "edu_level",
|
|
# "edu_high",
|
|
# "edu_low",
|
|
"edu_level_hl"
|
|
),
|
|
assess.events = c(
|
|
"who_4",
|
|
"mdi_4",
|
|
"mfi_gen_4",
|
|
"mrs_4_above1",
|
|
"time",
|
|
"status",
|
|
"event.include"
|
|
),
|
|
assess.pred = c(
|
|
"who_0",
|
|
"mrs_0_above0"
|
|
),
|
|
extra = c(
|
|
"soc_status",
|
|
"pase_0",
|
|
"pase_4" ,
|
|
"event"
|
|
),
|
|
cpr = "PNR"
|
|
)
|
|
}
|
|
|
|
#' Get var names in vector from group names. Possibility to keep all vars for as log as possible. Can be supplied to `gtsummary` functions
|
|
#'
|
|
#' @param groups vector of group names. See names(define_variables()) for options
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
get_var_vec <- function(v.groups){
|
|
define_variables()[{{ v.groups }}] |> purrr::list_c()
|
|
}
|
|
|
|
#' SUbsets dataset based on variable group names as defined
|
|
#'
|
|
#' @param vector character vector of category names
|
|
#'
|
|
#' @return character vector
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_all_data_formatted) |> get_vars(c("universal", "events"))
|
|
get_vars <- function(data, vars.groups) {
|
|
data |> dplyr::select(tidyselect::all_of(get_var_vec(vars.groups)))
|
|
}
|
|
|
|
|
|
|
|
#' Collect all relevant data for the events analysis data set
|
|
#'
|
|
#' @param ls ls of tibbles
|
|
#'
|
|
#' @return tibble
|
|
#'
|
|
#' @examples
|
|
#' ls <- targets::tar_read(list_filtered)
|
|
#'
|
|
collectall <- function(ls) {
|
|
purrr::pluck(ls, "clinical") |>
|
|
dplyr::left_join(purrr::pluck(ls, "all_events") |> set_colnames(prefix = "event"), by = c("PNR" = "CPR")) |>
|
|
dplyr::left_join(purrr::pluck(ls, "dst") |> set_colnames(prefix = "dst"), by = "PNR")
|
|
}
|
|
|
|
|
|
## Formatting for analysis
|
|
|
|
#' Function to cut and group PASE data
|
|
#'
|
|
#' @param data data set including pase_0 and _4
|
|
#'
|
|
#' @return tibble
|
|
pase_cutter <- function(data, pase.rev = TRUE, drop.pase = FALSE, drop.nas=FALSE) {
|
|
data.classes <- class(data)
|
|
if ("mids" %in% data.classes) {
|
|
data <- data |> mice::complete(action = "long", include = TRUE)
|
|
}
|
|
|
|
data <- data |>
|
|
dplyr::mutate(dplyr::across(.cols = c("pase_0", "pase_4"), \(i) {
|
|
cut(x = i, breaks = quantile(pase_0, na.rm = TRUE), labels = 1:4, include.lowest = TRUE)
|
|
}, .names = "{.col}_quartile")) |>
|
|
dplyr::mutate(pase_change = factor(dplyr::case_when(
|
|
pase_0_quartile == 1 & pase_4_quartile == 1 ~ "Persistent low",
|
|
pase_0_quartile %in% 2:4 &
|
|
pase_4_quartile %in% 2:4 ~ "Persistent high",
|
|
pase_0_quartile == 1 &
|
|
pase_4_quartile %in% 2:4 ~ "Increase",
|
|
pase_0_quartile %in% 2:4 &
|
|
pase_4_quartile == 1 ~ "Decrease"
|
|
), ordered = FALSE),
|
|
pase_change=factor(pase_change,levels=c("Persistent low", "Increase", "Decrease", "Persistent high")))
|
|
|
|
|
|
if (drop.pase) {
|
|
data <- data |> dplyr::select(-tidyselect::all_of(c("pase_0_quartile", "pase_4_quartile", "pase_0", "pase_4")))
|
|
}
|
|
|
|
|
|
if (pase.rev) {
|
|
data <- data |> dplyr::mutate(
|
|
pase_change = factor(pase_change, levels = c("Persistent high", "Decrease", "Increase", "Persistent low"))
|
|
)
|
|
}
|
|
|
|
if (drop.nas) {
|
|
data <- data |>
|
|
dplyr::filter(!is.na(pase_change))
|
|
}
|
|
|
|
|
|
if ("mids" %in% data.classes) {
|
|
data |> mice::as.mids()
|
|
} else {
|
|
data
|
|
}
|
|
}
|
|
|
|
# as.Date(data$event_date.event)
|
|
define_status_time <- function(data) {
|
|
data |> dplyr::mutate(dplyr::across(c("rdate", "enddate", "event_date.event"), ~ as.Date(.x)),
|
|
time = difftime(dplyr::if_else(is.na(event_date.event), date_cutter(), event_date.event), enddate) |> lubridate::time_length("years"),
|
|
status = as.integer(!is.na(event_event.type)),
|
|
time = dplyr::if_else(time > censor_cutter(), censor_cutter(), time),
|
|
status = dplyr::if_else(time > censor_cutter(), FALSE, status),
|
|
# status= dplyr::if_else(status,1,0),
|
|
event.include = time > 0
|
|
)
|
|
}
|
|
|
|
# as.integer(c(TRUE,FALSE))
|
|
|
|
#' Grouping soc status
|
|
#'
|
|
#' @param data tibble
|
|
#'
|
|
#' @return tibble
|
|
group_soc_status <- function(data) {
|
|
data |>
|
|
dplyr::mutate(
|
|
soc_status = factor(dplyr::case_when(
|
|
soc_status < 200 ~ "work",
|
|
soc_status == 200 ~ "off",
|
|
# only ~4 in the data set off work
|
|
soc_status >= 200 ~ "outside"
|
|
)),
|
|
soc_status_work = soc_status == "work",
|
|
soc_status_nowork = !soc_status_work
|
|
)
|
|
}
|
|
|
|
#' Correction of character NA
|
|
#'
|
|
#' @param data tibble
|
|
#' @param char.missing character vector of entries to consider as NA
|
|
#'
|
|
#' @return tibble
|
|
correct_na <- function(data, char.missing = "NA") {
|
|
data |> dplyr::mutate(dplyr::across(dplyr::where(is.character), ~ dplyr::na_if(.x, char.missing)))
|
|
}
|
|
|
|
#' Formatting the complete data set
|
|
#'
|
|
#' @param data the merged raw data set
|
|
#'
|
|
#' @return tibble
|
|
#' @examples
|
|
#' ds <- targets::tar_read(df_all_data) |>
|
|
#' data_formatting() |>
|
|
#' subset_df("mdi")
|
|
#' ds |> skimr::skim()
|
|
#' ds |> View()
|
|
data_formatting <- function(data) {
|
|
to_logical <- grep(match_str_grepl(c("missings", "incompletes")), names(data))
|
|
|
|
suppressWarnings(
|
|
data |>
|
|
correct_na() |>
|
|
dplyr::mutate(dplyr::across(all_of(to_logical), ~ .x == "TRUE")) |>
|
|
dplyr::mutate(
|
|
# This uses the work-corrected score
|
|
# pase_0 = dplyr::if_else(pase_score_missings_w_0 | is.na(talos_pase10_0), NA, pase_score_sum_w_0),
|
|
# pase_4 = dplyr::if_else(pase_score_missings_w_4 | is.na(talos_pase10_4), NA, pase_score_sum_w_4),
|
|
# Below is the plain PASE scor used according to the manual used with TALOS
|
|
pase_0 = dplyr::if_else(pase_score_missings_0, NA,pase_score_sum_0),
|
|
pase_4 = dplyr::if_else(pase_score_missings_4, NA,pase_score_sum_4),
|
|
who_0 = as.numeric(talos_who07_0),
|
|
who_4 = as.numeric(talos_who07_4),
|
|
mrs_0 = factor(substr(talos_mrs01_0, 1, 1), ordered = FALSE),
|
|
mrs_0_above0 = (as.numeric(mrs_0) - 1) > 0,
|
|
mrs_4 = factor(substr(talos_mrs01_4, 1, 1), ordered = FALSE),
|
|
mrs_4_above1 = (as.numeric(mrs_4) - 1) > 1,
|
|
mdi_4 = as.numeric(talos_mdi12_4),
|
|
mfi_gen_4 = as.numeric(talos_mfi_gen_4),
|
|
nihss_0 = as.numeric(talos_nihss16_0),
|
|
soc_status = dst_SOC_STATUS_KODE.latest,
|
|
fam_indk = cut(dst_FAMAEKVIVADISP_13.5.mean,
|
|
breaks = quantile(dst_FAMAEKVIVADISP_13.5.mean, probs = seq(0, 1, 1 / 3), na.rm = TRUE),
|
|
ordered_results = FALSE,
|
|
labels = c("low", "medium", "high"),
|
|
include.lowest = TRUE
|
|
),
|
|
fam_indk_bin = cut(dst_FAMAEKVIVADISP_13.5.mean,
|
|
breaks = quantile(dst_FAMAEKVIVADISP_13.5.mean, probs = seq(0, 1, 1 / 2), na.rm = TRUE),
|
|
ordered_results = FALSE,
|
|
labels = c("low", "high"),
|
|
include.lowest = TRUE
|
|
),
|
|
fam_indk_hl=forcats::fct_rev(fam_indk),
|
|
fam_indk_high = dplyr::if_else(fam_indk_bin=="high",TRUE,FALSE),
|
|
fam_indk_low = dplyr::if_else(fam_indk_bin=="low",TRUE,FALSE),
|
|
edu_level = factor(dst_ISCED_lvl, ordered = FALSE, levels = c("low", "medium", "high")),
|
|
edu_high = dplyr::if_else(dst_ISCED_bin=="high",TRUE,FALSE),
|
|
edu_low = dplyr::if_else(dst_ISCED_lvl=="low",TRUE,FALSE),
|
|
edu_level_hl=forcats::fct_rev(edu_level),
|
|
rtreat_placebo=rtreat=="Placebo",
|
|
event = factor(event_event.type)
|
|
) |>
|
|
group_soc_status() |>
|
|
define_status_time()
|
|
)
|
|
}
|
|
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_all_data_formatted) |>
|
|
#' events_ready() |>
|
|
#' View()
|
|
events_ready <- function(data,v.groups=c("clin","lifestyle.events","ses", "assess.events")) {
|
|
data |>
|
|
get_vars(vars.groups = v.groups) |>
|
|
dplyr::filter(event.include) |>
|
|
# dplyr::filter(!is.na(pase_0),!is.na(pase_4))|>
|
|
dplyr::select(-tidyselect::all_of("event.include"))# |>
|
|
# labelling_data()
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#' @param v.groups
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
events_ready_small <- function(data,v.groups=c("clin","lifestyle.events","ses", "assess.events")) {
|
|
data |>
|
|
get_vars(vars.groups = v.groups) |>
|
|
dplyr::filter(event.include) |>
|
|
dplyr::filter(!is.na(pase_0),!is.na(pase_4))|>
|
|
dplyr::select(-tidyselect::all_of("event.include"))# |>
|
|
# labelling_data()
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param date
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_all_data_formatted)
|
|
#'
|
|
#' data |>
|
|
#' prediction_ready() |>
|
|
#' View()
|
|
prediction_ready <- function(data) {
|
|
data |>
|
|
get_vars(c("clin","lifestyle","ses", "assess.pred"))|>
|
|
dplyr::filter(!is.na(pase_0),!is.na(pase_4))#|>
|
|
# labelling_data()
|
|
}
|
|
|
|
## Data inspection and exploration
|
|
##
|
|
##
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#' @param subdf
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_all_data) |>
|
|
#' subset_df() |>
|
|
#' View()
|
|
subset_df <- function(data, subdf = "pase") {
|
|
data[grepl(paste0("^(", paste("PNR", subdf, paste0("talos_", subdf), sep = "|"), ")"), names(data))]
|
|
}
|
|
|
|
#' Imputation as a function, includes "pragmatic imputation"
|
|
#'
|
|
#' @param data
|
|
#' @param outcome.vars
|
|
#' @param ignore
|
|
#' @param pragmatic.reg
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_all_data_formatted) |> events_ready()
|
|
#' data |> labelling_data() |> fun_impute()
|
|
fun_impute <- function(data, outcome.vars = c("status", "time"), ignore = NULL,pragmatic.reg=TRUE,pase.mod=FALSE) {
|
|
# data |> mice::md.pattern()
|
|
if (pragmatic.reg){
|
|
data <- data |> pragmatic_imputation()
|
|
}
|
|
|
|
## Excluding entries with missing outcome measures
|
|
data <- data |>
|
|
dplyr::filter(!dplyr::if_any(tidyselect::all_of(c(outcome.vars)), ~ is.na(.x)))
|
|
|
|
init <- data |>
|
|
mice::mice(maxit = 0)
|
|
|
|
meth <- init$method
|
|
meth[ignore] <- ""
|
|
|
|
pred <- init$predictorMatrix
|
|
pred[, c(outcome.vars)] <- 0
|
|
|
|
# data_out <- data |> mice::futuremice(
|
|
# pred = pred,
|
|
# method = meth,
|
|
# print = FALSE,
|
|
# parallelseed = 8123,
|
|
# use.logical = FALSE,
|
|
# maxit = 20,
|
|
# m = 10
|
|
# )
|
|
|
|
data_out <- data |> mice::mice(
|
|
pred = pred,
|
|
method = meth,
|
|
print = FALSE,
|
|
seed = 8123,
|
|
maxit = 20,
|
|
m = 10
|
|
)
|
|
|
|
if (pase.mod){
|
|
data_out <- data_out |> pase_cutter_mids()
|
|
}
|
|
|
|
data_out
|
|
|
|
# lattice::densityplot(imp_data)
|
|
# Regarding EVENTS
|
|
#
|
|
# On inspection/eye-balling densityplots looks reasonable with the current settings
|
|
#
|
|
}
|
|
|
|
#' Function to cut PASE in mids object
|
|
#'
|
|
#' @param data mids object
|
|
#'
|
|
#' @return mids object
|
|
#' @export
|
|
#'
|
|
pase_cutter_mids <- function(data){
|
|
data |>
|
|
mice::complete(action = "long", include = TRUE) |>
|
|
pase_cutter(drop.pase = TRUE, drop.nas = TRUE)|>
|
|
mice::as.mids()
|
|
}
|
|
|
|
|
|
#' Completes events data set, option to impute
|
|
#'
|
|
#' @param data
|
|
#' @param impute
|
|
#'
|
|
#' @return mids or tibble
|
|
#' @examples
|
|
#' targets::tar_read(df_all_data_formatted) |> events_dataset() |>
|
|
#' targets::tar_read(df_all_data_formatted) |>
|
|
events_dataset <- function(data, impute = TRUE, uv=FALSE) {
|
|
# data <- data |> events_ready()
|
|
if (impute) {
|
|
data |>
|
|
fun_impute(ignore = c("pase_0","pase_4"),pase.mod = TRUE)
|
|
} else if (uv){
|
|
data |>
|
|
pase_cutter(drop.pase = TRUE,drop.nas = TRUE)
|
|
} else {
|
|
data |>
|
|
dplyr::select(-tidyselect::all_of("reg_bmi")) |>
|
|
pase_cutter(drop.pase = TRUE,drop.nas = TRUE)
|
|
}
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#' @param all.vars
|
|
#' @param outcome.var
|
|
#' @param use.strata
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_all_data_formatted) |> events_ready() |> subset_df("pase")
|
|
#' data <- targets::tar_read(df_event_data) |> events_dataset(impute = FALSE)
|
|
|
|
cox_regression <- function(data, all.vars = TRUE,outcome.var="pase_change",use.strata=TRUE) {
|
|
if ("mids" %in% class(data)) {
|
|
nms <- names(data$data)
|
|
} else {
|
|
nms <- names(data)
|
|
data <- data |>
|
|
labelling_data()
|
|
# BMI meassure is excluded from non-imputed dataset
|
|
# data <- data |> dplyr::select(-tidyselect::all_of(c("reg_bmi")))
|
|
}
|
|
|
|
vars <- nms[!nms %in% c("time", "status", outcome.var)]
|
|
|
|
form.prefix <- "survival::Surv(time, status) ~"
|
|
|
|
if (use.strata) {
|
|
reg.form <- glue::glue("{form.prefix} strata({outcome.var})")
|
|
} else {
|
|
reg.form <- glue::glue("{form.prefix} {outcome.var}")
|
|
}
|
|
|
|
if (all.vars) reg.form <- paste0(reg.form, " + ", paste(vars, collapse = " + "))
|
|
|
|
require(survival)
|
|
out <- with(data, survival::coxph(
|
|
as.formula(reg.form)
|
|
))
|
|
|
|
out$call$formula <- as.formula(reg.form)
|
|
|
|
out
|
|
}
|
|
|
|
#' Creates UV cox models for all variables in data set (but time and status)
|
|
#'
|
|
#' @param data
|
|
#' @param include
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
splitdf4uvcox <- function(data,include=c("pase_change","time","status")){
|
|
names(data)[!names(data)%in%include] |>
|
|
purrr::map(\(.x){
|
|
data |> dplyr::select(tidyselect::all_of(c(.x,include))) |>
|
|
cox_regression(outcome.var = .x,use.strata=FALSE,all.vars = FALSE)
|
|
|
|
})
|
|
}
|
|
|
|
#' Creates and prints UV Cox analyses
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_event_data) |> uv_cox_table()
|
|
uv_cox_table <- function(data){
|
|
data |>
|
|
events_dataset(impute = FALSE,uv = TRUE) |>
|
|
dplyr::select(pase_change,dplyr::everything()) |>
|
|
splitdf4uvcox(include=c("time","status")) |>
|
|
purrr::map(\(.x){
|
|
.x |> tbl_regression_standard()
|
|
}) |>
|
|
gtsummary::tbl_stack()
|
|
}
|
|
|
|
tbl_merged_named <- function(data){
|
|
data |> gtsummary::tbl_merge(tab_spanner = names(data))
|
|
}
|
|
|
|
tbl_regression_standard <- function(data){
|
|
data |> gtsummary::tbl_regression(exponentiate = TRUE,
|
|
add_estimate_to_reference_rows = TRUE,
|
|
show_single_row = where(is.logical),
|
|
statistics=list(gtsummary::all_continuous()~"[{conf.low};{conf.high}]",
|
|
gtsummary::all_categorical()~"[{conf.low}%;{conf.high}%]")
|
|
) |>
|
|
fix_labels()
|
|
}
|
|
|
|
#' Wrapper to print summary table with extended info
|
|
#'
|
|
#' @param data formatted and subset data set
|
|
#' @param by.var stratify by
|
|
#'
|
|
#' @return
|
|
#' @examples
|
|
#' targets::tar_read(df_pred_data)|>print_table_summary(by="reg_female")
|
|
print_table_summary <- function(data, by.var = "pase_change") {
|
|
data |>
|
|
labelling_data() |>
|
|
# pase_cutter(drop.pase = TRUE) |>
|
|
gtsummary::tbl_summary(
|
|
missing = "no",
|
|
by = tidyselect::all_of(by.var),
|
|
value = list(where(is.logical) ~ TRUE)
|
|
) |>
|
|
gtsummary::add_overall() |>
|
|
gtsummary::add_n()
|
|
}
|
|
|
|
print_table_summary_explorer <- function(data, by.var = "pase_change") {
|
|
data |>
|
|
labelling_data() |>
|
|
# pase_cutter(drop.pase = TRUE) |>
|
|
gtsummary::tbl_summary(
|
|
missing = "no",
|
|
by = tidyselect::all_of(by.var),
|
|
value = list(where(is.logical) ~ TRUE),
|
|
type = list(gtsummary::all_continuous() ~ "continuous2"),
|
|
statistic = list(gtsummary::all_continuous() ~ c(
|
|
# "{N_nonmiss} ({p_nonmiss}%)",
|
|
"{median} ({p25}, {p75})",
|
|
# "{min}, {max}",
|
|
"{mean} ({sd})"#,
|
|
# "{N_miss} ({p_miss}%)"
|
|
)#,
|
|
# gtsummary::all_categorical() ~ c(
|
|
# "{N_obs} ({p_nonmiss}%)"#,
|
|
# # "{N_miss} ({p_miss})"
|
|
# )
|
|
)
|
|
) |>
|
|
gtsummary::add_overall() |>
|
|
gtsummary::add_n() #|>
|
|
# gtsummary::add_p()
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_all_data_formatted)
|
|
#' targets::tar_read(df_all_data_formatted) |> events_tblone()
|
|
events_tblone <- function(data) {
|
|
data |>
|
|
# events_ready() |>
|
|
pase_cutter(drop.pase = TRUE) |>
|
|
dplyr::select(-tidyselect::all_of(c("status", "time"))) |>
|
|
dplyr::filter(!is.na(pase_change)) |>
|
|
print_table_summary()
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_all_data_formatted) |> View()
|
|
#' targets::tar_read(df_pred_data)|>
|
|
#' dplyr::transmute(stRoke::quantile_cut(pase_0,4,group.names = 1:4),soc_status_work,fam_indk,edu_level) |>
|
|
#' summary_tblone()
|
|
summary_tblone <- function(data,by=names(data)[1]) {
|
|
# data <- targets::tar_read(df_pred_data)
|
|
data |>
|
|
labelling_data() |>
|
|
# prediction_ready() |>
|
|
# dplyr::select(-reg_bmi) |>
|
|
# pase_cutter(drop.pase = TRUE) |>
|
|
# dplyr::filter(!is.na(pase_change)) |>
|
|
# dplyr::mutate(pase_change=forcats::fct_rev(pase_change)) |>
|
|
print_table_summary(by.var = by)
|
|
}
|
|
|
|
#
|
|
#' Summaries of DST data for PASE quartiles at 0 and 4
|
|
#'
|
|
#' @param data
|
|
#' @param vars
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_pred_data) |> sum_pase_tables()
|
|
sum_pase_tables <- function(data,vars=c("pase_0","pase_4")){
|
|
vars |> lapply(function(.x){
|
|
dplyr::tibble(stRoke::quantile_cut(data[[.x]],y=data[["pase_0"]],4,group.names = 1:4),
|
|
dplyr::select(data,soc_status_nowork,fam_indk_hl,edu_level_hl)) |>
|
|
summary_tblone()
|
|
})
|
|
}
|
|
|
|
|
|
#' Creating a truthful stratified table for predictions
|
|
#'
|
|
#' @param data data frame
|
|
#'
|
|
#' @return list
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_pred_data) |> true_pred_sum_plot()
|
|
true_pred_sum_plot <- function(data){
|
|
true_sum <- data |>
|
|
pase_cutter(drop.pase = TRUE) |>
|
|
dplyr::mutate(pase_change=forcats::fct_rev(pase_change))
|
|
|
|
list(true_sum,true_sum |> (function(.x){
|
|
split(.x,.x$pase_change %in% c("Persistent low","Increase"))
|
|
})() |> purrr::map(function(.y){.y |> dplyr::mutate(pase_change=factor(pase_change))})) |>
|
|
purrr::list_flatten() |> purrr::map(summary_tblone,by="pase_change") |>
|
|
gtsummary::tbl_merge()
|
|
}
|
|
|
|
#' Get quick summary of missing vs non-missing for each given variable
|
|
#'
|
|
#' @param data data set
|
|
#' @param var variable to summarise over
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_pred_data) |> dplyr::select(soc_status_work,fam_indk,edu_level) |> who_is_missing()
|
|
#' targets::tar_read(df_pred_data) |> who_is_missing(var="reg_bmi")
|
|
who_is_missing <- function(data, var = "edu_level") {
|
|
data |>
|
|
dplyr::mutate(log = factor(c("non-missing","missing")[is.na(data[[var]])+1])) |>
|
|
dplyr::select(log, tidyselect::everything(),-tidyselect::all_of(var)) |>
|
|
summary_tblone() #|> gtsummary::bold_p()
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_all_data_formatted)
|
|
#' targets::tar_read(df_all_data_formatted) |> View()
|
|
#' targets::tar_read(df_pred_data) |> preds_tblone()
|
|
preds_tblone <- function(data) {
|
|
data |>
|
|
labelling_data() |>
|
|
# prediction_ready() |>
|
|
# dplyr::select(-reg_bmi) |>
|
|
pase_cutter(drop.pase = TRUE) |>
|
|
dplyr::filter(!is.na(pase_change)) |>
|
|
dplyr::mutate(pase_change=forcats::fct_rev(pase_change)) |>
|
|
print_table_summary()
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#' @param b.cols
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' gt <- targets::tar_read(ls_pred_summary)[[1]]
|
|
#' gt |> add_var_groups_gt()
|
|
#'
|
|
#' # For this to work, the function would need to handle labels and levels
|
|
#' gt <- targets::tar_read(tbl_pred_summary)|> gtsummary::as_gt()
|
|
#' gt |> add_var_groups_gt()
|
|
add_var_groups_gt <- function(gt){
|
|
# gt <- sex_ls |> purrr::pluck(2) |> gtsummary::as_gt()
|
|
# gt <- fix_labels(gt)
|
|
cls <- class(gt)
|
|
|
|
b.cols <- names(gt$`_data`)
|
|
|
|
if (b.cols[[1]]!="variable"){
|
|
# Flag to indicate if format is native gt or not. Simple assumption
|
|
# class(gt) gt is not enough
|
|
labels <- gt$`_data`[[1]]
|
|
group.var <- names(gt$`_data`[[1]])
|
|
} else {
|
|
labels <- gt$`_data`[["label"]][gt$`_data`[["row_type"]]=="label"]
|
|
group.var <- gt$`_data`[["variable"]]
|
|
}
|
|
|
|
|
|
|
|
groups <- matrix(ncol=length(labels)) |>
|
|
data.frame() |>
|
|
setNames(ifelse(labels=="","unknown_var",labels)) |>
|
|
tibble::as_tibble() |> groups_in_ds(labels = TRUE)
|
|
|
|
group.labels <- names(groups) |> subset_named_labels(labels.raw = group_labels())
|
|
|
|
labels.all <- group.labels |> purrr::imap(function(.x,.y){
|
|
c(.x,groups[[.y]][["label"]])
|
|
}) |> purrr::list_c()
|
|
|
|
for (i in rev(seq_along(group.labels))){
|
|
gt <- gt |> gt::tab_row_group(label=gt::md(glue::glue("*{group.labels[[i]]}*")),
|
|
rows=which(group.var %in% groups[[names(group.labels)[[i]]]][["var"]]))
|
|
|
|
}
|
|
|
|
class(gt) <- cls
|
|
gt
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(tbl_preds_lin_imp_reg)
|
|
#' data |> fix_labels()
|
|
fix_labels <- function(data){
|
|
cls <- class(data)
|
|
data[[1]][["variable"]][data[[1]][["row_type"]]=="label"] |>
|
|
subset_named_labels(var_labels()) |>
|
|
unname() -> data[[1]][["label"]][data[[1]][["row_type"]]=="label"]
|
|
class(data) <- cls
|
|
data
|
|
}
|
|
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#' @param b.cols
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' tbl <- targets::tar_read(ls_pred_summary)[[1]]
|
|
add_var_groups_pre_calc <- function(data,b.cols){
|
|
|
|
groups <- data |> groups_in_ds()
|
|
|
|
group.labels <- names(groups) |> subset_named_labels(labels.raw = group_labels())
|
|
|
|
t0 <- data.frame(matrix(ncol=length(b.cols))) |>
|
|
setNames(b.cols) |>
|
|
tibble::tibble()
|
|
list(ext = group.labels |> purrr::imap(function(.x,.y){
|
|
t0 |> dplyr::mutate(
|
|
variable=.y,
|
|
val_label=.x,
|
|
row_type="group",
|
|
label=.x
|
|
)
|
|
}) |> dplyr::bind_rows(),
|
|
lvls = group.labels |> purrr::imap(function(.x,.y){
|
|
c(.y,groups[[.y]][["var"]])
|
|
}) |> purrr::list_c()
|
|
)
|
|
}
|
|
|
|
#' Adds variable grouping and formatting to gtsummary tables
|
|
#'
|
|
#' @param tbl
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#'
|
|
#' tbl <- targets::tar_read(tbl_pred_summary)
|
|
#' targets::tar_read(tbl_pred_summary) |> add_var_groups()
|
|
#'
|
|
add_var_groups <- function(tbl,
|
|
pre_ls=add_var_groups_pre_calc(tbl$inputs$data,
|
|
names(tbl$table_body))){
|
|
|
|
|
|
tbl |> gtsummary::modify_table_body(
|
|
~.x |> dplyr::bind_rows(pre_ls[["ext"]]) |>
|
|
dplyr::arrange(factor(variable,levels=pre_ls[["lvls"]]))
|
|
) |>
|
|
gtsummary::modify_table_styling(columns=label,
|
|
rows= row_type%in%"level",text_format = "indent2") |>
|
|
gtsummary::modify_table_styling(columns=label,rows= row_type%in%"label",text_format = "indent")|>
|
|
gtsummary::modify_table_styling(columns=label,rows= row_type%in%"group",text_format = c("italic"))
|
|
}
|
|
|
|
#' Functionalised character vector of all labels
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
var_labels <- function(){
|
|
c(
|
|
age = "Age",
|
|
reg_female = "Female sex",
|
|
reg_bmi = "Body mass index",
|
|
reg_smoker = "Current smoker",
|
|
reg_alone = "Living alone",
|
|
reg_more_alc = "High alcohol consumption",
|
|
reg_hyperten = "Hypertension",
|
|
reg_diabetes = "Diabetes",
|
|
reg_atriefli = "Atrial fibrillation",
|
|
reg_perifer_arteriel = "Peripheral arterial disease",
|
|
reg_tidl_tci = "Previous TIA",
|
|
reg_ami = "Previous MI",
|
|
reg_trombolyse = "Treated with IVT",
|
|
reg_trombektomi = "Treated with EVT",
|
|
# reg_any_perf,
|
|
# rtreat = "Study group allocation",
|
|
rtreat_placebo = "Placebo trial treatment",
|
|
pase_0 = "Pre-stroke PASE score",
|
|
pase_4 = "6 months post-stroke PASE score",
|
|
# pase_change,
|
|
nihss_0 = "Admission NIHSS",
|
|
# soc_status,
|
|
soc_status_work = "Employed",
|
|
soc_status_nowork = "Not employed",
|
|
fam_indk = "Family income group",
|
|
fam_indk_hl = "Lower family income",
|
|
fam_indk_high = "Higher family income",
|
|
fam_indk_low = "Lower family income",
|
|
edu_level = "Educational level group",
|
|
edu_level_hl = "Lower educational level",
|
|
edu_high = "Higher educational level",
|
|
edu_low = "Low educational level",
|
|
who_4 = "WHO-5 score 6 months post-stroke",
|
|
mdi_4 = "MDI score 6 months post-stroke",
|
|
mrs_4_above1 = "mRS > 1 at 6 months post-stroke",
|
|
mfi_gen_4 = "General fatigue (MFI domain) 6 months post-stroke",
|
|
time = "Time",
|
|
status = "Status",
|
|
event.include = "Include event",
|
|
who_0 = "Pre-stroke WHO-5 score",
|
|
mrs_0_above0 = "Pre-stroke mRS > 0",
|
|
pase_change = "PA change group"
|
|
)
|
|
}
|
|
|
|
|
|
group_labels <- function(data){
|
|
c("clin" = "Clinical data",
|
|
"lifestyle" = "Lifestyle and chronic diseases",
|
|
"ses" = "Socio-economic factors",
|
|
"assess.events" = "Assessments",
|
|
"assess.pred" = "Assessments",
|
|
"extra" = "extras")
|
|
}
|
|
|
|
rev_naming <- function(x){
|
|
setNames(names(x),x)
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_pred_data)
|
|
groups_in_ds <- function(data, labels=FALSE){
|
|
groups <- define_variables() |> purrr::imap(function(.x,.y){
|
|
tibble::tibble(group=.y,var=.x)
|
|
}) |>
|
|
dplyr::bind_rows()
|
|
|
|
if (labels){
|
|
matching <- subset_named_labels(names(data),
|
|
rev_naming(var_labels()))
|
|
}else {
|
|
matching <- names(data)
|
|
}
|
|
groups[match(matching,groups[["var"]]),] |>
|
|
(\(.x){
|
|
.x |> dplyr::mutate(group=factor(group,levels=unique(.x[["group"]])))
|
|
})() |>
|
|
cbind(
|
|
tibble::tibble(
|
|
label=labelling_data(data) |> labelled::var_label() |> purrr::list_c()
|
|
)
|
|
)|>
|
|
(\(.x){
|
|
split(.x,.x[["group"]])
|
|
})()
|
|
}
|
|
|
|
|
|
#' Subset labels
|
|
#'
|
|
#' @param data
|
|
#' @param labels.raw
|
|
#'
|
|
#' @return character vector
|
|
#' @export
|
|
#'
|
|
subset_named_labels <- function(data,labels.raw){
|
|
labels.raw[match(data,names(labels.raw))]
|
|
}
|
|
|
|
#' Assign labels to data.frame or tibble
|
|
#'
|
|
#' @param data
|
|
#' @param labels
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
assign_labels <- function(data,labels){
|
|
# data |> labelled::set_variable_labels(labels)
|
|
|
|
labelled::var_label(data) <- labels
|
|
|
|
data
|
|
}
|
|
|
|
#' Flexible labelling using labelled for nicer tables
|
|
#'
|
|
#' @param data data set
|
|
#'
|
|
#' @return
|
|
#' @export labelled data.frame/tibble
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_pred_data)
|
|
#' data <- data |> dplyr::mutate(test="test")
|
|
#' data |> labelling_data() |> labelled::var_label()
|
|
labelling_data <- function(data,label.list=var_labels()){
|
|
|
|
labs <- subset_named_labels(names(data),label.list)
|
|
labs[is.na(labs)] <- names(data)[is.na(labs)]
|
|
|
|
data |> assign_labels(labels = labs)
|
|
}
|
|
|
|
|
|
|
|
#' Print regression table
|
|
#'
|
|
#' @param data cox regression ready data set
|
|
#'
|
|
#' @return gtsummary tbl_regression list object
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_event_data)
|
|
#' targets::tar_read(df_event_data) |> show_table_regression(use.mice=FALSE)
|
|
show_table_regression <- function(data, use.mice=FALSE, by.var="pase_change") {
|
|
data |>
|
|
events_dataset(impute = use.mice) |>
|
|
cox_regression(all.vars = TRUE, use.strata = FALSE,outcome.var = by.var) |>
|
|
tbl_regression_standard()
|
|
# gtsummary::add_n() #|>
|
|
# gtsummary::bold_p()
|
|
}
|
|
|
|
|
|
|
|
|
|
#' Splitting df to list by PA trajectory
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_pred_data) |> pred_ls_split()
|
|
pred_ls_split <- function(data, excluded.vars = "reg_bmi"){
|
|
data |>
|
|
pase_cutter(drop.pase = FALSE) |>
|
|
dplyr::select(-tidyselect::all_of(c("pase_4","pase_0_quartile","pase_4_quartile"))) |>
|
|
dplyr::group_split(pase_split = pase_change %in% c("Increase", "Persistent low")) |>
|
|
setNames(c("drop", "hop")) |>
|
|
purrr::map2(.y = c("Decrease", "Increase"), .f = \(x, y){
|
|
x |>
|
|
dplyr::mutate(pase_bin = pase_change == y) |>
|
|
dplyr::select(-tidyselect::all_of(c(excluded.vars, c("pase_change", "pase_split")))) |>
|
|
na.omit()
|
|
})
|
|
}
|
|
|
|
#' Run regularisation steps for split data set
|
|
#'
|
|
#' @param data selected data set
|
|
#'
|
|
#' @return list
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_pred_data)
|
|
#' targets::tar_read(df_pred_data) |> pred_models()
|
|
pred_models <- function(data, excludes = "reg_bmi") {
|
|
ls <- data |>
|
|
pred_ls_split(excluded.vars = excludes) |>
|
|
purrr::map(regularisation_steps)
|
|
|
|
class(ls) <- c("regular_list", class(ls))
|
|
ls
|
|
}
|
|
|
|
cross_mean_median_exp_table <- function(data) {
|
|
nms <- paste0("v", seq_len(ncol(data)))
|
|
|
|
cross_calcs <- data |>
|
|
as.data.frame() |>
|
|
setNames(nms) |>
|
|
dplyr::rowwise() |>
|
|
dplyr::transmute(
|
|
median = median(dplyr::c_across(tidyselect::all_of(nms))),
|
|
medianOR = exp(median),
|
|
mean = mean(dplyr::c_across(tidyselect::all_of(nms))),
|
|
meanOR = exp(mean)
|
|
)
|
|
|
|
dplyr::tibble(names = rownames(data), cross_calcs) |>
|
|
dplyr::select(-tidyselect::all_of(c("mean","median")))
|
|
}
|
|
|
|
|
|
gather_coefs_step1 <- function(data) {
|
|
data |>
|
|
list3levelpluck(lvl1 = "model", lvl2 = "B") |>
|
|
purrr::map(purrr::reduce, cbind)
|
|
}
|
|
|
|
gather_coefs <- function(data) {
|
|
# imputed.list <- "mids_regular_list" %in% class(data)
|
|
|
|
if ("mids_regular_list" %in% class(data)) {
|
|
data_step1 <- data |>
|
|
purrr::map(gather_coefs_step1) |>
|
|
purrr::map(purrr::reduce, cbind)
|
|
} else if ("regular_list" %in% class(data)) {
|
|
data_step1 <- data |> gather_coefs_step1()
|
|
} else {
|
|
stop("The supplied list has to be class 'mids_regular_list' or 'regular_list'")
|
|
}
|
|
|
|
data_step1 |>
|
|
purrr::map(cross_mean_median_exp_table) |>
|
|
purrr::reduce(dplyr::full_join, by = "names", suffix = paste0("_", names(data)))
|
|
}
|
|
|
|
|
|
#' Merge and print model coefficients. Pools datafrom mids analyses.
|
|
#'
|
|
#' @param data list
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(ls_pred_models)
|
|
#' targets::tar_read(ls_pred_models) |> print_pred_coefs()
|
|
#' targets::tar_read(ls_pred_mids_reg) |> print_pred_coefs() |> add_var_groups_gt()
|
|
print_pred_coefs <- function(data) {
|
|
# data <- targets::tar_read(ls_pred_mids_reg)
|
|
# nms <- names(data)
|
|
|
|
if ("mids_regular_list" %in% class(data)) {
|
|
type.table <- "Pooled regularised models"
|
|
} else if ("regular_list" %in% class(data)) {
|
|
type.table <- "Single regularised model"
|
|
} else {
|
|
stop("The supplied list has to be class 'mids_regular_list' or 'regular_list'")
|
|
}
|
|
|
|
merged_tbl <- data |>
|
|
gather_coefs() |>
|
|
## Leaving out the intercept
|
|
(function(.x) .x[-1,])()
|
|
|
|
sel_mean_med <- colnames(merged_tbl)[!grepl(pattern = "OR",colnames(merged_tbl))][-1]
|
|
sel_or <- colnames(merged_tbl)[grepl(pattern = "OR",colnames(merged_tbl))]
|
|
|
|
news <- subset_named_labels(merged_tbl$names,var_labels())
|
|
|
|
merged_tbl <- merged_tbl |> dplyr::mutate(names=dplyr::if_else(is.na(news),names,news))
|
|
|
|
gt_merged_tbl <- merged_tbl|>
|
|
gt::gt() |>
|
|
gt::fmt_number(decimals = 5)
|
|
|
|
merged_tbl_log <- merged_tbl |> dplyr::mutate(dplyr::across(tidyselect::all_of(sel_or), ~.x!=1),
|
|
dplyr::across(tidyselect::all_of(sel_mean_med), ~.x!=0))
|
|
|
|
for (j in colnames(merged_tbl)[-1]) {
|
|
|
|
i <- merged_tbl_log[[j]]
|
|
|
|
gt_merged_tbl <- gt_merged_tbl |> gt::tab_style(style = list(
|
|
gt::cell_text(weight="bold")
|
|
),
|
|
locations = gt::cells_body(
|
|
columns=j,
|
|
rows = i
|
|
)
|
|
)}
|
|
|
|
for (i in names(data)) {
|
|
gt_merged_tbl <- gt_merged_tbl |>
|
|
gt::tab_spanner(label = i, columns = tidyselect::ends_with(i))
|
|
}
|
|
|
|
gt_merged_tbl |> gt::tab_spanner(
|
|
label = type.table,
|
|
columns = -1
|
|
)
|
|
}
|
|
|
|
#' Calculates confusionMatrix from contingency tables. Pools if object class is .
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return list
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(ls_pred_mids_reg) |> multi_table_cfm()
|
|
#' targets::tar_read(ls_pred_models) |> multi_table_cfm()
|
|
multi_table_cfm <- function(data) {
|
|
# data <- targets::tar_read(ls_pred_mids_reg)
|
|
if ("mids_regular_list" %in% class(data)) {
|
|
data <- data |> purrr::map(\(x){
|
|
x |>
|
|
# Test tables are plucked
|
|
# purrr::map(\(y) y |> purrr::pluck("model") |> purrr::pluck("cMatTest"))|>
|
|
list3levelpluck(lvl1 = "model", lvl2 = "cMatTest") |>
|
|
# All tables are add together
|
|
purrr::reduce(\(i, j) i + j)
|
|
})
|
|
} else if ("regular_list" %in% class(data)) {
|
|
data <- data |> list3levelpluck(lvl1 = "model", lvl2 = "cMatTest")
|
|
} else {
|
|
stop("The supplied list has to be class 'mids_regular_list' or 'regular_list'")
|
|
}
|
|
|
|
data |>
|
|
purrr::map(caret::confusionMatrix)
|
|
}
|
|
|
|
#' Collect and summarise auc meassures. Pools if "mids_regular_list" object
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(ls_pred_mids_reg) |> multi_auc_summary()
|
|
#' targets::tar_read(ls_pred_models) |> multi_auc_summary()
|
|
multi_auc_summary <- function(data) {
|
|
if ("mids_regular_list" %in% class(data)) {
|
|
data_step <- data |> purrr::map(\(x){
|
|
x |>
|
|
# Test tables are plucked
|
|
list3levelpluck(lvl1 = "model", lvl2 = "auc_test") |>
|
|
# All tables are add together
|
|
purrr::reduce(c)
|
|
})
|
|
} else if ("regular_list" %in% class(data)) {
|
|
data_step <- data |>
|
|
list3levelpluck(lvl1 = "model", lvl2 = "auc_test") |>
|
|
purrr::map(c)
|
|
} else {
|
|
stop("The supplied list has to be class 'mids_regular_list' or 'regular_list'")
|
|
}
|
|
|
|
data_step |>
|
|
purrr::map(summary)
|
|
}
|
|
|
|
#' Map and 2 level recursive purrr::pluck to ease regular_list subsetting
|
|
#'
|
|
#' @param data
|
|
#' @param lvl1
|
|
#' @param lvl2
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
list3levelpluck <- function(data, lvl1 = "model", lvl2 = "cMatTest") {
|
|
data |> purrr::map(\(y) y |>
|
|
purrr::pluck(lvl1) |>
|
|
purrr::pluck(lvl2))
|
|
}
|
|
|
|
#' Plot performance curve from glmnet regularisation
|
|
#'
|
|
#' @param data list of cvs.glmnet objects
|
|
#'
|
|
#' @return ggplot list object
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
plot_roc_curve <- function(data, title.text) {
|
|
ggplot2::ggplot() +
|
|
purrr::map(data, function(i) {
|
|
ggplot2::geom_step(data = i, ggplot2::aes(x = FPR, y = TPR))
|
|
}) +
|
|
ggplot2::coord_cartesian(xlim = c(0, 1), ylim = c(0, 1)) +
|
|
ggplot2::geom_abline() +
|
|
ggplot2::theme_bw() +
|
|
ggplot2::ggtitle(title.text)
|
|
}
|
|
|
|
roc_gather_step <- function(x) {
|
|
with(x, glmnet::roc.glmnet(cvs[[1]]$fit.preval, newy = y1)[match(bestL, lambdas)])
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(ls_pred_mids_reg) |> multi_roc_plot()
|
|
#' targets::tar_read(ls_pred_models) |> multi_roc_plot()
|
|
multi_roc_plot <- function(data) {
|
|
if ("mids_regular_list" %in% class(data)) {
|
|
data_step1 <- data |>
|
|
purrr::map(purrr::map, roc_gather_step) |>
|
|
purrr::map(purrr::list_flatten)
|
|
} else if ("regular_list" %in% class(data)) {
|
|
data_step1 <- data |> purrr::map(roc_gather_step)
|
|
} else {
|
|
stop("The supplied list has to be class 'mids_regular_list' or 'regular_list'")
|
|
}
|
|
|
|
data_step1 |>
|
|
purrr::map2(.y = names(data), plot_roc_curve) |>
|
|
patchwork::wrap_plots()
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param tuning.param
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
multi_tuning_gather <- function(tuning.param = "bestA", data) {
|
|
if ("mids_regular_list" %in% class(data)) {
|
|
data_step1 <- data |>
|
|
purrr::map(purrr::map, \(x) x |> purrr::pluck(tuning.param)) |>
|
|
purrr::map(purrr::reduce, c)
|
|
} else if ("regular_list" %in% class(data)) {
|
|
data_step1 <- data |>
|
|
purrr::map(purrr::pluck, tuning.param)
|
|
} else {
|
|
stop("The supplied list has to be class 'mids_regular_list' or 'regular_list'")
|
|
}
|
|
|
|
data_step1 |> purrr::map(summary)
|
|
}
|
|
|
|
|
|
#' Tidied tuning summary call
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(ls_pred_mids_reg) |> tuning_summary()
|
|
#' targets::tar_read(ls_pred_models) |> tuning_summary()
|
|
tuning_summary <- function(data) {
|
|
c(ALPHA = "bestA", LAMBDA = "bestL") |> purrr::map(\(x) x |> multi_tuning_gather(data = data))
|
|
}
|
|
|
|
#' Apply regularisation steps to MIDS object, output arranged by grouping
|
|
#'
|
|
#' @param data mids object from mice package
|
|
#'
|
|
#' @return list
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_pred_mids) |> mids_regularisation()
|
|
mids_regularisation <- function(data) {
|
|
ls <- data |>
|
|
mice::complete(action = "long") |>
|
|
dplyr::group_split(.imp) |>
|
|
purrr::modify(\(x){
|
|
x |> dplyr::select(-tidyselect::all_of(c(".imp", ".id")))
|
|
}) |>
|
|
purrr::map(pred_models)
|
|
|
|
nms <- ls |>
|
|
purrr::map(names) |>
|
|
unique() |>
|
|
purrr::reduce(c)
|
|
|
|
# As a consequence of the above code each "set" of analyses are together.
|
|
# Here the same group analyses are subset and grouped
|
|
ls_n <- purrr::map(nms, function(i) {
|
|
ls |> purrr::map(purrr::pluck, i)
|
|
}) |>
|
|
setNames(nms)
|
|
|
|
# Special class is applied to ease future handling
|
|
class(ls_n) <- c("mids_regular_list", class(ls_n))
|
|
ls_n
|
|
}
|
|
|
|
#' A collection of all the summary functions to be applied to list of
|
|
#' pred_models() output
|
|
#'
|
|
#' @param data list of data
|
|
#'
|
|
#' @return list
|
|
#' @export
|
|
#'
|
|
multi_summary <- function(data){
|
|
list( "coefTable" = print_pred_coefs(data) |>
|
|
gt::fmt_number(n_sigfig = 3) |>
|
|
fix_labels() #|> add_var_groups_gt()
|
|
,
|
|
"confusionMatrices" = multi_table_cfm(data),
|
|
"summaryAUC" = multi_auc_summary(data),
|
|
"rocPlots" = multi_roc_plot(data),
|
|
"tuningSummaries" = tuning_summary(data))
|
|
}
|
|
|
|
# funs <-list(
|
|
# "coefTable" = print_pred_coefs,
|
|
# "confusionMatrices" = multi_table_cfm,
|
|
# "summaryAUC" = multi_auc_summary,
|
|
# "rocPlots" = multi_roc_plot,
|
|
# "tuningSummaries" = tuning_summary
|
|
# )
|
|
|
|
# multi_summary <- plyr::each(
|
|
# "coefTable" = print_pred_coefs,
|
|
# "confusionMatrices" = multi_table_cfm,
|
|
# "summaryAUC" = multi_auc_summary,
|
|
# "rocPlots" = multi_roc_plot,
|
|
# "tuningSummaries" = tuning_summary
|
|
# )
|
|
|
|
#' Subset multiple elements from list
|
|
#'
|
|
#' @param data list
|
|
#' @param indices numeric or character vector
|
|
#'
|
|
#' @return list
|
|
#' @examples
|
|
#' targets::tar_read(ls_pred_summary)$confusionMatrices |> purrr::map(list_subset)
|
|
list_subset <- function(data,indices=c("overall","byClass")){
|
|
data[indices]
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(ls_pred_summary) |> print_model_resutls()
|
|
print_model_resutls <- function(data){
|
|
par(mfrow=c(1,2))
|
|
|
|
list(data$coefTable,
|
|
invisible(data$confusionMatrices |> purrr::map(\(x) x |> purrr::pluck("table"))),
|
|
data$confusionMatrices |> purrr::map(list_subset),
|
|
data$summaryAUC,
|
|
data$tuningSummaries
|
|
)
|
|
}
|
|
|
|
|
|
#' Classic logistic regression on prediction covariates
|
|
#'
|
|
#' @param data data frame
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples gtsummary list elemnt
|
|
#' targets::tar_read(df_pred_data) |> pred_log_reg()
|
|
#' targets::tar_read(df_pred_data) |> pred_ls_split()
|
|
pred_log_reg <- function(data){
|
|
data |> pred_ls_split() |>
|
|
purrr::map(\(x) {
|
|
gtsummary::tbl_regression(glm(pase_bin~.,family = binomial,data = x),
|
|
exponentiate= TRUE)#|>
|
|
# gtsummary::bold_p()
|
|
}
|
|
) |> (\(x){gtsummary::tbl_merge(tbls = x,
|
|
tab_spanner = names(x))})() }
|
|
|
|
#' Small wrapper to format CI with square brackets
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
square_ci <- function(data){
|
|
gsub("(-?\\d*\\.?\\d*)(, )(-?\\d*\\.?\\d*)",
|
|
"\\[\\1; \\3\\]",data)}
|
|
|
|
#' Apply CI formatting across gtsummary table including merged tables
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
fix_ci <- function(data){
|
|
data |> gtsummary::modify_table_body(~ .x |>
|
|
dplyr::mutate(dplyr::across(dplyr::starts_with("ci"),
|
|
function(.y){square_ci(.y)})))
|
|
}
|
|
|
|
#' Classic linear regression on 6 months PASE score. Uni and multi.
|
|
#'
|
|
#' @param data data frame
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples gtsummary list elemnt
|
|
#' data <- targets::tar_read(df_pred_data)
|
|
#' data <- targets::tar_read(df_pred_mids)
|
|
#' targets::tar_read(df_pred_data) |> pred_lin_reg()
|
|
#' targets::tar_read(df_pred_mids) |> pred_lin_reg()
|
|
pred_lin_reg <- function(data){
|
|
|
|
# list("tbl_regression-str:ref_row_text"="Reference") |>
|
|
# gtsummary::set_gtsummary_theme()
|
|
|
|
if ("mids" %in% class(data)){
|
|
cols <- names(data$data)
|
|
|
|
} else {
|
|
cols <- names(data)
|
|
data <- data |>
|
|
labelling_data()
|
|
}
|
|
|
|
vars <- cols[cols!="pase_4"]
|
|
|
|
formula_pase <- paste("pase_4",paste(vars,collapse = "+"),sep="~" )
|
|
|
|
# multi <- with(data=data,lm(pase_4~.)) |>
|
|
# gtsummary::tbl_regression(add_estimate_to_reference_rows = TRUE)|>
|
|
# gtsummary::bold_p() |> gtsummary::add_n()
|
|
|
|
if (!"mids" %in% class(data)){
|
|
ls <- list("Univariate"=data |>
|
|
gtsummary::tbl_uvregression(method=lm, show_single_row = dplyr::where(is.logical),
|
|
y=pase_4,
|
|
add_estimate_to_reference_rows = TRUE,pvalue_fun = NULL)#|> gtsummary::bold_p()
|
|
,
|
|
"Multivariate (no BMI)"=lm(pase_4~.,data=dplyr::select(data,-reg_bmi)) |>
|
|
gtsummary::tbl_regression(add_estimate_to_reference_rows = TRUE, show_single_row = dplyr::where(is.logical))|>
|
|
# gtsummary::bold_p() |>
|
|
gtsummary::add_n()
|
|
,
|
|
"Multivariate (ALL)"= lm(pase_4~.,data=data) |>
|
|
gtsummary::tbl_regression(add_estimate_to_reference_rows = TRUE, show_single_row = dplyr::where(is.logical))|>
|
|
# gtsummary::bold_p() |>
|
|
gtsummary::add_n()
|
|
)
|
|
|
|
} else {
|
|
ls <- list("Multivariate (ALL)"= suppressWarnings(mice::lm.mids(pase_4~.,data=data) |>
|
|
gtsummary::tbl_regression(add_estimate_to_reference_rows = TRUE, show_single_row = dplyr::where(is.logical))|>
|
|
# gtsummary::bold_p() |>
|
|
gtsummary::add_n()))
|
|
}
|
|
|
|
ls |> (\(x){gtsummary::tbl_merge(tbls = x,
|
|
tab_spanner = names(x))})() |>
|
|
fix_ci()
|
|
}
|
|
|
|
|
|
#' Simple standard plot
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_event_data) |> events_dataset(impute = FALSE)|> cox_regression() |> plot_survival()
|
|
plot_survival <- function(data){
|
|
data |>
|
|
ggsurvfit::survfit2() |>
|
|
ggsurvfit::ggsurvfit(linetype_aes = TRUE, size = 0.8) +
|
|
ggsurvfit::add_confidence_interval() +
|
|
ggsurvfit::add_risktable(
|
|
risktable_stats = c("n.risk", "cum.event"),
|
|
stats_label = list(cum.event = "Cumulative Observed Events",
|
|
n.risk = "Number at Risk"),
|
|
theme =
|
|
list(
|
|
ggsurvfit::theme_risktable_default(axis.text.y.size = 11,
|
|
plot.title.size = 11),
|
|
ggplot2::theme(plot.title = ggplot2::element_text(face = "bold"))
|
|
)
|
|
) +
|
|
ggplot2::scale_y_continuous(
|
|
limits = c(0, 1),
|
|
labels = scales::percent,
|
|
expand = c(0.01, 0)
|
|
) +
|
|
ggplot2::scale_x_continuous(breaks = 0:9, expand = c(0.02, 0))
|
|
}
|
|
|
|
|
|
#' Smooth tidy survfit object
|
|
#'
|
|
#' @param data survfit object
|
|
#'
|
|
#' @return tibble
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' ls <- targets::tar_read(df_event_data) |> events_dataset(impute = FALSE)|> cox_regression()
|
|
#' data <- ls |> ggsurvfit::survfit2(robust=TRUE) |>
|
|
#' ggsurvfit::tidy_survfit(type="survival") |>
|
|
#' dplyr::group_split(strata) |> purrr::pluck(1)
|
|
#'
|
|
#' ls |> ggsurvfit::survfit2(robust=TRUE) |>
|
|
#' ggsurvfit::tidy_survfit(type="survival") |>
|
|
#' dplyr::group_split(strata) |>
|
|
#' purrr::map(smooth_col)
|
|
smooth_col <- function(data, force_mono=TRUE){
|
|
smoothed <- lapply(c("estimate","conf.high","conf.low"),function(i){
|
|
# stats::predict(cobs::cobs(x = data$time,
|
|
# y = data[i],
|
|
# constraint = "decrease",
|
|
# nknots=4,
|
|
# pointwise = rbind(c(0,min(data$time),1)),
|
|
# degree = 2,)) |>
|
|
# tibble::as_tibble() |> dplyr::select(fit) |>
|
|
stats::predict(mgcv::gam(data=data,formula = as.formula(glue::glue("{i}~s(time,bs='cs')")))) |>
|
|
tibble::as_tibble()|>
|
|
setNames(glue::glue("{i}_smooth"))
|
|
}) |> purrr::list_cbind()
|
|
|
|
if (force_mono){
|
|
## Forcing starting point to be 1
|
|
smoothed[1,1] <- 1
|
|
|
|
smoothed[1] <- force_decrease(smoothed[1]) ## Only monotonize the esitimate
|
|
}
|
|
|
|
dplyr::tibble(data,
|
|
smoothed)
|
|
|
|
}
|
|
|
|
#' Forces the direction
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
force_decrease <- function(data){
|
|
data |> purrr::imap(function(.x,.n){
|
|
s <- c()
|
|
for (i in seq_along(.x)){
|
|
if (i == 1) {
|
|
s[1] <- .x[1]
|
|
} else {
|
|
if (.x[i]>s[i-1]){
|
|
s[i] <- s[i-1]
|
|
} else {
|
|
s[i] <- .x[i]
|
|
}
|
|
}
|
|
}
|
|
s
|
|
}) |>
|
|
dplyr::bind_cols()
|
|
}
|
|
|
|
|
|
#' Prepare cox regression for smooth survival plot
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_all_data_formatted) |> events_dataset(impute = FALSE)|> cox_regression() |> smooth_cox_data()
|
|
smooth_cox_data <- function(data){
|
|
data |>
|
|
ggsurvfit::survfit2(robust=TRUE) |>
|
|
ggsurvfit::tidy_survfit(type="survival") |>
|
|
dplyr::group_split(strata) |>
|
|
purrr::map(smooth_col) |>
|
|
purrr::list_rbind()
|
|
}
|
|
|
|
#' Plot smooth survival plot
|
|
#'
|
|
#' @param data df from cox regression
|
|
#'
|
|
#' @return ggplot list object
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_event_data) |> events_dataset(impute = FALSE) |> cox_regression()
|
|
#' data <- targets::tar_read(df_events_mids) |> cox_regression()
|
|
#' data |> plot_survival_smooth()
|
|
plot_survival_smooth <- function(data){
|
|
if ("mira" %in% class(data)) stop("Only plots non-imputed survival data")
|
|
|
|
n.level <- length(data$xlevels[[1]])
|
|
|
|
# data |>
|
|
# ggsurvfit::survfit2() |>
|
|
# ggsurvfit::tidy_survfit()
|
|
|
|
if (data |> ggsurvfit::survfit2() |> purrr::pluck("n") |> length() ==1 ){
|
|
ds <- data |>
|
|
ggsurvfit::survfit2() |>
|
|
ggsurvfit::tidy_survfit()
|
|
p <- ds |>
|
|
ggplot2::ggplot(ggplot2::aes(x=time, y=estimate))+
|
|
ggplot2::geom_smooth(se=TRUE, method="loess", formula = "y~x", linewidth=2, color="grey10")
|
|
# Added auto max for y axis removed again to ensure same y axis
|
|
# max_y <- max(ds$conf.high)
|
|
|
|
} else {
|
|
ds <- data |>
|
|
smooth_cox_data()
|
|
p <- ds |>
|
|
ggplot2::ggplot()+
|
|
ggplot2::geom_line(ggplot2::aes(x=time, y=estimate_smooth, color=strata, linetype=strata), linewidth=2)+
|
|
ggplot2::geom_ribbon(ggplot2::aes(x=time, ymin=conf.low_smooth,ymax=conf.high_smooth, fill=strata), alpha=.2)
|
|
# Added auto max for y axis removed again to ensure same y axis
|
|
# max_y <- max(ds$conf.high_smooth)
|
|
|
|
}
|
|
|
|
if (n.level==4){
|
|
colors <- viridisLite::turbo(n=n.level,direction = 1)[c(1,3,2,4)]
|
|
} else {
|
|
colors <- viridisLite::turbo(n=n.level,direction = 1)
|
|
}
|
|
|
|
p+
|
|
ggplot2::scale_y_continuous(limits = c(0,1.02),
|
|
breaks = seq(0,1,.25),
|
|
labels = scales::percent,
|
|
expand = c(0.01, 0)
|
|
) +
|
|
ggplot2::scale_x_continuous(breaks = 0:9, expand = c(0.02, 0))+
|
|
ggplot2::scale_fill_manual(values=colors)+
|
|
ggplot2::scale_color_manual(values=colors)+
|
|
ggplot2::theme_minimal()+
|
|
ggplot2::theme(axis.title.x = ggplot2::element_blank(),
|
|
axis.title.y = ggplot2::element_blank(),
|
|
# axis.text = ggplot2::element_blank(),
|
|
# legend.position = "none",
|
|
panel.grid.minor.y = ggplot2::element_blank(),
|
|
panel.grid.major.y = ggplot2::element_line(color="grey45",linewidth = 1))
|
|
|
|
}
|
|
|
|
# viridisLite::turbo(n=4,direction = 1)[c(1,3,2,4)]
|
|
|
|
cluster_rank <- function(data){
|
|
data |> cox_regression(outcome.var = "clust",use.strata = TRUE) |> ggsurvfit::survfit2(robust=TRUE) |>
|
|
ggsurvfit::tidy_survfit(type="survival") |>
|
|
dplyr::group_split(strata) |>
|
|
purrr::map(\(x){
|
|
min(x[["estimate"]])
|
|
}) |> purrr::list_c() |> rank() |> rev()
|
|
}
|
|
|
|
cox_relevel <- function(data){
|
|
data |> dplyr::mutate(clust=factor(clust,levels=cluster_rank(data)))
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_all_data_formatted)
|
|
complete_preds_data <- function(data){
|
|
data |>
|
|
# events_ready() |>
|
|
fun_impute(ignore = c("pase_0","pase_4"),pase.mod = FALSE) |>
|
|
mice::complete() |>
|
|
dplyr::filter((!is.na(pase_0)&!is.na(pase_4)))
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' targets::tar_read(df_all_data_formatted) |> add_kamila_cluster()
|
|
#' data_kam <- targets::tar_read(df_all_data_formatted) |> add_kamila_cluster()
|
|
#' data_kam |>print_table_summary(by.var = "kam_grp")
|
|
#' data_kam |> cox_regression(outcome.var="kam_grp")|> plot_survival_smooth()
|
|
#' data_kam |> cox_regression(outcome.var="kam_grp",use.strata = FALSE)|> gtsummary::tbl_regression(exponentiate = TRUE, add_estimate_to_reference_rows = TRUE) |> gtsummary::bold_p()
|
|
#' targets::tar_read(df_events_complete) |> kamila_cluster(n.clusters=3)
|
|
kamila_cluster <- function(data, n.clusters=3,include.out=FALSE){
|
|
# An index number could be added to later join pack. Of input a complete data set from imputation and pooling??
|
|
data_orig <- data
|
|
|
|
|
|
if (!include.out){
|
|
data <- data |>
|
|
dplyr::select(-tidyselect::one_of(c("time","status")))
|
|
}
|
|
|
|
|
|
catInd <- data |> lapply(\(x) is.character(x)|is.logical(x)) |> purrr::list_c()
|
|
conInd <- data |> lapply(\(x) is.numeric(x)|is.integer(x)) |> purrr::list_c()
|
|
|
|
catVars <- data[,catInd]
|
|
catVars <- catVars |> lapply(factor) |> dplyr::bind_cols() |> as.data.frame()
|
|
conVars <- data[,conInd] |> scale()|> as.data.frame()
|
|
|
|
if (is.null(n.clusters)){
|
|
out <- kamila::kamila(conVar = conVars, catFactor = catVars, numClust = 2:7, numInit = 10,
|
|
calcNumClust = "ps"
|
|
)
|
|
}else {
|
|
out <- kamila::kamila(conVar = conVars, catFactor = catVars, numClust = n.clusters, numInit = 10)
|
|
}
|
|
|
|
ls <- list("out"=out,"data_orig"=data_orig)
|
|
|
|
class(ls) <- c("kamila_cluster",class(ls))
|
|
|
|
ls
|
|
|
|
}
|
|
|
|
|
|
#' VarSelLCM wrapper
|
|
#'
|
|
#' @param data complete dataset with no missings
|
|
#' @param n.clusters number of clusters (if length 1, n is fixed, in n>1 given clusters are tested)
|
|
#' @param include.out flag to include outcome variables or not
|
|
#' @param memb.out output data frame with final membership or not (then outputs standard model output)
|
|
#'
|
|
#' @return list with VarSelLCM output and original dataset with cluster appended
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_events_complete)
|
|
#' data |> lcm_cluster()
|
|
lcm_cluster <- function(data, n.clusters=3, include.out=FALSE, var.sel=FALSE){
|
|
data_orig <- data
|
|
|
|
if (!include.out){
|
|
data <- data |>
|
|
dplyr::select(-c("time", "status"))
|
|
}
|
|
|
|
|
|
set.seed(5432)
|
|
|
|
out <- data |>
|
|
dplyr::mutate(dplyr::across(where(is.logical)|where(is.character),~factor(.x))) |>
|
|
as.data.frame() |>
|
|
VarSelLCM::VarSelCluster(
|
|
gvals=n.clusters,
|
|
crit.varsel="BIC",
|
|
vbleSelec = var.sel,
|
|
nbcores = round(parallel::detectCores()*.8)
|
|
)
|
|
|
|
ls <- list("out"=out,"data_orig"=data_orig)
|
|
|
|
class(ls) <- c("lcm_cluster",class(ls))
|
|
|
|
ls
|
|
|
|
}
|
|
|
|
#' Kmeans clustering
|
|
#'
|
|
#' @param data
|
|
#' @param n.clusters
|
|
#' @param include.out
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_events_complete)
|
|
#' data |> kmeans_cluster()
|
|
#' data |> kmeans_cluster(n.clusters=3)
|
|
kmeans_cluster <- function(data, n.clusters=3, include.out=FALSE, memb.out=TRUE){
|
|
data_orig <- data
|
|
|
|
if (!include.out){
|
|
data <- data |>
|
|
dplyr::select(!tidyselect::one_of(c("time", "status")))
|
|
}
|
|
|
|
out <- data |>
|
|
dplyr::mutate(dplyr::across(where(is.double),~scale(.x)),
|
|
dplyr::across(where(is.logical)|where(is.character),~factor(.x)),
|
|
dplyr::across(where(is.factor),~as.numeric(.x))) |>
|
|
stats::kmeans(
|
|
centers=n.clusters
|
|
)
|
|
|
|
ls <- list("out"=out,"data_orig"=data_orig)
|
|
|
|
class(ls) <- c("kmeans_cluster",class(ls))
|
|
|
|
ls
|
|
|
|
}
|
|
|
|
#' dbscan clustering
|
|
#'
|
|
#' @param data
|
|
#' @param n.clusters
|
|
#' @param include.out
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_events_complete)
|
|
#' data |> dbscan_cluster()
|
|
#' data |> dbscan_cluster(n.clusters=3)
|
|
dbscan_cluster <- function(data, n.clusters=3, include.out=FALSE, memb.out=TRUE){
|
|
data_orig <- data
|
|
|
|
if (!include.out){
|
|
data <- data |>
|
|
dplyr::select(!tidyselect::one_of(c("time", "status")))
|
|
}
|
|
|
|
data <- data |> na.omit() |> dplyr::mutate(rtreat=rtreat!="Placebo",
|
|
dplyr::across(dplyr::everything(), as.numeric))
|
|
|
|
|
|
## This plot indicates that eps should be set around 60, but at this value everything is one cluster.
|
|
dbscan::kNNdistplot(data,k = 5)
|
|
|
|
## Performing hierachical clustering, it is clear, that the algorithm is not able to seperate clusters.
|
|
hds <- dbscan::hdbscan(data,minPts = 5)
|
|
|
|
plot(hds,show_flat = TRUE)
|
|
|
|
## Clustering with set eps value and minPts
|
|
ds <- dbscan::dbscan(data,eps = 25,minPts = 2)
|
|
|
|
ds[["cluster"]]
|
|
|
|
## dbscan is not an interesting approach, apparently
|
|
|
|
#
|
|
#
|
|
#
|
|
#
|
|
# out <- data |>
|
|
# dplyr::mutate(dplyr::across(where(is.double),~scale(.x)),
|
|
# dplyr::across(where(is.logical)|where(is.character),~factor(.x)),
|
|
# dplyr::across(where(is.factor),~as.numeric(.x))) |>
|
|
# stats::kmeans(
|
|
# centers=n.clusters
|
|
# )
|
|
#
|
|
# ls <- list("out"=out,"data_orig"=data_orig)
|
|
#
|
|
# class(ls) <- c("kmeans_cluster",class(ls))
|
|
#
|
|
# ls
|
|
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param ls
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_events_complete)
|
|
#' ls <- data |> lcm_cluster()
|
|
#' ls |> final_membership()
|
|
final_membership <- function(ls){
|
|
cls <- class(ls)
|
|
if ("kamila_cluster" %in% cls) {
|
|
|
|
tibble::tibble(clust=factor(ls$out$finalMemb),
|
|
ls$data_orig)
|
|
|
|
} else if ("lcm_cluster" %in% cls) {
|
|
|
|
tibble::tibble(clust=factor(ls$out@partitions@zMAP),
|
|
ls$data_orig)
|
|
|
|
} else if ("kmeans_cluster" %in% cls) {
|
|
|
|
tibble::tibble(clust=factor(ls$out$cluster),
|
|
ls$data_orig)
|
|
|
|
} else stop("Class not recognised")
|
|
|
|
}
|
|
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_events_complete)
|
|
#' data |> get_clusters()
|
|
#' data |> get_clusters(n.cl=2:7)
|
|
get_clusters <- function(data,n.cl=4,rm.out=TRUE){
|
|
set.seed(1123)
|
|
|
|
if (length(n.cl)>1){
|
|
list(
|
|
# "kmeans"=data |> kmeans_cluster(n.clusters = n.cl,include.out = !rm.out),
|
|
"lcm"= data |> lcm_cluster(n.clusters = n.cl,include.out = !rm.out),
|
|
"kamila"=data |> kamila_cluster(n.clusters = n.cl,include.out = !rm.out)
|
|
)
|
|
} else {
|
|
list(
|
|
"kmeans"=data |> kmeans_cluster(n.clusters = n.cl,include.out = !rm.out),
|
|
"lcm"= data |> lcm_cluster(n.clusters = n.cl,include.out = !rm.out),
|
|
"kamila"=data |> kamila_cluster(n.clusters = n.cl,include.out = !rm.out)
|
|
)
|
|
}
|
|
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(list_pred_clusters)
|
|
#' data |> final_clusters()
|
|
final_clusters <- function(data,new.levels=NULL){
|
|
out <- data |> lapply(final_membership) |> lapply(labelling_data)
|
|
|
|
if (is.null(new.levels)){
|
|
out
|
|
} else {
|
|
out |>
|
|
purrr::map2(relevels,function(x,y){
|
|
# x$clust <- factor(factor(x$clust,levels=y),labels=1:4)
|
|
x$clust <- factor(x$clust,levels=y)
|
|
x #|>
|
|
# dplyr::filter(clust %in% range(as.numeric(clust))) |>
|
|
# dplyr::mutate(clust=factor(clust))
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#' @param by
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
merged_summary_tbl <- function(data,by="clust"){
|
|
data |>
|
|
purrr::map(function(x){
|
|
x |>
|
|
# print_table_summary(by.var = by)
|
|
gtsummary::tbl_summary(by=by) #|>
|
|
# gtsummary::add_p() |> gtsummary::bold_p()
|
|
}
|
|
) |> (\(x){
|
|
x |> gtsummary::tbl_merge(tab_spanner = names(x))
|
|
})()
|
|
}
|
|
|
|
#' Easy cox regression tbl for uniform results
|
|
#'
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
cox2tbl <- function(data,by="clust",all.vars=FALSE){
|
|
data|> cox_regression(outcome.var = by,use.strata = FALSE,all.vars = all.vars) |>
|
|
gtsummary::tbl_regression(exponentiate =TRUE,
|
|
statistics=list(gtsummary::all_continuous()~"[{conf.low};{conf.high}]",
|
|
gtsummary::all_categorical()~"[{conf.low}%;{conf.high}%]")) |>
|
|
gtsummary::bold_p()
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#' @param by
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
merged_cox_reg_tbl <- function(data,by="clust"){
|
|
data |>
|
|
purrr::map(function(x){
|
|
x |> cox2tbl(by=by)
|
|
}
|
|
) |> (\(x){
|
|
x |> gtsummary::tbl_merge(tab_spanner = names(x))
|
|
})()
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#' @param by
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
wrapped_surv_plot <- function(data,by="clust"){
|
|
data |>
|
|
purrr::map(function(x){
|
|
x |> cox_regression(outcome.var = by,use.strata = TRUE,all.vars = FALSE) |>
|
|
plot_survival_smooth()+ggplot2::labs(color="Cluster",fill="Cluster",linetype="Cluster")
|
|
}
|
|
) |> (\(x){
|
|
x |> patchwork::wrap_plots(ncol=1) + patchwork::plot_annotation(tag_levels = list(names(x)))
|
|
})()
|
|
}
|
|
|
|
|
|
# Ranking by most events
|
|
relevel_by_rank <- function(data){
|
|
## Assigning clusters to each dataset
|
|
data <- targets::tar_read(list_pred_clusters) |>
|
|
final_clusters(new.levels = NULL)
|
|
|
|
## Calculating cox regressions and ranking by the final point on the survival plot
|
|
relevels <- data |>
|
|
purrr::map(function(x){
|
|
x |> cox_regression(outcome.var = "clust",use.strata = TRUE,all.vars = FALSE) |>
|
|
ggsurvfit::survfit2() |>
|
|
ggsurvfit::tidy_survfit() |>
|
|
(\(x){
|
|
split(x,x[["strata"]]) |>
|
|
purrr::map(function(.y){
|
|
.y[["estimate"]][nrow(.y)]
|
|
})
|
|
})() |> purrr::reduce(c) |> rank()
|
|
}
|
|
)
|
|
|
|
#3 Assigning the new, ranked levels
|
|
targets::tar_read(list_pred_clusters) |>
|
|
final_clusters(new.levels = relevels)
|
|
}
|
|
|
|
## TODO
|
|
## Verify definitions
|
|
## Do remaining documentation of functions
|
|
##
|
|
##
|
|
## How does elastic net work with imputed dataset?
|
|
## Functionalise to allow for imputed and non-imputed (both analyses) - in both cases with and without BMI - include department of inclusion to investigate reason of missing BMI data
|
|
##
|
|
## tidymodels does not allow pmm in mice. Thy're out!
|
|
|
|
##
|
|
|
|
missing_stats <- function(index,data,glue.mask= "{N_miss} ({round(p_miss*100,dec)}%)",dec){
|
|
index.var <- unique(data$table_body$variable)[index]
|
|
|
|
table_body <- data$table_body |>
|
|
dplyr::filter(variable == index.var)
|
|
|
|
if ("missing" %in% table_body$row_type){
|
|
f1 <- grep("stat_\\d+",names(table_body))
|
|
|
|
## This approach
|
|
masks <- data$meta_data$df_stats |>
|
|
purrr::pluck(index) |>
|
|
dplyr::filter(!duplicated(col_name)) |>
|
|
dplyr::arrange(col_name) |>
|
|
dplyr::mutate(mask=glue::glue(glue.mask))|>
|
|
dplyr::pull(mask)
|
|
|
|
table_body[table_body$row_type=="missing",f1] <- masks |>
|
|
as.matrix() |>
|
|
t() |>
|
|
tibble::as_tibble(.name_repair = "unique_quiet")
|
|
|
|
}
|
|
table_body
|
|
}
|
|
|
|
missing_stats_steps <- function(body,ls,glue.mask,dec){
|
|
seq_along(unique(body$variable)) |>
|
|
purrr::map(function(.x) {
|
|
missing_stats(index=.x,data=ls,glue.mask = glue.mask,dec=dec)
|
|
}) |>
|
|
dplyr::bind_rows()
|
|
}
|
|
|
|
add_missing_stats <- function(data,glue.mask= "{N_miss} ({round(p_miss*100,dec)}%)",dec=1){
|
|
data |>
|
|
gtsummary::modify_table_body(
|
|
~ .x |> missing_stats_steps(ls=data,glue.mask = glue.mask,dec=dec)
|
|
)
|
|
}
|
|
|
|
variable_masks <- function(index, data, cut.off,glue.mask= "<{n} (<{p}%)",dec=dec) {
|
|
index.var <- unique(data$table_body$variable)[index]
|
|
|
|
table_body <- data$table_body |>
|
|
dplyr::filter(variable == index.var)
|
|
|
|
|
|
## Filtering bu two different approaches
|
|
if (table_body$var_type[1] %in% c("dichotomous", "categorical")) {
|
|
if (any(grepl("^stat_[1-9]|[1-9]\\d",names(table_body)))){
|
|
masked <- micro_n_masks(
|
|
## Handling nominal/binary
|
|
|
|
body = table_body |>
|
|
dplyr::filter(row_type!="missing"),
|
|
n.all = data$meta_data$df_stats |>
|
|
purrr::pluck(index) |>
|
|
dplyr::select(n),
|
|
N.all=data$meta_data$df_stats |>
|
|
purrr::pluck(index) |>
|
|
dplyr::select(N),
|
|
cut.off=cut.off,
|
|
glue.mask = glue.mask
|
|
)
|
|
} else {
|
|
masked <- table_body |> dplyr::filter(row_type!="missing")
|
|
}
|
|
|
|
|
|
if ("stat_0" %in% names(masked)){
|
|
body <- masked
|
|
tb <- body |> dplyr::filter(row_type!="missing",!is.na(stat_0))
|
|
f1 <- grep("^stat_0", names(tb))
|
|
|
|
meta.index <- data$meta_data$df_stats |>
|
|
purrr::pluck(index)
|
|
|
|
if ("by" %in% names(meta.index)) {
|
|
meta.index <- meta.index |> dplyr::filter(is.na(by))
|
|
}
|
|
|
|
masked <- masking(body=body,
|
|
tb=tb,
|
|
f1=f1,
|
|
ns = meta.index |>
|
|
dplyr::select(n)|>
|
|
dplyr::slice(seq_len(length(f1) * nrow(tb))) |>
|
|
unlist(use.names = FALSE) |>
|
|
matrix(ncol = length(f1), byrow = TRUE) |>
|
|
tibble::as_tibble(.name_repair = "unique_quiet"),
|
|
Ns= meta.index |>
|
|
dplyr::select(N_obs) |>
|
|
dplyr::slice(1)|>
|
|
unlist(use.names = FALSE),
|
|
cut.off=cut.off,
|
|
glue.mask=glue.mask,
|
|
dec=dec)
|
|
}
|
|
|
|
out <- rbind(
|
|
masked,
|
|
table_body |> dplyr::filter(row_type=="missing"))
|
|
|
|
} else {
|
|
out <- table_body
|
|
}
|
|
|
|
if ("missing" %in% out$row_type) {
|
|
## Handling missings n is N_miss, and N is N_obs
|
|
|
|
missings <- micro_n_masks(
|
|
body = out |>
|
|
dplyr::filter(row_type == "missing"),
|
|
n.all = data$meta_data$df_stats |>
|
|
purrr::pluck(index) |>
|
|
dplyr::select(N_miss),
|
|
N.all=data$meta_data$df_stats |>
|
|
purrr::pluck(index) |>
|
|
dplyr::select(N_obs),
|
|
cut.off=cut.off,
|
|
glue.mask=glue.mask
|
|
)
|
|
|
|
if ("stat_0" %in% names(missings)) {
|
|
## Handling overall column
|
|
body <- missings
|
|
tb <- body |> dplyr::filter(row_type=="missing")
|
|
f1 <- grep("^stat_0", names(tb))
|
|
|
|
masking(body=body,
|
|
tb=tb,
|
|
f1=f1,
|
|
ns = data$meta_data$df_stats |>
|
|
purrr::pluck(index) |>
|
|
dplyr::filter(is.na(by)) |>
|
|
dplyr::select(N_miss) |>
|
|
dplyr::slice(1) |>
|
|
unlist(use.names = FALSE) |>
|
|
matrix(ncol = length(f1), byrow = TRUE) |>
|
|
tibble::as_tibble(.name_repair = "unique_quiet"),
|
|
Ns=data$meta_data$df_stats |>
|
|
purrr::pluck(index) |>
|
|
dplyr::filter(is.na(by)) |>
|
|
dplyr::select(N_obs) |>
|
|
dplyr::slice(1) |>
|
|
unlist(use.names = FALSE),
|
|
cut.off=cut.off*2,
|
|
glue.mask=glue.mask,
|
|
dec=dec)
|
|
|
|
}
|
|
|
|
out <- rbind(
|
|
out |>
|
|
dplyr::filter(row_type != "missing"),
|
|
missings)
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
out
|
|
}
|
|
|
|
micro_n_masks <- function(body, n.all, N.all, cut.off, glue.mask,dec=1) {
|
|
if (nrow(body) > 1) {
|
|
# First row removed in case of categorical
|
|
# Possibly change to include in filter, to have whole df, or just rbind in the end
|
|
tb <- body |> dplyr::filter(row_type!="label")
|
|
} else { # last option is "dichotomous"
|
|
tb <- body
|
|
}
|
|
|
|
## Supports any number of stat columns (overkill!)
|
|
f1 <- grep("^stat_[1-9]|[1-9]\\d", names(tb))
|
|
|
|
masking(body=body,
|
|
tb=tb,
|
|
f1 = f1,
|
|
ns=n.all |>
|
|
dplyr::slice(seq_len(length(f1) * nrow(tb))) |>
|
|
unlist(use.names = FALSE) |>
|
|
matrix(ncol = length(f1), byrow = TRUE) |>
|
|
tibble::as_tibble(.name_repair = "unique_quiet"),
|
|
Ns=N.all |>
|
|
dplyr::slice(seq_len(length(f1))) |>
|
|
unlist(use.names = FALSE),
|
|
cut.off=cut.off,
|
|
glue.mask=glue.mask,
|
|
dec=dec
|
|
)
|
|
|
|
}
|
|
|
|
|
|
#' Title
|
|
#'
|
|
#' @param body full table body
|
|
#' @param tb filtered table body
|
|
#' @param f1 Subsets indexes of relevant columns
|
|
#' @param ns Relevant ns arranged in matrix
|
|
#' @param Ns All Ns
|
|
#' @param cut.off
|
|
#' @param glue.mask
|
|
#' @param dec
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
masking <- function(body,
|
|
tb=NULL,
|
|
f1,
|
|
ns,
|
|
Ns,
|
|
cut.off=cut.off,
|
|
glue.mask=glue.mask,
|
|
dec=dec
|
|
){
|
|
|
|
if (is.null(tb)) tb <- body
|
|
|
|
## Logical matrix of relevant ns to mask
|
|
f2 <- ns |>
|
|
purrr::map(\(.x) .x %in% 1:(cut.off - 1)) |>
|
|
dplyr::bind_cols() |>
|
|
as.matrix()
|
|
|
|
## Number of cells with zero observations in each matrix row
|
|
n0 <- apply(ns == 0, 1, sum)
|
|
|
|
## Number of cells with ns to mask in each matrix row
|
|
ns.low <- apply(f2, 1, sum)
|
|
|
|
|
|
if (all(ns.low==0)){
|
|
out <- body
|
|
} else {
|
|
|
|
ls.rows <- seq_len(nrow(f2)) |>
|
|
purrr::map(\(.i){
|
|
|
|
ds <- tb[.i, ]
|
|
# Creating masked matrix to record maskings
|
|
masked <- matrix(FALSE, ncol = ncol(f2))
|
|
|
|
# Only modify in case of low, handle one col matrices
|
|
if (apply(f2, 1, any)[.i]) {
|
|
n <- cut.off
|
|
N <- Ns[f2[.i,]]
|
|
p <- round(100 * n / N, dec)
|
|
|
|
ds[f1[f2[.i,]]] <- glue::glue(glue.mask) |>
|
|
as.matrix() |>
|
|
t() |>
|
|
tibble::as_tibble(.name_repair = "unique_quiet")
|
|
masked[f2[.i,]] <- TRUE
|
|
|
|
## loop to add masks until satisfied
|
|
while (sum(masked)==1 & # The case of only one low
|
|
nrow(masked)>1 | # But ignored in case of ncol==1
|
|
sum(ns[.i,][masked]) <= cut.off &
|
|
(sum(masked) + n0[.i]) < ncol(masked)) {
|
|
# in the case that sum of smalls is below cutoff, another field is added.
|
|
|
|
ranked <- apply(ns[.i,], 1, rank, ties.method = "first") |> t()
|
|
|
|
ranked.i <- ranked == sum(masked) + n0[.i] + 1
|
|
n <- ns[.i,][ranked.i] |> plyr::round_any(accuracy = cut.off, f = ceiling)
|
|
N <- Ns[ranked.i]
|
|
p <- round(100 * n / N, dec)
|
|
|
|
ds[f1[ranked.i]] <- glue::glue(glue.mask)
|
|
masked[ranked.i] <- TRUE
|
|
}}
|
|
|
|
list(
|
|
ds = ds,
|
|
masked = masked
|
|
)
|
|
})
|
|
|
|
# The case for dichotomous
|
|
if (nrow(tb) == 1) {
|
|
out <- ls.rows |>
|
|
purrr::map(purrr::pluck, "ds") |>
|
|
dplyr::bind_rows()
|
|
|
|
# Handling categorical data
|
|
} else if (nrow(tb) > 1) {
|
|
masks <- ls.rows |>
|
|
purrr::map(purrr::pluck, "masked") |>
|
|
purrr::reduce(rbind)
|
|
|
|
out <- ls.rows |>
|
|
purrr::map(purrr::pluck, "ds") |>
|
|
dplyr::bind_rows()
|
|
|
|
## Indices by row
|
|
col.i <- seq_len(nrow(masks)) |> purrr::map(\(.j){
|
|
which(masks[.j,])
|
|
})
|
|
|
|
col.i.vec <- purrr::list_c(col.i) |> unique()
|
|
|
|
if (purrr::compact(col.i) |> length() == 1){
|
|
# As this is only the case with overall column
|
|
# This should be reworked
|
|
|
|
# This was the approach, to just select the first
|
|
# which(purrr::map_lgl(col.i,is_empty))[1]
|
|
|
|
# This will select the cell with the second lowest number
|
|
col.i[[which(rank(ns,ties.method = "first")==2)]] <- c("")
|
|
}
|
|
|
|
out <- col.i |>
|
|
purrr::map(\(.y){
|
|
# length(.y)
|
|
if (length(.y) > 0) {
|
|
cols <- col.i.vec[!col.i.vec %in% .y]
|
|
if (length(cols)==0){
|
|
cols <- ""
|
|
} else {
|
|
cols
|
|
}
|
|
} else {
|
|
.y
|
|
}
|
|
}) |>
|
|
purrr::imap(\(.y, .i){
|
|
ds <- out[.i, ]
|
|
if (length(.y) > 0 & all(.y!="")) {
|
|
n <- ns[.i, .y] |>
|
|
purrr::map_dfr(plyr::round_any,accuracy = cut.off, f = ceiling)
|
|
N <- Ns[.y]
|
|
p <- round(100 * n / N, dec)
|
|
if (n==0) glue.mask <- "{n}"
|
|
|
|
ds[f1[.y]] <- glue::glue(glue.mask)|>
|
|
as.matrix() |>
|
|
t() |>
|
|
tibble::as_tibble(.name_repair = "unique_quiet")
|
|
ds
|
|
} else {
|
|
ds
|
|
}
|
|
}) |>
|
|
dplyr::bind_rows()
|
|
|
|
out <- rbind(
|
|
body |>
|
|
dplyr::filter(row_type=="label"),
|
|
out
|
|
)
|
|
}
|
|
}
|
|
out
|
|
|
|
}
|
|
|
|
|
|
summary_masks <- function(body,ls,cut.off=5,dec=dec){
|
|
seq_along(unique(body$variable)) |>
|
|
purrr::map(function(.x) {
|
|
variable_masks(index=.x,data=ls,cut.off=cut.off,dec=dec)
|
|
}) |>
|
|
dplyr::bind_rows()
|
|
}
|
|
|
|
mask_micro_summary <- function(data,micro.n=5){
|
|
data |>
|
|
gtsummary::modify_table_body(
|
|
~ .x |> summary_masks(ls=data,cut.off = micro.n,dec=1)
|
|
)
|
|
}
|
|
|
|
#' Title
|
|
#'
|
|
#' @param mask
|
|
#' @param data
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' minimal_mask(mask=tibble::as_tibble(matrix(c(FALSE,FALSE,TRUE,TRUE),nrow=1),.name_repair="unique_quiet"),
|
|
#' data=tibble::as_tibble(matrix(c(20,12,8,4),nrow=1),.name_repair="unique_quiet"),micro.n=5)
|
|
minimal_mask <- function(mask,data,micro.n){
|
|
## Function assumes monotonous data
|
|
## Use with column_diffs() for risk tables
|
|
|
|
## FUnction works in practice, but example doesn't??
|
|
|
|
out <- mask
|
|
|
|
for (i in seq_len(nrow(mask))){
|
|
if (i>1){
|
|
for (j in seq_len(ncol(mask))){
|
|
if(dplyr::pull(mask[i,j])){
|
|
n <- max(which(which(!out[i,])<j))
|
|
out[i,j] <- abs(data[i,n]-data[i,j]) %in% 1:(micro.n-1)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
out
|
|
}
|
|
|
|
|
|
#' Title
|
|
#'
|
|
#' @param data
|
|
#' @param micro.n
|
|
#' @param down
|
|
#' @param col.sel
|
|
#'
|
|
#' @return
|
|
#' @export
|
|
#'
|
|
#' @examples
|
|
#' data <- targets::tar_read(df_event_data) |>
|
|
#' events_dataset(impute = FALSE) |>
|
|
#' cox_regression(all.vars = FALSE) |>
|
|
#' ggsurvfit::survfit2() |>
|
|
#' ggsurvfit::tidy_survfit(times = c(0, 2, 4, 6, 8.5))|>
|
|
#' tidyr::pivot_wider(id_cols = strata, names_from = time, values_from = cum.event)
|
|
#' data |> mask_micro_table(col.sel=-strata)
|
|
#' data.sel <- data |> dplyr::select(-strata)
|
|
mask_micro_table <- function(data, micro.n = 5, down = TRUE, col.sel) {
|
|
data.sel <- data |>
|
|
dplyr::select({{ col.sel }})
|
|
|
|
## List of the two selection matrices
|
|
masked <- list(
|
|
data.sel, # The actual data
|
|
data.sel |>
|
|
column_diffs(include.first = TRUE) # Row differences (incl first row)
|
|
) |>
|
|
purrr::map(\(.y){ # For each element in the list, do colwise test
|
|
.y |>
|
|
purrr::map_dfr(\(.x) .x %in% 1:(micro.n-1))
|
|
}) |>
|
|
purrr::imap(\(.y,.i){ # apply minimal masking to last list object
|
|
if (.i==2){
|
|
.y |> minimal_mask(data = data.sel,micro.n=micro.n)
|
|
}else {
|
|
.y
|
|
}
|
|
}) |>
|
|
purrr::reduce(`|`) |> # Combine matrices
|
|
tibble::as_tibble() |> # To tibble
|
|
purrr::map2(data.sel, \(.x, .y){ # Apply masking based on combined selection
|
|
ifelse(.x, rounded_interval(.y, round = micro.n, down = down), .y)
|
|
}) |>
|
|
dplyr::bind_cols()
|
|
|
|
## Bind masked data to original columns
|
|
dplyr::bind_cols(
|
|
data |>
|
|
dplyr::select(-{{ col.sel }}),
|
|
masked|>
|
|
# Converts new to character for uniform data
|
|
dplyr::mutate(dplyr::across(dplyr::everything(), ~ as.character(.x)))
|
|
) |>
|
|
dplyr::select(colnames(data)) # Order columns as original input data
|
|
}
|
|
|
|
rounded_interval <- function(data, round = 5, down = TRUE) {
|
|
# Handle "ties"
|
|
sub <- ifelse(down, -1, 1)
|
|
data <- ifelse(data %% round == 0 & data != 0, data + 1, data)
|
|
|
|
c(floor, ceiling) |>
|
|
purrr::map(\(.x) {
|
|
plyr::round_any(x = data, accuracy = round, f = .x)
|
|
}) |>
|
|
dplyr::bind_cols(.name_repair = "unique_quiet") |>
|
|
setNames(c("l", "h")) |>
|
|
dplyr::transmute(mask = glue::glue("{l}-{h}")) |>
|
|
dplyr::pull(mask)
|
|
}
|
|
|
|
column_diffs <- function(data, prefix.pattern = NULL, include.first = TRUE, suffix.out = "_diff") {
|
|
if (!is.null(prefix.pattern)) {
|
|
data <- data |>
|
|
dplyr::select(tidyselect::starts_with(prefix.pattern))
|
|
}
|
|
|
|
index <- seq_along(data)[-1]
|
|
|
|
diff <- index |>
|
|
purrr::map(\(.y){
|
|
abs(data[.y - 1] - data[.y])
|
|
}) |>
|
|
dplyr::bind_cols() |>
|
|
(\(.x) setNames(.x, paste0(names(.x), suffix.out)))()
|
|
|
|
if (include.first) {
|
|
out <- dplyr::bind_cols(data[1], diff)
|
|
} else {
|
|
out <- diff
|
|
}
|
|
out
|
|
}
|
|
|