---
title: "bgns: Biweight Graph and Network Statistics"
author: "Aditya Kshirsagar"
output:
  rmarkdown::html_vignette:
    toc: true
vignette: >
  %\VignetteIndexEntry{bgns: Biweight Graph and Network Statistics}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", error = FALSE)
```

## Scope

`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.

## Installation

Source installation requires R >= 4.3.0, a C++17 compiler, and the numerical
libraries configured for R. Install the appropriate
[Rtools](https://cran.r-project.org/bin/windows/Rtools/) on Windows or follow
the [R for macOS toolchain instructions](https://mac.r-project.org/tools/).
OpenMP support is optional. WGCNA is not a runtime dependency.

```{r installation, eval=FALSE}
install.packages("remotes")
remotes::install_github("metaddict/bgns")
```

## From measurements to a correlation graph

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.

```{r measurements}
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)
```

For 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`.

### Selecting correlation edges

An absolute-score cutoff retains associations of either sign:

```{r correlation-edges}
edges <- bicor(x, tidy = TRUE, threshold = 0.5)
edges
```

The 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.

### Selecting nearest neighbors

```{r nearest-neighbors}
kn <- bicor_knn(x, knn = 2, threshold = 0)
head(kn)
```

Here, `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.

## Sparse measurements and cross-matrix neighbors

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.

```{r sparse-measurements}
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)
head(kn_sparse)
```

Sparse 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.

```{r cross-matrix-neighbors}
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
```

Each 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.

## Finite overlap and normalization

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:

```{r overlap-normalization}
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)
```

For 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.

```{r overlap-eligibility}
n_shared <- sum(is.finite(x_missing[, 1]) & is.finite(x_missing[, 2]))
n_shared
bicor(x_missing, min_overlap = n_shared + 1)[1, 2]
bicor(x_missing, pairwise.complete.obs = FALSE)[1, 2]
```

The 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.

## Undefined scores

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.

```{r degenerate-columns}
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))
```

Undefined 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.

## Resource configuration

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:

```{r runtime-settings, eval=FALSE}
Sys.setenv(BGNS_MEM_MB = "256", BGNS_NUM_THREADS = "2")
```

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.

## Session information

```{r session-info}
sessionInfo()
```
