This vignette provides a comprehensive mathematical description of the MAGIC algorithm. Understanding these foundations helps users make informed decisions about parameter selection and interpret results correctly.
Single-cell RNA sequencing captures transcriptomic profiles at single-cell resolution, but suffers from technical limitations:
These issues result in sparse, noisy count matrices where biological signal is obscured.
MAGIC leverages the manifold hypothesis: high-dimensional single-cell data lies on a lower-dimensional manifold reflecting biological states. By diffusing information along this manifold, MAGIC recovers missing transcripts while preserving biological structure.
For computational efficiency, MAGIC first reduces dimensionality using PCA:
\[X_{pca} = X \cdot V_k\]
where \(V_k\) contains the top \(k\) principal components (default: 100).
For each cell \(i\), identify its \(k\) nearest neighbors based on Euclidean distance in PCA space:
\[\mathcal{N}_k(i) = \{j : d(x_i, x_j) \leq d_k(x_i)\}\]
where \(d_k(x_i)\) is the distance to the \(k\)-th nearest neighbor.
The affinity between cells is computed using an α-decaying kernel with adaptive bandwidth:
\[K(x_i, x_j) = \exp\left(-\left(\frac{\|x_i - x_j\|_2}{\varepsilon_i}\right)^\alpha\right)\]
where:
Why adaptive bandwidth?
# Visualize α-decaying kernel
x <- seq(0, 3, length.out = 100)
par(mfrow = c(1, 2))
# Effect of distance
plot(x, exp(-x), type = "l", lwd = 2, col = "#e74c3c",
xlab = "Normalized Distance (d/ε)", ylab = "Affinity K(x,y)",
main = "α-Decaying Kernel (α = 1)")
abline(h = 0.5, lty = 2, col = "gray")
abline(v = log(2), lty = 2, col = "gray")
# Effect of α
plot(x, exp(-x^0.5), type = "l", lwd = 2, col = "#3498db",
xlab = "Normalized Distance (d/ε)", ylab = "Affinity K(x,y)",
main = "Effect of α Parameter", ylim = c(0, 1))
lines(x, exp(-x^1), lwd = 2, col = "#e74c3c")
lines(x, exp(-x^2), lwd = 2, col = "#2ecc71")
legend("topright", legend = c("α = 0.5", "α = 1", "α = 2"),
col = c("#3498db", "#e74c3c", "#2ecc71"), lwd = 2)The kernel matrix is row-normalized to create a Markov transition matrix:
\[P = D^{-1}K\]
where \(D\) is the diagonal degree matrix with \(D_{ii} = \sum_j K_{ij}\).
Properties of P:
The diffusion operator is powered \(t\) times:
\[X_{imputed} = P^t X\]
Each power of \(P\) propagates information one step along the graph:
# Simulate diffusion effect
set.seed(42)
n <- 100
x <- c(rnorm(50, -2, 0.5), rnorm(50, 2, 0.5))
y <- c(rnorm(50, 0, 0.5), rnorm(50, 0, 0.5))
# Add noise
x_noisy <- x + rnorm(n, 0, 0.3)
y_noisy <- y + rnorm(n, 0, 0.3)
par(mfrow = c(1, 2))
plot(x_noisy, y_noisy, pch = 16, col = adjustcolor("#3498db", 0.6),
main = "Before Diffusion (Noisy)", xlab = "Gene 1", ylab = "Gene 2")
# Simple smoothing simulation
x_smooth <- 0.7 * x + 0.3 * x_noisy
y_smooth <- 0.7 * y + 0.3 * y_noisy
plot(x_smooth, y_smooth, pch = 16, col = adjustcolor("#e74c3c", 0.6),
main = "After Diffusion (Denoised)", xlab = "Gene 1", ylab = "Gene 2")When t = "auto", MAGIC uses Procrustes
analysis to find optimal diffusion time.
Given two matrices \(X\) and \(Y\), Procrustes analysis finds the optimal rotation \(R\) that minimizes:
\[d_{Procrustes}(X, Y) = \|X - YR\|_F^2\]
after centering and normalizing both matrices.
MAGIC iteratively applies diffusion and monitors:
\[\Delta_t = d_{Procrustes}(X_t, X_{t+1})\]
The algorithm stops when \(\Delta_t < \theta\) (default: 0.001), indicating convergence.
Computes imputation in full gene space:
\[X_{imputed} = P^t X\]
Advantages: - Preserves all gene-gene relationships - No approximation error
Disadvantages: - Memory intensive for large gene sets - Slower computation
Operates in PCA space and projects back:
\[X_{imputed} = P^t X_{pca} \cdot V_k^T\]
Advantages: - Much faster for large datasets - Lower memory footprint
Disadvantages: - Some information loss from PCA projection
The diffusion process has an elegant spectral interpretation. If \(P\) has eigendecomposition:
\[P = \sum_i \lambda_i v_i v_i^T\]
Then:
\[P^t = \sum_i \lambda_i^t v_i v_i^T\]
Since \(|\lambda_i| \leq 1\):
| Scenario | Recommended Parameters |
|---|---|
| Standard scRNA-seq | t = 3-5, knn = 5,
decay = 1 |
| Highly sparse data | t = 5-10, knn = 10 |
| Rare populations | t = 2-3, decay = 2 |
| Large datasets | solver = "approximate", npca = 50 |
✅ Good use cases: - Visualizing gene-gene relationships - Trajectory analysis preprocessing - Recovering dropout in sparse data
❌ Avoid when: - Performing differential expression (use raw counts) - Data is already dense - Preserving exact count values is critical
van Dijk, D., et al. (2018). Recovering Gene Interactions from Single-Cell Data Using Data Diffusion. Cell, 174(3), 716-729.e27.
Coifman, R.R., & Lafon, S. (2006). Diffusion maps. Applied and Computational Harmonic Analysis, 21(1), 5-30.
Moon, K.R., et al. (2019). Visualizing structure and transitions in high-dimensional biological data. Nature Biotechnology, 37(12), 1482-1492.
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04 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] MAGICR_1.0.0 rmarkdown_2.31
#>
#> loaded via a namespace (and not attached):
#> [1] cli_3.6.6 knitr_1.51 rlang_1.2.0 xfun_0.59
#> [5] otel_0.2.0 jsonlite_2.0.0 listenv_1.0.0 buildtools_1.0.0
#> [9] htmltools_0.5.9 maketools_1.3.2 sys_3.4.3 sass_0.4.10
#> [13] grid_4.6.1 evaluate_1.0.5 jquerylib_0.1.4 fastmap_1.2.0
#> [17] yaml_2.3.12 lifecycle_1.0.5 compiler_4.6.1 codetools_0.2-20
#> [21] irlba_2.3.7 Rcpp_1.1.1-1.1 future_1.70.0 lattice_0.22-9
#> [25] digest_0.6.39 R6_2.6.1 RANN_2.6.2 parallelly_1.48.0
#> [29] parallel_4.6.1 bslib_0.11.0 Matrix_1.7-5 tools_4.6.1
#> [33] globals_0.19.1 cachem_1.1.0