Chatomics Field GuideWhat They Don't Teach You

Glossary · Programming and Math Basics

Matrix factorization

Every PCA plot, NMF gene module, and UMAP embedding you've ever generated is the same operation wearing a different constraint.

By Ming "Tommy" Tang, Director of Bioinformatics in Big Pharma · Reviewed September 2026 · 3 min read

Also: NMF, low-rank approximation

Definition

Matrix factorization decomposes a data matrix X, typically genes by samples or genes by cells, into the product of two smaller matrices, X ≈ W × H, where W holds genes-by-latent-factors and H holds latent-factors-by-samples. The latent factors are inferred from correlation structure in the data, not labeled in advance; a 20,000-gene by 500-sample RNA-seq matrix might factor down to 5 latent factors that track hidden pathways. PCA (eigenvalue decomposition of the covariance matrix, computed via SVD), NMF, and ICA are all specific instances of this decomposition, distinguished by the constraints placed on W and H: PCA enforces orthogonal components that maximize explained variance, NMF forces every entry non-negative, and ICA seeks statistically independent components.

You meet this term the moment you run RunPCA() in Seurat or scanpy.pp.pca() in scanpy and get back a matrix of "principal components" with no clear sense of what math just happened. It's matrix factorization: your gene-by-sample or gene-by-cell matrix X got decomposed into smaller matrices whose product approximates X. PCA, NMF, ICA, and the SVD that feeds UMAP and t-SNE are all this same operation, just with different constraints on the pieces.

The choice of method is not cosmetic. It decides whether the hidden factors you recover can go negative (repression as well as activation) or must stay positive (activation only), which changes what biology you're even able to find. Get this wrong and you'll either miss real repression signal or chase gene modules that are artifacts of a constraint you didn't know you'd imposed.

Why it matters

Get the constraint wrong and you get the wrong biology. NMF's non-negativity constraint means a gene can only load positively onto a component, so a single NMF factor can represent "these genes are co-overexpressed" but cannot represent "this gene goes up while that one goes down" within the same factor. That's exactly why NMF was used to reveal breast cancer expression subtypes: real counts can't be negative, so forcing the factorization to respect that constraint produces components that map cleanly onto biological subtypes. Run ICA or PCA instead, and you'll pick up components with negative loadings that mix repression and activation into one axis, which is a different, sometimes more informative, sometimes noisier, picture.

The preprocessing you feed into the factorization matters just as much as the method. Seurat's default pipeline (NormalizeData with scale.factor=10000, FindVariableFeatures with 2000 variable genes, ScaleData, then RunPCA) exists because PCA's covariance-based eigen decomposition assumes roughly continuous, centered data, not raw zero-inflated UMI counts. Skip the normalization and scaling steps and your top components will track sequencing depth, not cell type.

Where people get it wrong

The common mistake is treating PCA and NMF as interchangeable "dimensionality reduction," picking whichever a tutorial used, without registering that they answer different questions. PCA loadings run negative and are optimized to explain variance, not to be biologically interpretable per se; NMF factors are constrained non-negative and each one is meant to read as a coherent, overexpressed gene module. If you cluster on PCA components and then try to interpret the "top negative genes" of a component the way you'd interpret an NMF module, you're importing a non-negativity assumption that PCA never made.

A second, quieter version of this mistake: running PCA directly on raw or lightly normalized sparse counts and treating the resulting components as biology. In scRNA-seq, sequencing depth varies from roughly 400 to 20,000 UMIs per cell, and if you skip proper normalization, your first principal component often correlates with depth, not cell identity, and you won't notice unless you specifically check that correlation.

A concrete example

Factor a gene-by-sample expression matrix into 5 latent components with NMF, then extract which genes drive each component using a loading threshold.

r
library(NMF)
res <- nmf(expr_matrix, 5)
basis <- basis(res)  # genes x 5 features (W)
coef <- coef(res)    # 5 features x samples (H)

# gene modules: genes with high weight on one component
module1_genes <- rownames(basis)[basis[,1] > mean(basis[,1]) + 3*sd(basis[,1])]

Related terms

Questions people ask

What is the difference between matrix factorization and PCA?

PCA is one specific matrix factorization method: eigenvalue decomposition of the covariance matrix, computed in practice via SVD, that produces orthogonal components ranked by variance explained. Matrix factorization is the broader operation; NMF, ICA, and correspondence analysis are other instances of it with different constraints.

Should I use NMF or PCA for gene expression data?

Use NMF when you want each latent factor to read as a coherent, non-negative gene module, such as identifying overexpressed pathway signatures or subtypes. Use PCA when you need a fast, variance-maximizing embedding for clustering or visualization and don't need the components to be individually interpretable as gene sets.

What do W and H mean in NMF?

W is genes by latent factors (how strongly each gene loads onto each factor) and H is latent factors by samples (how strongly each factor is expressed in each sample). In R's NMF package, basis(res) returns W and coef(res) returns H.

Why does sparsity matter for matrix factorization in single-cell data?

scRNA-seq matrices are often more than 90% zeros, and running dense-matrix eigen decomposition on that directly is wasteful and can be numerically unstable. Sparse formats like R's dgCMatrix or Python's scipy.sparse csr_matrix, combined with truncated SVD solvers like irlba, let you factor large sparse matrices without materializing them as dense arrays.

How many components should I keep after factorization?

There's no universal number: Seurat's ElbowPlot approach typically keeps 7 to 12 principal components for a roughly 3,000-cell dataset, chosen where variance explained flattens out. For NMF, rank selection is less standardized and usually set by testing a small range of values and checking which produces stable, interpretable gene modules.

Related pages

Related reading on the blog

Sources

  1. Matrix Factorization for single-cell RNAseq data — NMF formula, gene module extraction via mean ± 3 SD thresholding, and preprocessing choices for factorization
  2. Seurat PBMC 3K Tutorial — NormalizeData/FindVariableFeatures/ScaleData/RunPCA pipeline and ElbowPlot component selection
  3. Scanpy PCA Function Documentation — scanpy.pp.pca solver options and zero_center behavior
  4. NMF: A Framework for Performing Non-Negative Matrix Factorization — NMF R package, basis()/coef() extraction of W and H
  5. irlba: Fast and memory efficient methods for truncated SVD and PCA — Truncated SVD for large sparse matrices