bgns estimates biweight midcorrelation and selects exact
nearest neighbors from the resulting similarities. This vignette
describes score calculation, edge selection, and incomplete-data
handling through reproducible examples. The simulated measurements
include positive and negative associations.
Source installation requires R >= 4.3.0, a C++17 compiler, and the numerical libraries configured for R. Install the appropriate Rtools on Windows or follow the R for macOS toolchain instructions. OpenMP support is optional. WGCNA is not a runtime dependency.
Arrange observations in rows and the entities to be compared in columns. For expression data, this orientation determines whether the graph connects genes, cells, or metacells. Apply the normalization and feature-selection steps appropriate to the study before calculating similarities.
library(bgns)
set.seed(11)
x <- matrix(rnorm(80 * 6), nrow = 80, ncol = 6)
colnames(x) <- paste0("item_", seq_len(ncol(x)))
x[, 2] <- 0.8 * x[, 1] + 0.2 * rnorm(nrow(x))
x[, 3] <- -0.7 * x[, 1] + 0.3 * rnorm(nrow(x))
x[1:6, 4] <- NA_real_
cm <- bicor(x, min_overlap = 3)
round(cm, 3)
#> item_1 item_2 item_3 item_4 item_5 item_6
#> item_1 1.000 0.956 -0.907 0.010 0.137 -0.007
#> item_2 0.956 1.000 -0.842 0.036 0.076 -0.002
#> item_3 -0.907 -0.842 1.000 -0.021 -0.136 -0.033
#> item_4 0.010 0.036 -0.021 1.000 -0.047 0.091
#> item_5 0.137 0.076 -0.136 -0.047 1.000 -0.029
#> item_6 -0.007 -0.002 -0.033 0.091 -0.029 1.000For a dense input, bicor(x) returns the square matrix of
column similarities. Supplying a second dense matrix produces an
ncol(x) by ncol(y) result. Matrix output
retains undefined entries as NA.
An absolute-score cutoff retains associations of either sign:
edges <- bicor(x, tidy = TRUE, threshold = 0.5)
edges
#> col1 col2 cor
#> 1 item_1 item_2 0.9564391
#> 2 item_1 item_3 -0.9066908
#> 3 item_2 item_3 -0.8424723The table reports column identifiers in col1 and
col2, and the signed score in cor. A
self-comparison reports each eligible pair once, without the diagonal.
For bicor(x, y, tidy = TRUE), the identifiers refer to
x and y, respectively.
kn <- bicor_knn(x, knn = 2, threshold = 0)
head(kn)
#> col1 col2 val rank
#> 1 item_1 item_2 0.95643913 1
#> 2 item_1 item_5 0.13657572 2
#> 3 item_2 item_1 0.95643913 1
#> 4 item_2 item_5 0.07624946 2
#> 5 item_4 item_6 0.09099176 1
#> 6 item_4 item_2 0.03638161 2Here, col1 denotes the target and col2 its
selected neighbor. val is the signed score, and
rank orders neighbors within each target. Selection is
directed: an edge need not be reciprocal. Self-neighbors are excluded
when y is omitted, and fewer than knn edges
are returned if too few candidates qualify.
The two selection rules are distinct:
| Output | Eligibility rule | Ordering |
|---|---|---|
| Correlation edges | abs(cor) >= threshold |
No neighbor ranking |
| KNN edges | val > threshold |
Decreasing signed score within target |
Consequently, a strong negative association can survive an absolute
cutoff but fail a positive KNN cutoff. The default
threshold = -Inf admits all finite scores, including
negative values. The argument does not filter a dense correlation
matrix.
To retain a defined robust scale, construct the sparse example by setting 20% of the simulated measurements to zero. Convert the result to sparse storage after modifying the values.
set.seed(12)
x_zero <- x
index <- sample.int(length(x_zero), floor(0.2 * length(x_zero)))
x_zero[index] <- 0
xs <- Matrix::Matrix(x_zero, sparse = TRUE)
edges_sparse <- bicor(xs, tidy = TRUE, threshold = 0.5)
kn_sparse <- bicor_knn(xs, knn = 2, threshold = 0)
head(edges_sparse)
#> col1 col2 cor
#> 1 item_1 item_2 0.6677082
#> 2 item_1 item_3 -0.6881657
#> 3 item_2 item_3 -0.6439078
head(kn_sparse)
#> col1 col2 val rank
#> 1 item_1 item_2 0.66770825 1
#> 2 item_1 item_4 0.10508486 2
#> 3 item_2 item_1 0.66770825 1
#> 4 item_2 item_5 0.01126153 2
#> 5 item_3 item_6 0.03957315 1
#> 6 item_4 item_1 0.10508486 1Sparse calculations accept double-valued Matrix classes
that can be represented as dgCMatrix. Implicit entries
contribute observed zeros. Sparse inputs require edge or KNN output;
dense correlation-matrix output requires dense inputs. Internal working
panels may nevertheless be dense.
For cross-matrix selection, align the observations of both inputs before calling the function. Row order is positional; row names are not matched.
set.seed(13)
y <- cbind(
target_A = x[, 1] + rnorm(nrow(x), sd = 0.2),
target_B = x[, 3] + rnorm(nrow(x), sd = 0.2)
)
kn_xy <- bicor_knn(
xs, y, knn = 2, threshold = 0.5,
bipartite_levels = "separate"
)
kn_xy
#> col1 col2 val rank
#> 1 target_A item_1 0.8670493 1
#> 2 target_A item_2 0.7933726 2
#> 3 target_B item_3 0.8340956 1Each column of y is a target whose candidates are
columns of xs. Accordingly, col1 carries
target names and col2 carries source names.
bipartite_levels = "separate" preserves these distinct name
sets. Either input may use dense or sparse storage.
For each column, the implementation estimates a median and scaled
median absolute deviation (MAD) from its finite observations. The scale
is 1.4826 * median(abs(x - median(x))); biweight deviations
use a tuning multiplier of 9. These columnwise estimates and weights
remain fixed across pairwise comparisons.
With pairwise.complete.obs = TRUE, the numerator
combines weighted deviations only at observations finite in both
columns. NA, NaN, and infinities are excluded;
zeros are retained. The denominator is selected separately:
use_intersection_denominator = FALSE uses sums of
squared weighted deviations over each column’s full set of finite
observations.use_intersection_denominator = TRUE restricts both sums
to the shared finite observations, without re-estimating the columnwise
weights.The following example changes only the denominator convention:
x_missing <- x[, 1:2]
x_missing[1:20, 2] <- NA_real_
r_column <- bicor(x_missing, use_intersection_denominator = FALSE)[1, 2]
r_shared <- bicor(x_missing, use_intersection_denominator = TRUE)[1, 2]
c(column_denominator = r_column, shared_denominator = r_shared)
#> column_denominator shared_denominator
#> 0.8345499 0.9586580For fully observed pairs, the two conventions coincide.
min_overlap specifies the required count of shared finite
observations, independently of their robust weights; the default is 3.
It applies to complete data too.
n_shared <- sum(is.finite(x_missing[, 1]) & is.finite(x_missing[, 2]))
n_shared
#> [1] 60
bicor(x_missing, min_overlap = n_shared + 1)[1, 2]
#> [1] NA
bicor(x_missing, pairwise.complete.obs = FALSE)[1, 2]
#> [1] NAThe first correlation is undefined because the overlap requirement
exceeds the available observations. The second is undefined because
pairwise.complete.obs = FALSE excludes comparisons
involving an incomplete column. This option does not delete incomplete
rows across the entire matrix.
A zero MAD or undefined normalization makes bicor unavailable. This can occur even in a nonconstant column when most measurements are identical. Sparse storage does not alter that statistical condition.
z <- cbind(
variable = x[, 1],
constant = rep(1, nrow(x)),
zero_heavy = c(rep(0, 60), seq_len(20)),
all_missing = rep(NA_real_, nrow(x))
)
diag(bicor(z))
#> variable constant zero_heavy all_missing
#> 1 NA NA NAUndefined comparisons remain NA in matrix output,
including ineligible diagonal entries, and are omitted from edge and KNN
tables. Inspect column variability when expected vertices have no
reported neighbors.
The default KNN path (direct_sparse = TRUE) retains
bounded candidate sets while processing similarities in panels. It
avoids retaining a full dense similarity matrix, but exact self-KNN
still evaluates a quadratic number of candidate pairs. A thresholded
correlation table can itself approach quadratic size if most pairs
qualify.
BGNS_MEM_MB guides panel scratch allocation and defaults
to 256 MB. Total process memory also includes inputs, returned results,
preprocessing, and numerical-library workspace.
BGNS_NUM_THREADS limits the package’s OpenMP regions, with
a default of at most two threads; BLAS threading is configured
independently. Builds without OpenMP execute those regions serially.
When changing runtime controls, set them before the first computation:
Optional SIMD paths depend on the compiled target and runtime
processor. See help("bgns") for additional controls.
Specify the scientific overlap criterion through
min_overlap rather than an environment variable.
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] bgns_0.4.3 rmarkdown_2.32
#>
#> loaded via a namespace (and not attached):
#> [1] digest_0.6.39 R6_2.6.1 fastmap_1.2.0 Matrix_1.7-6
#> [5] xfun_0.61 lattice_0.23-1 maketools_1.3.2 cachem_1.1.0
#> [9] knitr_1.52 htmltools_0.5.9 buildtools_1.0.0 lifecycle_1.0.5
#> [13] cli_3.6.6 grid_4.6.1 sass_0.4.10 jquerylib_0.1.4
#> [17] compiler_4.6.1 sys_3.4.3 tools_4.6.1 evaluate_1.0.5
#> [21] bslib_0.12.0 yaml_2.3.12 otel_0.2.0 jsonlite_2.0.0
#> [25] rlang_1.3.0