Nothing
pcor_from_precision <- function(Theta) {
d <- diag(Theta)
S <- -Theta / sqrt(outer(d, d))
diag(S) <- 1
S
}
strip_matrix_metadata <- function(x) {
attrs <- attributes(x)
keep <- intersect(names(attrs), c("dim", "dimnames"))
attributes(x) <- attrs[keep]
x
}
manual_partial_fisher_ci_R <- function(X, conf_level = 0.95) {
X <- as.matrix(X)
n <- nrow(X)
p <- ncol(X)
c_vars <- p - 2L
S <- stats::cov(X)
Theta <- solve(S)
pcor <- pcor_from_precision(Theta)
lwr <- matrix(NA_real_, p, p, dimnames = dimnames(pcor))
upr <- matrix(NA_real_, p, p, dimnames = dimnames(pcor))
diag(lwr) <- 1
diag(upr) <- 1
se <- 1 / sqrt(n - 3 - c_vars)
crit <- stats::qnorm(0.5 * (1 + conf_level))
eps <- sqrt(.Machine$double.eps)
for (j in seq_len(p - 1L)) {
for (i in (j + 1L):p) {
r <- pcor[j, i]
if (!is.finite(r) || abs(r) >= 1) next
z <- atanh(max(min(r, 1 - eps), -1 + eps))
lo <- tanh(z - crit * se)
hi <- tanh(z + crit * se)
lwr[j, i] <- lwr[i, j] <- lo
upr[j, i] <- upr[i, j] <- hi
}
}
list(
pcor = pcor,
lwr = lwr,
upr = upr,
n_complete = n,
n_conditioning = c_vars
)
}
oas_shrink_R <- function(X) {
n <- nrow(X)
p <- ncol(X)
mu <- colMeans(X)
Xc <- sweep(X, 2, mu, check.margin = FALSE)
S_mle <- crossprod(Xc) / n
trS <- sum(diag(S_mle))
trS2 <- sum(S_mle * S_mle)
mu_sc <- trS / p
num <- (1 - 2 / p) * trS2 + trS^2
den <- (n + 1 - 2 / p) * (trS2 - trS^2 / p)
rho <- if (den > 0) num / den else 1
rho <- max(0, min(1, rho))
Sigma <- (1 - rho) * S_mle
diag(Sigma) <- diag(Sigma) + rho * mu_sc
list(Sigma = Sigma, rho = rho)
}
glasso_reference_case <- function() {
vars <- paste0("V", seq_len(6))
Sigma <- structure(
c(0.892577511863956, -3.47460427750079e-05, -0.048912146675666,
0.00145719602008398, 0.00184184223658341, 7.32384941200259e-08,
-3.47460427750079e-05, 1.18178348379229, 0.000607657901650682,
-1.81034106257749e-05, -0.0211063204776342, -0.00249098993197849,
-0.048912146675666, 0.000607657901650682, 0.855402516097313,
-0.0254842452589996, -0.0322111497962843, -1.28083505639143e-06,
0.00145719602008398, -1.81034106257749e-05, -0.0254842452589996,
1.08365876399129, 0.000959638095557692, 3.81587780019788e-08,
0.00184184223658341, -0.0211063204776342, -0.0322111497962843,
0.000959638095557692, 1.11881841527388, 4.44883792437081e-05,
7.32384941200259e-08, -0.00249098993197849, -1.28083505639143e-06,
3.81587780019788e-08, 4.44883792437081e-05, 0.894638069650798),
dim = c(6L, 6L),
dimnames = list(vars, vars)
)
Theta <- structure(
c(1.12387243015938, 0, 0.0642633288004597, 0, 0, 0,
0, 0.846468847136127, 0, 0, 0.0159683981780265, 0.0023560756458958,
0.0642633288004597, 0, 1.1748032714163, 0.0275114249784953,
0.033693582191095, 0, 0, 0, 0.0275114249784953, 0.923446698493756, 0, 0,
0, 0.0159683981780265, 0.033693582191095, 0, 0.895071380200203, 0,
0, 0.0023560756458958, 0, 0, 0, 1.11777701272096),
dim = c(6L, 6L),
dimnames = list(vars, vars)
)
list(lambda = 0.08, Sigma = Sigma, Theta = Theta)
}
test_that("pcorr returns expected components for each method", {
set.seed(111)
X <- matrix(rnorm(200), nrow = 40, ncol = 5)
colnames(X) <- paste0("V", seq_len(5))
samp <- pcorr(X, method = "sample", return_cov_precision = TRUE)
expect_s3_class(samp, "partial_corr")
expect_equal(dim(samp$pcor), c(5, 5))
expect_true(all(diag(samp$pcor) == 1))
expect_true(is.matrix(samp$cov))
expect_true(is.matrix(samp$precision))
expect_equal(samp$method, "sample")
expect_true(is.na(samp$lambda))
expect_true(is.na(samp$rho))
ridge <- pcorr(X, method = "ridge", lambda = 1e-2, return_cov_precision = TRUE)
expect_equal(ridge$method, "ridge")
expect_equal(ridge$lambda, 1e-2)
expect_true(is.na(ridge$rho))
oas <- pcorr(X, method = "oas", return_cov_precision = TRUE)
expect_equal(oas$method, "oas")
expect_true(is.na(oas$lambda))
expect_true(oas$rho >= 0 && oas$rho <= 1)
glasso <- pcorr(X, method = "glasso", lambda = 0.05, return_cov_precision = TRUE)
expect_equal(glasso$method, "glasso")
expect_equal(glasso$lambda, 0.05)
expect_true(is.na(glasso$rho))
})
test_that("pcorr print and plot methods cover options", {
skip_if_not_installed("ggplot2")
set.seed(222)
X <- matrix(rnorm(150), nrow = 30, ncol = 5)
colnames(X) <- paste0("G", seq_len(5))
pc <- pcorr(X, method = "ridge", lambda = 5e-3, return_cov_precision = TRUE)
out1 <- capture.output(print(pc, digits = 4, max_rows = 3, max_cols = 4))
expect_true(any(grepl("omitted", out1)))
out2 <- capture.output(print(pc, show_method = FALSE, max_rows = 2, max_cols = 2))
expect_true(any(grepl("Partial correlation matrix", out2)))
p <- plot(pc, mask_diag = FALSE, reorder = TRUE, value_text_size = 3,
low_color = "blue", high_color = "red", mid_color = "white")
expect_s3_class(p, "ggplot")
})
test_that("pcorr validates lambda", {
set.seed(1)
X <- matrix(rnorm(40), nrow = 10, ncol = 4)
expect_error(pcorr(X, method = "ridge", lambda = -1), "must be >=")
})
test_that("pcorr rejects non-finite matrix inputs without an R-side copy", {
X <- matrix(rnorm(16), nrow = 4)
X[3] <- Inf
expect_error(pcorr(X, method = "sample"), "finite values")
})
test_that("pcorr exposes shrinkage metadata without cov/precision", {
set.seed(333)
X <- matrix(rnorm(120), nrow = 30, ncol = 4)
oas <- pcorr(X, method = "oas")
expect_true(is.numeric(oas$rho))
expect_true(isTRUE(oas$rho >= 0 && oas$rho <= 1))
expect_true(is.numeric(oas$jitter))
expect_false(is.na(oas$jitter))
expect_null(oas$cov)
expect_null(oas$precision)
samp <- pcorr(X, method = "sample")
expect_true(is.na(samp$rho))
expect_true(is.na(samp$lambda))
expect_true(is.numeric(samp$jitter))
expect_null(samp$p_value)
})
test_that("pcorr default CI behaviour remains estimate-only", {
set.seed(334)
X <- matrix(rnorm(120), nrow = 30, ncol = 4)
fit <- pcorr(X, method = "sample")
expect_null(fit$ci)
expect_null(attr(fit, "ci", exact = TRUE))
expect_null(attr(fit, "conf.level", exact = TRUE))
expect_true(is.list(fit$diagnostics))
})
test_that("pcorr Fisher-z CI matches manual partial-correlation calculation", {
set.seed(335)
X <- matrix(rnorm(240), nrow = 60, ncol = 4)
colnames(X) <- paste0("V", 1:4)
fit <- pcorr(X, method = "sample", ci = TRUE, return_p_value = TRUE)
ref <- manual_partial_fisher_ci_R(X, conf_level = 0.95)
ci <- attr(fit, "ci", exact = TRUE)
expect_true(is.list(ci))
expect_identical(ci$ci.method, "fisher_z_partial")
expect_equal(ci$conf.level, 0.95)
expect_equal(
strip_matrix_metadata(fit$pcor),
strip_matrix_metadata(ref$pcor),
tolerance = 1e-10
)
expect_equal(ci$lwr.ci, ref$lwr, tolerance = 1e-10)
expect_equal(ci$upr.ci, ref$upr, tolerance = 1e-10)
expect_identical(unique(as.integer(fit$diagnostics$n_complete)), as.integer(ref$n_complete))
expect_identical(unique(as.integer(fit$diagnostics$n_conditioning[upper.tri(fit$diagnostics$n_conditioning)])), as.integer(ref$n_conditioning))
expect_true(is.matrix(fit$p_value))
expect_false("inference" %in% names(unclass(fit)))
expect_null(attr(fit$pcor, "ci", exact = TRUE))
expect_null(attr(fit$pcor, "diagnostics", exact = TRUE))
})
test_that("pcorr custom conf_level changes CI width", {
set.seed(336)
X <- matrix(rnorm(320), nrow = 80, ncol = 4)
colnames(X) <- paste0("V", 1:4)
fit90 <- pcorr(X, method = "sample", ci = TRUE, conf_level = 0.90)
fit99 <- pcorr(X, method = "sample", ci = TRUE, conf_level = 0.99)
ci90 <- attr(fit90, "ci", exact = TRUE)
ci99 <- attr(fit99, "ci", exact = TRUE)
width90 <- ci90$upr.ci["V1", "V2"] - ci90$lwr.ci["V1", "V2"]
width99 <- ci99$upr.ci["V1", "V2"] - ci99$lwr.ci["V1", "V2"]
expect_equal(attr(fit90, "conf.level", exact = TRUE), 0.90)
expect_equal(attr(fit99, "conf.level", exact = TRUE), 0.99)
expect_true(width99 > width90)
})
test_that("pcorr CI integrates with the existing summary contract", {
set.seed(337)
X <- matrix(rnorm(240), nrow = 60, ncol = 4)
colnames(X) <- paste0("V", 1:4)
fit <- pcorr(X, method = "sample", ci = TRUE)
sm <- summary(fit)
txt <- capture.output(print(sm, show_ci = "yes"))
expect_s3_class(sm, "summary.partial_corr")
expect_s3_class(sm, "summary.matrixCorr")
expect_s3_class(sm, "data.frame")
expect_true(isTRUE(attr(sm, "has_ci")))
expect_true(all(c("estimate", "lwr", "upr") %in% names(sm)))
expect_match(paste(txt, collapse = "\n"), "Partial correlation summary")
expect_match(paste(txt, collapse = "\n"), "ci_method")
expect_match(paste(txt, collapse = "\n"), "ci_width")
expect_match(paste(txt, collapse = "\n"), "Strongest pairs by \\|estimate\\|")
expect_true(any(grepl("\\blwr\\b", txt)))
expect_true(any(grepl("\\bupr\\b", txt)))
})
test_that("sample partial-correlation p-values match the reference example", {
y.data <- data.frame(
hl = c(7, 15, 19, 15, 21, 22, 57, 15, 20, 18),
disp = c(0.000, 0.964, 0.000, 0.000, 0.921, 0.000, 0.000, 1.006, 0.000, 1.011),
deg = c(9, 2, 3, 4, 1, 3, 1, 3, 6, 1),
BC = c(1.78e-02, 1.05e-06, 1.37e-05, 7.18e-03, 0.00e+00,
0.00e+00, 0.00e+00, 4.48e-03, 2.10e-06, 0.00e+00)
)
ours <- pcorr(y.data, method = "sample", return_p_value = TRUE)
vars <- c("hl", "disp", "deg", "BC")
expected_pcor <- structure(
c(
1.0000000, -0.6720863, -0.6161163, 0.1148459,
-0.6720863, 1.0000000, -0.7215522, 0.2855420,
-0.6161163, -0.7215522, 1.0000000, 0.6940953,
0.1148459, 0.2855420, 0.6940953, 1.0000000
),
dim = c(4L, 4L),
dimnames = list(vars, vars)
)
expected_p_value <- structure(
c(
0.00000000, 0.06789202, 0.10383620, 0.78654997,
0.06789202, 0.00000000, 0.04332869, 0.49299871,
0.10383620, 0.04332869, 0.00000000, 0.05615021,
0.78654997, 0.49299871, 0.05615021, 0.00000000
),
dim = c(4L, 4L),
dimnames = list(vars, vars)
)
expect_equal(
strip_matrix_metadata(ours$pcor),
strip_matrix_metadata(expected_pcor),
tolerance = 1e-7
)
expect_equal(ours$p_value, expected_p_value, tolerance = 1e-8)
expect_equal(unname(diag(ours$p_value)), rep(0, ncol(y.data)))
})
test_that("pcorr p-values are restricted to the classical sample setting", {
X <- matrix(rnorm(60), nrow = 20, ncol = 3)
expect_error(
pcorr(X, method = "ridge", return_p_value = TRUE),
"available only for"
)
X_wide <- matrix(rnorm(36), nrow = 6, ncol = 6)
expect_error(
pcorr(X_wide, method = "sample", return_p_value = TRUE),
"requires .*n > p"
)
})
test_that("pcorr CI is restricted to the classical sample setting", {
X <- matrix(rnorm(80), nrow = 20, ncol = 4)
expect_error(
pcorr(X, method = "ridge", ci = TRUE),
"available only for the classical .*method = \"sample\""
)
expect_error(
pcorr(X, method = "oas", ci = TRUE),
"available only for the classical .*method = \"sample\""
)
expect_error(
pcorr(X, method = "glasso", ci = TRUE),
"available only for the classical .*method = \"sample\""
)
})
test_that("pcorr CI requires positive Fisher-z residual degrees of freedom", {
X <- matrix(rnorm(20), nrow = 5, ncol = 4)
expect_error(
pcorr(X, method = "sample", ci = TRUE),
"requires .*n > p \\+ 1"
)
})
test_that("pcorr CI warns and returns NA bounds when the sample covariance needs jitter", {
x1 <- 1:20
x2 <- x1
x3 <- rep(c(0, 1), each = 10)
X <- cbind(x1 = x1, x2 = x2, x3 = x3)
expect_warning(
fit <- pcorr(X, method = "sample", ci = TRUE),
class = "matrixCorr_ci_warning"
)
ci <- attr(fit, "ci", exact = TRUE)
expect_true(all(is.na(ci$lwr.ci[upper.tri(ci$lwr.ci)])))
expect_true(all(is.na(ci$upr.ci[upper.tri(ci$upr.ci)])))
})
test_that("sample partial correlation is close to truth in a structured MVN model", {
skip_if_not_installed("MASS")
set.seed(20240819)
p <- 8
alpha <- 0.3
Theta <- diag(p)
for (j in 1:(p - 1)) Theta[j, j + 1] <- Theta[j + 1, j] <- -alpha
Sigma <- solve(Theta)
n <- 1500
X <- MASS::mvrnorm(n, mu = rep(0, p), Sigma = Sigma)
colnames(X) <- paste0("V", seq_len(p))
ours <- pcorr(X, method = "sample", return_cov_precision = FALSE)$pcor
truth <- pcor_from_precision(Theta)
ut <- upper.tri(ours, diag = FALSE)
z_hat <- atanh(ours[ut])
z_true <- atanh(truth[ut])
se_z <- 1 / sqrt(n - p - 1)
expect_lt(max(abs(z_hat - z_true)), 3 * se_z)
non_edge <- matrix(TRUE, p, p)
diag(non_edge) <- FALSE
non_edge[abs(row(non_edge) - col(non_edge)) == 1] <- FALSE
expect_lt(max(abs(ours[non_edge])), 0.07)
expect_true(isSymmetric(ours, tol = 1e-12))
expect_equal(as.numeric(diag(ours)), rep(1, p), tolerance = 1e-12)
})
test_that("sample method equals the base-R precision construction", {
set.seed(123)
n <- 200
p <- 12
X <- matrix(rnorm(n * p), n, p)
colnames(X) <- paste0("V", seq_len(p))
ours <- pcorr(X, method = "sample", return_cov_precision = TRUE)
S_unb <- stats::cov(X)
Theta <- solve(S_unb)
ref <- pcor_from_precision(Theta)
expect_equal(
strip_matrix_metadata(ours$pcor),
strip_matrix_metadata(ref),
tolerance = 1e-10
)
expect_true(isSymmetric(ours$pcor))
expect_equal(as.numeric(diag(ours$pcor)), rep(1, p))
})
test_that("ridge method equals the base-R ridge construction", {
set.seed(99)
n <- 180
p <- 10
X <- matrix(rnorm(n * p), n, p)
colnames(X) <- paste0("V", seq_len(p))
lambda <- 5e-3
ours <- pcorr(X, method = "ridge", lambda = lambda,
return_cov_precision = TRUE)
S_unb <- stats::cov(X)
diag(S_unb) <- diag(S_unb) + lambda
Theta <- solve(S_unb)
ref <- pcor_from_precision(Theta)
expect_equal(
strip_matrix_metadata(ours$pcor),
strip_matrix_metadata(ref),
tolerance = 1e-10
)
I <- diag(p)
dimnames(I) <- dimnames(S_unb)
expect_equal(S_unb %*% Theta, I, tolerance = 1e-8)
})
test_that("OAS method matches an R implementation of the same formula", {
set.seed(7)
n <- 120
p <- 25
X <- matrix(rnorm(n * p), n, p)
colnames(X) <- paste0("V", seq_len(p))
ours <- pcorr(X, method = "oas", return_cov_precision = TRUE)
oas <- oas_shrink_R(X)
Sigma <- oas$Sigma
Theta <- solve(Sigma)
ref <- pcor_from_precision(Theta)
expect_equal(
strip_matrix_metadata(ours$pcor),
strip_matrix_metadata(ref),
tolerance = 1e-10
)
expect_true(isSymmetric(ours$pcor))
expect_equal(as.numeric(diag(ours$pcor)), rep(1, p))
})
test_that("glasso method matches fixed graphical-lasso reference values", {
set.seed(1234)
n <- 120
p <- 6
X <- matrix(rnorm(n * p), n, p)
colnames(X) <- paste0("V", seq_len(p))
ref <- glasso_reference_case()
lambda <- ref$lambda
ours <- pcorr(X, method = "glasso", lambda = lambda, return_cov_precision = TRUE)
ref_pcor <- pcor_from_precision(ref$Theta)
dimnames(ref_pcor) <- dimnames(ours$pcor)
expect_equal(ours$precision, ref$Theta, tolerance = 2e-4)
expect_equal(ours$cov, ref$Sigma, tolerance = 2e-4)
expect_equal(
strip_matrix_metadata(ours$pcor),
strip_matrix_metadata(ref_pcor),
tolerance = 2e-4
)
expect_true(isSymmetric(ours$pcor, tol = 1e-12))
expect_equal(as.numeric(diag(ours$pcor)), rep(1, p), tolerance = 1e-12)
})
test_that("p >> n: OAS returns a finite, well-formed matrix", {
set.seed(4242)
n <- 40
p <- 80
X <- matrix(rnorm(n * p), n, p)
colnames(X) <- paste0("V", seq_len(p))
oas <- pcorr(X, method = "oas", return_cov_precision = TRUE)
M <- oas$pcor
expect_true(is.matrix(M))
expect_true(isSymmetric(M, tol = 1e-12))
expect_equal(as.numeric(diag(M)), rep(1, p), tolerance = 1e-12)
expect_true(all(is.finite(M)))
expect_true(all(abs(M[upper.tri(M, diag = FALSE)]) <= 1 + 1e-10))
I <- diag(p)
dimnames(I) <- dimnames(oas$cov)
expect_equal(oas$cov %*% oas$precision, I, tolerance = 1e-6)
})
test_that("non-numeric columns are ignored and dimnames propagate", {
set.seed(1)
X <- data.frame(
a = rnorm(50),
b = rnorm(50),
f = factor(sample(letters[1:3], 50, TRUE)),
c = rnorm(50),
s = sample(c("x", "y", "z"), 50, TRUE),
l = sample(c(TRUE, FALSE), 50, TRUE)
)
cols <- c("a", "b", "c")
pc <- pcorr(X, method = "oas", return_cov_precision = FALSE)$pcor
expect_equal(dim(pc), c(3L, 3L))
expect_equal(dimnames(pc), list(cols, cols))
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.