Chatomics Field GuideWhat They Don't Teach You

Glossary · Statistics, Artifacts and Pitfalls

Z-score

A z-score tells you how far a value sits from the mean in standard deviations, and it only means what you think it means if the mean and SD it's built from are stable.

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

Also: standard score

Definition

A z-score (standard score) measures how many standard deviations a data point sits from the mean of its dataset: z = (x − μ) / σ, where x is the value, μ is the mean, and σ is the standard deviation. A positive z-score is above the mean, a negative one is below it, and the transformed distribution has mean 0 and standard deviation 1. Because it removes both scale and offset, it lets you compare values measured in different units or ranges, such as two genes with very different absolute expression levels, on one common axis.

You'll meet the z-score the moment you try to compare things measured on different scales: a gene expressed at a mean of 8000 counts next to one expressed at a mean of 12, or a screening plate where well intensities drift batch to batch. The z-score strips out the mean and the spread so everything lands on the same axis, in units of "how many standard deviations from typical."

In practice you'll hit it in two places constantly: ScaleData() in Seurat and sc.pp.scale() in scanpy, both of which z-score your expression matrix as the last step before PCA, and in any heatmap where you want row colors to show relative pattern instead of absolute expression level. Get the "what am I z-scoring, and across what axis" question wrong, and the plot or the PCA downstream will look confidently wrong.

Why it matters

Z-scoring the wrong layer of data, or with an outlier-contaminated mean and SD, changes which genes or samples look like outliers and which principal components look like biology; robust z-score (median/MAD) exists specifically to survive the long tails common in omics data.

Where people get it wrong

Practitioners z-score raw counts instead of normalized, log-transformed values, mistaking it for a substitute for CPM/TPM/DESeq2 normalization rather than a rescaling step that comes after it; they also read heatmap z-score coloring as if it drove the clustering, when clustering runs on the unscaled matrix and z-scoring only sets the color bar afterward.

A concrete example

Z-scoring logCPM values gene-by-gene before a heatmap, and z-scoring a single-cell matrix before PCA, are the two most common versions you'll actually run. Note the mean/SD in each are computed on different axes: per-gene across samples for the heatmap, per-gene across cells for scRNA-seq scaling.

r
# Bulk RNA-seq: z-score logCPM per gene for a heatmap (not for DE testing)
logcpm <- edgeR::cpm(raw_counts, log = TRUE)
z <- t(scale(t(logcpm)))  # scale() z-scores columns, so transpose to do it per gene (row)

# Single-cell: Seurat scales (z-scores) the normalized data before PCA
pbmc3k <- Seurat::NormalizeData(pbmc3k)
pbmc3k <- Seurat::ScaleData(pbmc3k, verbose = FALSE)  # writes to the scale.data layer

# scanpy equivalent, with clipping to limit outlier influence on PCA
import scanpy as sc
sc.pp.normalize_total(adata)
sc.pp.log1p(adata)
sc.pp.scale(adata, zero_center=True, max_value=10)

Related terms

Questions people ask

What is a z-score in simple terms?

It's a value's distance from the mean of its dataset, measured in standard deviations. A z-score of 2 means the value sits two standard deviations above average; -1 means one standard deviation below.

What is z-score normalization in gene expression analysis?

It's the step where you take already-normalized expression values (logCPM, or log-normalized counts in single-cell data) and rescale each gene to mean 0, standard deviation 1. It's used for heatmap coloring and as the input to PCA, not as a replacement for library-size normalization.

Is z-score the same as normalization?

No. Normalization (CPM, TPM, DESeq2 median-of-ratios, TMM) corrects for technical factors like sequencing depth and gene length. Z-scoring is a separate rescaling step, usually applied after normalization, that puts every gene or feature on the same mean-0, SD-1 scale for comparison or visualization.

When should I use a robust z-score instead of a standard one?

Use median and MAD (robust z-score) whenever your data has outliers or a long tail, which is most omics data. A single extreme value drags a standard z-score's mean and SD off center; the robust version, available as normRobZ in the sights package or via robust = TRUE in datawizard::standardize(), resists that.

Does z-scoring change which genes cluster together on a heatmap?

No. Clustering runs on the expression matrix before z-scoring; the z-score is computed per row afterward purely to set the color scale. It changes what you see, not the grouping structure underneath it.

Related pages

Sources

  1. Bioconductor Support: Z-score transformation of normalized RNA-seq data — Z-score produces mean-0, SD-1 distribution and is typically applied to already-normalized (logCPM) values
  2. Seurat v5 Essential Commands — ScaleData() z-scores normalized data into the scale.data layer before PCA
  3. scanpy.pp.scale documentation — pp.scale standardizes to zero mean, unit variance, with zero_center and max_value (clipping) options
  4. HBC Training: RNA-seq data visualization and z-score scaling — Heatmap z-scores are computed per gene, after clustering, affecting only color not grouping
  5. sights R package: normRobZ function (robust z-score normalization) — Standard z-score is sensitive to outliers; robust variant uses median/MAD
  6. datawizard R package: standardize function — z-score formula and robust option