Unbiased data-driven analysis of five amyloid-beta peptides for biomarker investigations in familial Alzheimer's disease.
The 1 match
- [1] § Statistical analysis › Main metrics ↔ R/evaluate_biomarkers.R, lines 1 to 72 · score 0.63 · confidence intervals, evaluate biomarkers, Median, CIs, bootstrapping, metric · Report a mismatch
Paper
Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
R · 413 lines · 22 KB · GPL-3.0 · 1 match
- #' Evaluate Discovered Biomarkers Across Multiple Datasets and Groups
- #'
- #' Reads biomarker definitions from a discovery results file and evaluates their
- #' performance (Repeatability, Separation, Sample Size Estimate) on specified
- #' datasets and diagnostic groups (CU/CI), optionally calculating confidence intervals.
- #' Evaluation is performed dataset-by-dataset, group-by-group.
- #'
- #' @param discovery_results_csv_path Character string. Path to the input CSV file
- #' containing biomarker discovery results (typically generated by `run_experiments`).
- #' Expected columns include `regs_numerator`, `regs_denominator`, `var_composition`,
- #' and ideally identifiers like `discovery_dataset`, `experiment_tag`.
- #' @param prepared_data_list List. The output from `preprocess_data()`,
- #' containing filtered data for multiple datasets (e.g., `list(ADNI=list(data=..., data_suv_bi=...), MAYO=...)`).
- #' @param config List. The loaded main configuration object (output from
- #' `preprocess_data$config`). Must contain sections like
- #' `preprocessing`, `model_equations`, `power_params`.
- #' @param datasets_to_evaluate Character vector. Names of the datasets within
- #' `prepared_data_list` to evaluate the biomarkers on. Defaults to using all
- #' datasets present in `prepared_data_list`.
- #' @param groups_to_evaluate NULL or character vector. Groups ("CU", "CI") to evaluate
- #' within each dataset. Defaults to `NULL`.
- #' When NULL, it takes the same group that was used in the given row.
- #' @param calculate_ci Logical. If `TRUE`, calculate and return bootstrap/estimated
- #' 95% confidence intervals using `.feval_group_95CI`. Defaults to `FALSE`.
- #' @param nsim Integer. Number of bootstrap simulations if `calculate_ci` is `TRUE`.
- #' Defaults to 100. Ignored if `calculate_ci` is `FALSE`.
- #' @param output_evaluation_csv_path Character string or NULL. If provided, the full
- #' path to save the *evaluation* results table as a *new* CSV file. If the file
- #' exists, it will be overwritten. Directory created if needed.
- #' @param id_col Character string. Name of the unique individual identifier column,
- #' used for merging and checking data. Fetched from config if possible, defaults
- #' to "RID".
- #' @param verbose Logical. Print progress messages? Defaults to TRUE.
- #'
- #' @return A data frame containing the evaluation results. Each row corresponds to
- #' one discovered biomarker evaluated on one specific dataset and group. Columns
- #' include identifiers from the input CSV, evaluation context (dataset, group),
- #' evaluation status, and the calculated metrics (Rep, SepAB, SSE, plus CIs
- #' if requested). Returns `NULL` if the input CSV cannot be read or essential
- #' data is missing.
- #'
- #' @details
- #' Workflow:
- #' 1. Reads the `discovery_results_csv_path`.
- #' 2. Iterates through each dataset in `datasets_to_evaluate`.
- #' 3. For each dataset, identifies available features and prepares clinical data.
- #' 4. Iterates through each group in `groups_to_evaluate`.
- #' 5. For each dataset/group combination, iterates through each row (discovered biomarker)
- #' in the discovery results file.
- #' 6. Validates that the biomarker's required regions exist in the current dataset.
- #' 7. Reconstructs a 'chromosome' vector based on the biomarker's definition and
- #' the available features for the current dataset.
- #' 8. Calls internal `.calculate_cvr` to compute the biomarker value.
- #' 9. Merges the calculated value with the clinical data.
- #' 10. Checks for sufficient data in the specific dataset/group combination.
- #' 11. Calls `.feval_group` (or `.feval_group_95CI`) to get performance metrics.
- #' 12. Records the metrics along with discovery identifiers and evaluation context.
- #' 13. Combines all results into a single data frame after all loops complete.
- #' 14. Optionally saves the results table to `output_evaluation_csv_path`.
- #'
- #' @export
- #' @importFrom utils read.csv head
- #' @importFrom readr read_csv write_csv
- #' @importFrom dplyr bind_rows select all_of left_join filter n_distinct between
- #' @importFrom stringr str_split
- #' @importFrom rlang `%||%` .data sym is_scalar_character is_scalar_logical is_scalar_integerish
- #' @importFrom stats setNames median quantile IQR sd na.omit aggregate as.formula
- #' @importFrom lme4 lmerControl
- #' @importFrom longpower lmmpower
- #' @importFrom methods is
- #' @importFrom progress progress_bar
- evaluate_biomarkers <- function(discovery_results_csv_path,
- prepared_data_list,
- config,
- datasets_to_evaluate = names(prepared_data_list),
- groups_to_evaluate = c("CU", "CI"),
- calculate_ci = FALSE,
- nsim = 1000,
- output_evaluation_csv_path = NULL,
- id_col = NULL, # Get from config later
- verbose = TRUE) {
- # --- Input Validation ---
- stopifnot(
- rlang::is_scalar_character(discovery_results_csv_path),
- file.exists(discovery_results_csv_path),
- is.list(prepared_data_list), length(prepared_data_list) > 0,
- is.list(config),
- is.null(datasets_to_evaluate) || (is.character(datasets_to_evaluate) && length(datasets_to_evaluate) > 0),
- is.null(groups_to_evaluate) || (is.character(groups_to_evaluate) && all(groups_to_evaluate %in% c("CU", "CI"))),
- rlang::is_scalar_logical(calculate_ci),
- rlang::is_scalar_integerish(nsim), nsim > 1,
- is.null(output_evaluation_csv_path) || rlang::is_scalar_character(output_evaluation_csv_path),
- is.null(id_col) || rlang::is_scalar_character(id_col),
- rlang::is_scalar_logical(verbose)
- )
- # Check datasets exist
- missing_dsets <- setdiff(datasets_to_evaluate, names(prepared_data_list))
- datasets_to_evaluate <- intersect(datasets_to_evaluate, names(prepared_data_list))
- if (length(missing_dsets) > 0) message("Datasets not found in prepared_data_list: ", paste(missing_dsets, collapse=", "), "\nProceeding with ", datasets_to_evaluate)
- # Get ID col from config or use default
- id_col <- id_col %||% config$preprocessing$id_column %||% "RID"
- ignore_cols <- config$preprocessing$ignore_columns %||% "idx"
- # --- Load Discovery Results ---
- message("--- Loading Discovery Results ---")
- discovery_results_df <- try(readr::read_csv(discovery_results_csv_path, show_col_types = FALSE), silent = TRUE)
- if (inherits(discovery_results_df, "try-error")) {
- stop("Failed to read discovery results CSV: ", discovery_results_csv_path, "\nError: ", conditionMessage(attr(discovery_results_df,"condition")))
- }
- if (nrow(discovery_results_df) == 0) {
- warning("Discovery results CSV is empty: ", discovery_results_csv_path, call. = FALSE); return(NULL)
- }
- message(sprintf("Loaded %d discovered biomarker results to evaluate.", nrow(discovery_results_df)))
- # Check for essential columns from discovery results
- required_discovery_cols <- c("regs_numerator", "regs_denominator", "var_composition") # Add others used for ID if needed
- missing_discovery_cols <- setdiff(required_discovery_cols, names(discovery_results_df))
- if (length(missing_discovery_cols) > 0) {
- stop("Input discovery CSV is missing required columns: ", paste(missing_discovery_cols, collapse=", "))
- }
- # required_cols
- required_cols <- c(id_col, "time", "DX", "AB", "value") # Ensure these align with formulas/logic
- # --- Prepare Common Elements (Formulas, Control) ---
- all_power_params <- config$power_params
- eq_all_str <- config$model_equations$eq_all_string
- eq_group_str <- config$model_equations$eq_group_string
- if(is.null(eq_all_str) || is.null(eq_group_str)) stop("Missing formula strings in config.")
- eq_all <- try(stats::as.formula(eq_all_str)); if(inherits(eq_all, "try-error")) stop("Invalid eq_all_str")
- eq_group <- try(stats::as.formula(eq_group_str)); if(inherits(eq_group, "try-error")) stop("Invalid eq_group_str")
- lmer_control <- lme4::lmerControl(optimizer = "nloptwrap", calc.derivs = FALSE,
- check.conv.singular = "ignore",
- optCtrl = list(method = "nlminb", starttests = FALSE, kkt = FALSE))
- # --- Initialize List to Store All Evaluation Results ---
- all_evaluation_results <- list()
- result_counter <- 0
- # --- progress bar ---
- # Multicohort take as many time as the number of cohorts
- total_iterations <- max(1,length(datasets_to_evaluate)) * max(1, length(groups_to_evaluate)) * nrow(discovery_results_df)
- message(sprintf("Performing %d evaluations (%d datasets, %d groups, %d biomarkers).", total_iterations, length(datasets_to_evaluate), length(groups_to_evaluate), nrow(discovery_results_df)))
- pb <- progress_bar$new(
- format = "Progress [:bar] :percent (:current/:total) | ETA: :eta ",
- total = total_iterations,
- clear = FALSE,
- width = 50
- )
- # --- Loop 1: Through Datasets to Evaluate On ---
- message("\n--- Starting Biomarker Evaluation ---")
- for (eval_dset_name in datasets_to_evaluate) {
- if(verbose) message(sprintf("\nProcessing Dataset: %s", eval_dset_name))
- current_eval_data_list <- prepared_data_list[[eval_dset_name]]
- # --- Prepare data for this dataset once ---
- if (!"data" %in% names(current_eval_data_list) || !is.data.frame(current_eval_data_list$data) ||
- !"data_suv_bi" %in% names(current_eval_data_list) || !is.data.frame(current_eval_data_list$data_suv_bi)) {
- warning(sprintf("Skipping dataset '%s': Missing 'data' or 'data_suv_bi'.", eval_dset_name), call. = FALSE)
- next # Skip to next dataset
- }
- data_clinical <- current_eval_data_list$data
- data_suv <- current_eval_data_list$data_suv_bi
- available_features <- setdiff(names(data_suv), c(id_col, ignore_cols)) # Features available in THIS dataset
- required_clinical_cols <- c(id_col, "time", "DX", "AB") # Align with .feval_group needs
- if (length(available_features) == 0) {
- warning(sprintf("Skipping dataset '%s': No valid feature columns found in 'data_suv_bi'.", eval_dset_name), call. = FALSE)
- next
- }
- if (!all(required_clinical_cols %in% names(data_clinical))) {
- warning(sprintf("Skipping dataset '%s': Clinical data missing required columns: %s.",
- eval_dset_name, paste(setdiff(required_clinical_cols, names(data_clinical)), collapse=", ")), call. = FALSE)
- next
- }
- # --- Loop 2: Through Groups to Evaluate ---
- for (eval_group in groups_to_evaluate) {
- if(verbose) message(sprintf(" Evaluating Group: %s", eval_group))
- target_group_dx_val <- if(eval_group == "CI") 1L else 0L
- # --- Pre-Biomarker Loop Data Sufficiency Check *** ---
- min_rows_threshold <- 30
- min_group_members_threshold <- 20
- eval_status_group <- "Pending" # Status for this group overall
- # Check the clinical data prepared for this dataset
- group_subset_check <- data_clinical |>
- dplyr::filter(.data$DX == target_group_dx_val, .data$AB == TRUE, !is.na(.data$AB))
- n_group_rows_check <- nrow(group_subset_check)
- n_group_ids_check <- dplyr::n_distinct(group_subset_check[[id_col]])
- if(n_group_rows_check < min_rows_threshold || n_group_ids_check < min_group_members_threshold ) {
- warning(sprintf("Skipping ALL biomarkers for Dataset '%s', Group '%s': Insufficient initial data rows (%d) or unique IDs (%d) for reliable metrics evaluation. Min required: %d rows, %d IDs.",
- eval_dset_name, eval_group, n_group_rows_check, n_group_ids_check, min_rows_threshold, min_group_members_threshold), call.=FALSE)
- eval_status_group <- "Insufficient Data Pre-Check"
- # # --- Optionally log skipped rows for *all* biomarkers for this group/dataset ---
- # # This creates placeholder rows indicating the skip reason for clarity in output CSV
- # if (!is.null(output_evaluation_csv_path)) {
- # message(sprintf(" -> Logging skipped status for %d biomarkers...", nrow(discovery_results_df)))
- # # Define NA metrics structure
- # if (calculate_ci) metrics_na <- stats::setNames(rep(NA_real_, 9), c("Rep", "Rep.lower", "Rep.upper", "SepAB", "SepAB.lower", "SepAB.upper", "SSE", "SSE.lower", "SSE.upper"))
- # else metrics_na <- stats::setNames(rep(NA_real_, 3), c("Rep", "SepAB", "SSE"))
- # metrics_df_na <- as.data.frame(as.list(metrics_na))
- #
- # # Loop through biomarkers just to create the log entries
- # for (i_skip in seq_len(nrow(discovery_results_df))) {
- # biomarker_row_skip <- discovery_results_df[i_skip, , drop = FALSE]
- # discovery_info_skip <- biomarker_row_skip |>
- # dplyr::select(dplyr::any_of(c("timestamp", "experiment_tag", "discovery_dataset",
- # "group_evaluated", "bilateral", "var_composition",
- # "fitness_value", "regs_numerator", "regs_denominator",
- # "reference_vector", "ga_seed_used")))
- # result_counter <- result_counter + 1
- # all_evaluation_results[[result_counter]] <- dplyr::bind_cols(
- # discovery_info_skip,
- # data.frame(evaluation_dataset = eval_dset_name,
- # evaluation_group = eval_group,
- # evaluation_status = eval_status_group, # Log skip reason
- # stringsAsFactors = FALSE),
- # metrics_df_na
- # )
- # } # End loop logging skipped biomarkers
- # } # End if logging skipped rows
- # Advance progress bar
- pb$tick(nrow(discovery_results_df))
- next # Skip to the next group (or dataset if this was the last group)
- }
- # --- *** END: Pre-Biomarker Loop Data Sufficiency Check *** ---
- # --- Loop 3: Through Each Discovered Biomarker (Row in CSV) ---
- for (i in seq_len(nrow(discovery_results_df))) {
- # tick now in case a biomarker is skipped
- pb$tick()
- biomarker_row <- discovery_results_df[i, , drop = FALSE]
- # Use a unique identifier for the biomarker if available (e.g., from discovery_dataset + experiment_tag)
- # biomarker_id_str <- paste(biomarker_row$discovery_dataset %||% "NA", biomarker_row$experiment_tag %||% i, sep="_")
- # if(verbose) message(sprintf(" Biomarker %d/%d (%s)...", i, nrow(discovery_results_df), biomarker_id_str))
- # Extract definition
- regs_num_str <- biomarker_row$regs_numerator[[1]] %||% ""
- regs_den_str <- biomarker_row$regs_denominator[[1]] %||% ""
- var_comp <- biomarker_row$var_composition[[1]]
- if (!nzchar(regs_num_str) || !nzchar(regs_den_str) || is.na(var_comp)) {
- # warning(sprintf("Skipping biomarker row %d on %s/%s: Missing/invalid definition.", i, eval_dset_name, eval_group), call. = FALSE)
- next # Skip this biomarker if definition is bad
- }
- # Parse regions
- num_regions <- stringr::str_split(regs_num_str, ", ")[[1]]
- den_regions <- stringr::str_split(regs_den_str, ", ")[[1]]
- num_regions <- num_regions[nzchar(num_regions)]
- den_regions <- den_regions[nzchar(den_regions)]
- if (length(num_regions) == 0 || length(den_regions) == 0) {
- # warning(sprintf("Skipping biomarker row %d on %s/%s: Parsed definition empty.", i, eval_dset_name, eval_group), call. = FALSE)
- next # Skip this biomarker
- }
- # --- Check if required regions exist in THIS dataset ---
- missing_num <- setdiff(num_regions, available_features)
- missing_den <- setdiff(den_regions, available_features)
- if (length(missing_num) > 0 || length(missing_den) > 0) {
- # warning(sprintf("Skipping biomarker row %d on %s/%s: Required regions not found.", i, eval_dset_name, eval_group), call. = FALSE)
- # Optionally log which regions were missing
- next # Skip this biomarker for this dataset
- }
- # --- Reconstruct Chromosome for .calculate_cvr ---
- chromosome <- rep(1.5, length(available_features))
- chromosome[available_features %in% num_regions] <- 0.5
- chromosome[available_features %in% den_regions] <- 2.5
- # --- Calculate CVR Value ---
- calculated_value <- tryCatch({
- .calculate_cvr(
- chromosome = chromosome,
- dataset_cohort_data = current_eval_data_list,
- features = available_features,
- var_composition = var_comp,
- fixed_numerator_regs = NULL, # Not applicable here
- fixed_denominator_regs = NULL,
- verbose = FALSE
- )
- }, error = function(e) { NULL })
- if (is.null(calculated_value) || all(is.na(calculated_value))) {
- # warning(sprintf("Skipping biomarker row %d on %s/%s: .calculate_cvr failed.", i, eval_dset_name, eval_group), call.=FALSE)
- # Store a row indicating CVR failure? Or just skip? Let's skip.
- next
- }
- # --- Prepare data for evaluation ---
- eval_data <- data_clinical # Start fresh for each biomarker
- if (nrow(eval_data) != length(calculated_value)) {
- warning(sprintf("Skipping biomarker row %d on %s/%s: Row mismatch CVR/clinical (%d vs %d).",
- i, eval_dset_name, eval_group, length(calculated_value), nrow(eval_data)), call. = FALSE)
- next
- }
- eval_data$value <- calculated_value # Add the calculated biomarker value
- eval_data <- stats::na.omit(eval_data[, required_cols, drop = FALSE])
- # --- Initialize metrics & status ---
- metrics <- NULL
- eval_status <- "Pending"
- # --- Check data sufficiency for this group ---
- min_rows_threshold <- 30
- min_group_members_threshold <- 20
- group_subset <- eval_data |> dplyr::filter(.data$DX == target_group_dx_val, .data$AB == TRUE)
- n_group_rows <- nrow(group_subset)
- n_group_ids <- dplyr::n_distinct(group_subset[[id_col]])
- if(n_group_rows < min_rows_threshold || n_group_ids < min_group_members_threshold ) {
- # warning(sprintf("Biomarker row %d on %s/%s: Insufficient data rows (%d) or unique IDs (%d).",
- # i, eval_dset_name, eval_group, n_group_rows, n_group_ids), call.=FALSE)
- eval_status <- "Insufficient Data"
- if (calculate_ci) metrics <- stats::setNames(rep(NA_real_, 9), c("Rep", "Rep.lower", "Rep.upper", "SepAB", "SepAB.lower", "SepAB.upper", "SSE", "SSE.lower", "SSE.upper"))
- else metrics <- stats::setNames(rep(NA_real_, 3), c("Rep", "SepAB", "SSE"))
- } else {
- # --- Call appropriate evaluation function ---
- eval_func <- if(calculate_ci) .feval_group_95CI else .feval_group
- eval_args <- list(
- data = eval_data, # Use the data with 'value' column added
- group = eval_group,
- eq_all = eq_all,
- eq_group = eq_group,
- all_power_params = all_power_params,
- lmer_control = lmer_control
- )
- if(calculate_ci) eval_args$nsim <- nsim
- metrics_result <- try(do.call(eval_func, eval_args), silent = TRUE)
- if (inherits(metrics_result, "try-error")) {
- warning(sprintf("Biomarker row %d on %s/%s: Evaluation function failed. Error: %s",
- i, eval_dset_name, eval_group, conditionMessage(attr(metrics_result, "condition"))), call.=FALSE)
- eval_status <- "Metrics Calculation Error"
- if (calculate_ci) metrics <- stats::setNames(rep(NA_real_, 9), c("Rep", "Rep.lower", "Rep.upper", "SepAB", "SepAB.lower", "SepAB.upper", "SSE", "SSE.lower", "SSE.upper"))
- else metrics <- stats::setNames(rep(NA_real_, 3), c("Rep", "SepAB", "SSE"))
- } else if (anyNA(metrics_result)) {
- # warning(sprintf("Biomarker row %d on %s/%s: Evaluation function returned NA values.", i, eval_dset_name, eval_group), call.=FALSE)
- eval_status <- "Metrics Calculation NA"
- metrics <- metrics_result # Store the NAs
- } else {
- eval_status <- "Success"
- metrics <- metrics_result
- }
- } # End if sufficient data
- # --- Store Result Row ---
- result_counter <- result_counter + 1
- # Combine discovery info with evaluation info and metrics
- discovery_info <- biomarker_row |>
- dplyr::select(dplyr::any_of(c("timestamp", "experiment_tag", "discovery_dataset",
- "group_evaluated", "bilateral", "var_composition",
- "fitness_value", "regs_numerator", "regs_denominator",
- "reference_vector", "ga_seed_used"))) # Select relevant discovery columns
- metrics_df <- as.data.frame(as.list(metrics))
- all_evaluation_results[[result_counter]] <- dplyr::bind_cols(
- discovery_info,
- data.frame(evaluation_dataset = eval_dset_name,
- evaluation_group = eval_group,
- evaluation_status = eval_status,
- stringsAsFactors = FALSE),
- metrics_df
- )
- } # End loop over biomarkers (CSV rows)
- } # End loop over groups
- } # End loop over datasets
- # --- Combine All Results ---
- if (length(all_evaluation_results) == 0) {
- warning("No evaluation results were generated.", call. = FALSE)
- final_results_df <- data.frame() # Return empty data frame
- } else {
- final_results_df <- dplyr::bind_rows(all_evaluation_results)
- message(sprintf("\n--- Evaluation Complete: Generated %d result rows. ---", nrow(final_results_df)))
- }
- # --- Save Final Table (Optional) ---
- if (!is.null(output_evaluation_csv_path)) {
- tryCatch({
- output_dir <- dirname(output_evaluation_csv_path)
- if (!dir.exists(output_dir)) {
- message("Creating output directory for evaluation CSV: ", output_dir)
- dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
- }
- if(dir.exists(output_dir)){
- readr::write_csv(final_results_df, output_evaluation_csv_path, na = "NA") # Write NA for missing
- message("Evaluation results saved to: ", output_evaluation_csv_path)
- } else {
- warning("Failed to create output directory '", output_dir, "'. Cannot write evaluation CSV.", call.=FALSE)
- }
- }, error = function(e) {
- warning(sprintf("Failed to write evaluation results to '%s': %s", output_evaluation_csv_path, e$message), call. = FALSE)
- })
- }
- invisible(final_results_df)
- }
evaluate_biomarkers.R at commit 4903f81, under GPL-3.0 · at the source
Overview
- Department of Medical Physics and Biomedical Engineering and UCL Hawkes Institute, University College London, London WC1V 6LJ, UK
- Department of Neurodegenerative Disease, UCL Queen Square Institute of Neurology, London WC1N 3BG, UK
- Department of Clinical and Movement Neurosciences, Reta Lila Weston Institute, UCL Queen Square Institute of Neurology, London WC1N 3BG, UK
- Translational Mass Spectrometry Research Group, UCL Great Ormond Street Hospital Institute of Child Health, University College London, London WC1N 1EH, UK
- Dementia Research Centre, UCL Queen Square Institute of Neurology, London WC1N 3BG, UK
- UK Dementia Research Institute at UCL, London WC1E 6BT, UK
- Adelaide Medical School, The University of Adelaide, Adelaide, SA 5000, Australia
- Department of Neurology, Royal Adelaide Hospital and Queen Elizabeth Hospital, Adelaide, South Australia 5005, Australia
- VIB-KU Leuven Center for Brain & Disease Research, Leuven 3001, Belgium
- Department of Neurosciences, Leuven Research Institute for Neuroscience and Disease (LIND), KU Leuven, Leuven 3001, Belgium
- Department of Computer Science and UCL Hawkes Institute, University College London, London WC1V 6LJ, UK
Abstract
Changes to the relative abundance of amyloid-beta (Aβ) peptides are hallmarks of Alzheimer’s disease. Induced pluripotent stem cell (iPSC)-derived neurons offer a physiological model of Aβ production. We employed unbiased, data-driven analyses to investigate combinations of Aβ peptides as Alzheimer’s disease biomarkers and the relative contribution of peptides to Alzheimer’s disease pathogenesis.
We measured Aβ37, Aβ38, Aβ40, Aβ42 and Aβ43 in 10 iPSC-neuronal cultures from PSEN1 mutation carriers. We combined these data with published cell model data and used linear weighted combinations to (i) distinguish Alzheimer’s disease from controls, and (ii) predict age-at-onset for PSEN1 mutations. Data-driven approaches distinguished Aβ42 and Aβ43 from shorter peptides, providing unbiased evidence for a greater association of Aβ42 and Aβ43 to disease pathogenesis, compared with shorter peptides (Aβ37, Aβ38 and Aβ40). Weighted linear combinations of Aβ peptides outperform Aβ42/
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 1 match between paragraphs and lines of code.
isaac-6/biodiscvr
4903f81bc04d67aeb602dc1f9f068c6fb2925be4, 15 March 2026Availability: 1 check, the latest on 30 September 2026: the link answers
- 30 September 2026: the link answers
19 files
- R/
biodiscover.R , R, 667 lines - R/
biodiscvr_multicohort.R , R, 819 lines - R/
create_demographics.R , R, 180 lines - R/
evaluate_biomarkers.R , R, 413 lines, 1 match - R/
evaluation.R , R, 397 lines - R/
load_data.R , R, 153 lines - R/
preprocess.R , R, 656 lines - R/
region_ablation.R , R, 271 lines - R/
run_ablation.R , R, 308 lines - R/
run_experiments.R , R, 356 lines - R/
search_algorithm.R , R, 610 lines - R/
utils.R , R, 332 lines - README.Rmd, R, 129 lines
- tests/
testthat.R , R, 12 lines - tests/
testthat/ , R, 60 linestest-workflow.R - vignettes/
biodiscvr.Rmd , R, 329 lines - vignettes/
biodiscvr_synth.Rmd , R, 165 lines - LICENSE.md, License, 595 lines
- README.md, Text, 170 lines
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 17 scripts, each with its path and the digest of its content;
- 1 match between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data availability
The datasets generated and analysed during the current study are available in the supplementary figures and from the primary publications: Petit et al. colleagues,9 Liu et al.,8 and Schultz et al.10 The framework code used for biomarker discovery (weighted-composite value ratios), is available in a public GitHub repository under the GNU General Public License v3.0: https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 30 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 11 authors, 5 keywords, 9 funders, 27 references.
Cite
This paper
Llorente-Saguer, I., Gabriele, R., Bradshaw, T. Y., Leckey, C. A., Belder, C. R. S., Chávez-Gutiérrez, L., de Silva, R., Fox, N. C., Wray, S., Oxtoby, N. P., & Arber, C. (2026). Unbiased data-driven analysis of five amyloid-beta peptides for biomarker investigations in familial Alzheimer's disease. Brain communications, 8(2), fcag105. https://
BibTeX
@article{llorentesaguer2
author = {Llorente-Saguer, Isaac and Gabriele, Rebecca and Bradshaw, Teisha Y and Leckey, Claire A and Belder, Christopher R S and Chávez-Gutiérrez, Lucía and de Silva, Rohan and Fox, Nick C and Wray, Selina and Oxtoby, Neil P and Arber, Charles},
title = {{Unbiased data-driven analysis of five amyloid-beta peptides for biomarker investigations in familial Alzheimer's disease}},
journal = {Brain communications},
year = {2026},
month = mar,
volume = {8},
number = {2},
pages = {fcag105},
publisher = {Oxford University Press},
issn = {2632-1297},
doi = {10.1093/
url = {https://
pmid = {42022292},
pmcid = {PMC13096935}
}
RIS
TY - JOUR
AU - Llorente-Saguer, Isaac
AU - Gabriele, Rebecca
AU - Bradshaw, Teisha Y
AU - Leckey, Claire A
AU - Belder, Christopher R S
AU - Chávez-Gutiérrez, Lucía
AU - de Silva, Rohan
AU - Fox, Nick C
AU - Wray, Selina
AU - Oxtoby, Neil P
AU - Arber, Charles
TI - Unbiased data-driven analysis of five amyloid-beta peptides for biomarker investigations in familial Alzheimer's disease
T2 - Brain communications
J2 - Brain Commun
PY - 2026
DA - 2026/
VL - 8
IS - 2
SP - fcag105
SN - 2632-1297
PB - Oxford University Press
DO - 10.1093/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1093/
"type": "article-journal",
"title": "Unbiased data-driven analysis of five amyloid-beta peptides for biomarker investigations in familial Alzheimer's disease",
"container-title": "Brain communications",
"author": [
{
"family": "Llorente-Saguer",
"given": "Isaac"
},
{
"family": "Gabriele",
"given": "Rebecca"
},
{
"family": "Bradshaw",
"given": "Teisha Y"
},
{
"family": "Leckey",
"given": "Claire A"
},
{
"family": "Belder",
"given": "Christopher R S"
},
{
"family": "Chávez-Gutiérrez",
"given": "Lucía"
},
{
"family": "de Silva",
"given": "Rohan"
},
{
"family": "Fox",
"given": "Nick C"
},
{
"family": "Wray",
"given": "Selina"
},
{
"family": "Oxtoby",
"given": "Neil P"
},
{
"family": "Arber",
"given": "Charles"
}
],
"container-title-short":
"volume": "8",
"issue": "2",
"page": "fcag105",
"DOI": "10.1093/
"PMID": "42022292",
"PMCID": "PMC13096935",
"ISSN": "2632-1297",
"publisher": "Oxford University Press",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
22
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1371/journal.pbio.3002727 [code]
- Selective suppression of oligodendrocyte-derived amyloid beta rescues neuronal dysfunction in Alzheimer's disease.Journal: PLoS biologyIn common: Alzheimer's / dementia, 2 references, 2 authors
- [2] doi:10.1126/sciadv.adk1296 [code]
- The annotation of &
lt;i& gt;GBA1& lt;/ i& gt; has been concealed by its protein-coding pseudogene & lt;i& gt;GBAP1& lt;/ i& gt;. Journal: Science advancesIn common: tidyverse, 1 reference, 2 authors - [3] doi:10.1093/braincomms/fcaf497 [code]
- Enhanced monitoring of Alzheimer's disease brain atrophy using composite value ratios of volumes.Journal: Brain communicationsIn common: lme4, tidyverse, Alzheimer's / dementia, 2 authors
- [4] doi:10.1186/s13195-024-01659-6 [code]
- Integrative multiomics reveals common endotypes across PSEN1, PSEN2, and APP mutations in familial Alzheimer's disease.Journal: Alzheimer's research & therapyIn common: tidyverse, Alzheimer's / dementia, 6 references
- [5] doi:10.1002/advs.202514783
- Autophagy Activators Normalize Aberrant Tau Proteostasis and Rescue Synapses in Human Familial Alzheimer's Disease iPSC-Derived Cortical Organoids.Journal: Advanced science (Weinheim, Baden-Wurttemberg, Germany)In common: Alzheimer's / dementia, 4 references
- [6] doi:10.1021/acs.analchem.3c05557
- Integrative Single-Plaque Analysis Reveals Signature Aβ and Lipid Profiles in the Alzheimer's Brain.Journal: Analytical chemistryIn common: Alzheimer's / dementia, 4 references
- [7] doi:10.1038/s41380-026-03686-1 [code]
- Early oligodendrocyte dysfunction signature in Alzheimer's disease: Insights from DNA methylomics and transcriptomics.Journal: Molecular psychiatryIn common: tidyverse, Alzheimer's / dementia, author Rohan de Silva
- [8] doi:10.1101/2025.08.28.672821 [code]
- Early oligodendrocyte dysfunction signature in Alzheimer’s disease: Insights from DNA methylomics and transcriptomicsJournal: bioRxiv (preprint)In common: tidyverse, Alzheimer's / dementia, author Rohan de Silva
- [9] doi:10.3390/cells12030420
- The Breakthroughs and Caveats of Using Human Pluripotent Stem Cells in Modeling Alzheimer's Disease.Journal: CellsIn common: Alzheimer's / dementia, 4 references
- [10] doi:10.1093/brain/awad232 [code]
- A data-driven study of Alzheimer's disease related amyloid and tau pathology progression.Journal: Brain : a journal of neurologyIn common: Alzheimer's / dementia, 1 reference, author Neil P. Oxtoby
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 17 scripts, and 1 match between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:0031a3a9bfdea42f…
Add the badge to the code's README
If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.
Markdown
<!-- oscr-badge:start -->
[](https://openscicode.org/badge/doi/10.1093/braincomms/fcag105)
<!-- oscr-badge:end -->reStructuredText
.. oscr-badge:start
.. image:: https://openscicode.org/badge/doi/10.1093/braincomms/fcag105.svg
:target: https://openscicode.org/badge/doi/10.1093/braincomms/fcag105
:alt: OSCR
.. oscr-badge:endHTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1093/braincomms/fcag105"><img src="https://openscicode.org/badge/doi/10.1093/braincomms/fcag105.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".
If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge page.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.
Report a mismatch between the code and the paper · Report a code error (signed in)
Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.
Report a reproduction failure (signed in)
Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
