From 2e7cd96bd2e3af1c48cbc472a45218e701c73839 Mon Sep 17 00:00:00 2001 From: Yining97 Date: Tue, 30 Jun 2026 00:19:32 -0400 Subject: [PATCH] Reconcile chr-prefix conventions in the LD-sketch lookup .ldFromSketch did a raw match of the requested variant ids against the panel's SNP ids, so genotype-LD fine-mapping failed when the two agreed on the variants but disagreed on the chr prefix (e.g. chr22:... entry ids vs 22:... panel ids) -- even though variantId.R ships ensureChrMatch for exactly this. Reconcile the two id sets before matching; keep the caller's ids for the returned labels and fall back to the raw ids when normalization can't parse them. Co-Authored-By: Claude Opus 4.8 --- R/ld.R | 16 ++++++++++++++- tests/testthat/test_ld.R | 44 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/R/ld.R b/R/ld.R index aedb815d..890ae277 100644 --- a/R/ld.R +++ b/R/ld.R @@ -548,7 +548,21 @@ loadLdFromGenotype <- function(genotypePath, region, } onMissing <- match.arg(onMissing) snpInfo <- getSnpInfo(ldSketch) - idx <- match(variantIds, as.character(snpInfo$SNP)) + # Reconcile a pure chr-prefix mismatch between the requested ids and the + # panel (ensureChrMatch, variantId.R) before matching, so variants that + # differ only by a leading "chr" still resolve instead of reading as absent. + # The caller's original variantIds are kept for the returned labels; only the + # match keys are normalized. Fall back to the raw ids if normalization can't + # parse them (e.g. an rsID panel), preserving prior behavior. + matchIds <- variantIds + panelIds <- as.character(snpInfo$SNP) + reconciled <- tryCatch(ensureChrMatch(variantIds, panelIds), + error = function(e) NULL) + if (!is.null(reconciled)) { + matchIds <- reconciled$idsA + panelIds <- reconciled$idsB + } + idx <- match(matchIds, panelIds) if (anyNA(idx)) { if (onMissing == "error") { stop(sprintf("%s: %d variant id(s) not present in the LD sketch panel.", diff --git a/tests/testthat/test_ld.R b/tests/testthat/test_ld.R index d346dcff..696ec4e2 100644 --- a/tests/testthat/test_ld.R +++ b/tests/testthat/test_ld.R @@ -2840,6 +2840,50 @@ test_that(".ldFromSketch drops variants absent from the panel when onMissing='dr expect_equal(unname(diag(m)), c(1, 1)) }) +# ============================================================================= +# Additional coverage: .ldFromSketch reconciles chr-prefix conventions +# ============================================================================= + +test_that(".ldFromSketch resolves chr-prefixed request ids against a non-prefixed panel", { + skip_if_not_installed("pgenlibr") + h <- readGenotypes(file.path(geno_test_data_dir, "test_variants"), format = "plink2") + pid <- as.character(getSnpInfo(h)$SNP) # chr-prefixed (chr21_..._C_G) + h@snpInfo$SNP <- sub("^chr", "", pid) # panel now lacks the prefix + m <- pecotmr:::.ldFromSketch(h, pid[1:3]) # request keeps the prefix + expect_equal(dim(m), c(3L, 3L)) + expect_equal(rownames(m), pid[1:3]) # caller's labels preserved + expect_equal(unname(diag(m)), c(1, 1, 1)) +}) + +test_that(".ldFromSketch resolves non-prefixed request ids against a chr-prefixed panel", { + skip_if_not_installed("pgenlibr") + h <- readGenotypes(file.path(geno_test_data_dir, "test_variants"), format = "plink2") + pid <- as.character(getSnpInfo(h)$SNP) + req <- sub("^chr", "", pid[1:3]) # request drops the prefix + m <- pecotmr:::.ldFromSketch(h, req) + expect_equal(dim(m), c(3L, 3L)) + expect_equal(rownames(m), req) # caller's labels preserved +}) + +test_that(".ldFromSketch is unchanged when request and panel share a convention", { + skip_if_not_installed("pgenlibr") + h <- readGenotypes(file.path(geno_test_data_dir, "test_variants"), format = "plink2") + pid <- as.character(getSnpInfo(h)$SNP) + m <- pecotmr:::.ldFromSketch(h, pid[1:3]) + expect_equal(dim(m), c(3L, 3L)) + expect_equal(rownames(m), pid[1:3]) +}) + +test_that(".ldFromSketch still errors on a genuinely-absent variant after reconciliation", { + skip_if_not_installed("pgenlibr") + h <- readGenotypes(file.path(geno_test_data_dir, "test_variants"), format = "plink2") + pid <- as.character(getSnpInfo(h)$SNP) + # request the prefix-stripped form (forces reconciliation) plus one truly-absent variant + req <- c(sub("^chr", "", pid[1]), "21_99999999_A_G") + expect_error(pecotmr:::.ldFromSketch(h, req), + "not present in the LD sketch panel") +}) + # ============================================================================= # Additional coverage: .requireMatchingLdSketches error paths # =============================================================================