Chatomics Field GuideWhat They Don't Teach You

Glossary · Visualization

PCA plot (scores plot)

The plot that tells you in five seconds whether your experiment worked, or whether you're about to run differential expression on a batch effect.

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

Also: PC1 vs PC2, autoplot

Definition

A PCA scores plot is a scatterplot of samples (or cells), positioned by their principal component scores, usually PC1 on the x-axis and PC2 on the y-axis. Each point is one sample; distance between points reflects overall similarity across the whole expression profile, not similarity in any single gene. PC1 captures the largest share of variance in the data, PC2 the largest remaining share orthogonal to PC1, and so on for as many components as you have samples. It is the "scores" half of a PCA: the other half, the loadings, tells you which genes are pulling samples apart along each axis.

You'll meet the PCA plot in the first five minutes of almost any RNA-seq or single-cell QC report: it's the plot that answers "do my replicates cluster, and does my biology separate cleanly from everything else?" Before you trust a differential expression call or interpret a downstream UMAP, this is the sanity check that tells you whether the experiment worked at the sample level.

It also decides your next move. If PC1 splits samples by condition, you're in good shape. If PC1 instead splits samples by batch, extraction date, or sequencing depth, you have a confound to resolve before any p-value from that dataset is trustworthy.

Why it matters

Get this plot wrong and you either chase a batch effect as if it were biology, or you strip out real biology because you mistook it for a batch effect. In one real case, a PCA plot from a control-vs-KO bulk RNA-seq experiment showed PC1 cleanly separating replicate 1 from replicate 2 from replicate 3, and PC2 separating control from KO. The tempting move is to run limma::removeBatchEffect() on the matrix before DE testing. Usually that's the wrong call: if replicate is already a term in the DESeq2 or edgeR design formula, the model already accounts for it during fitting, and pre-correcting the matrix on top of that risks removing variance that overlaps with the condition you actually care about. Reading the plot correctly, PC1 is a nuisance variable already handled by the design, PC2 is the biology, changes the decision from "correct the data" to "leave it alone, the model already handles it."

Where people get it wrong

The mistake that costs the most time is running ggfortify::autoplot() on a prcomp object and reading the axis values as the actual PC scores. autoplot() rescales by default: PCi' = PCi / sd(PCi) * sqrt(n − 1). The clustering pattern on screen is still valid, but the axis numbers are not the raw scores, and if you plan to pull per-sample PC1 values downstream, say to correlate PC1 with library size, you need pca$x[, 1] from prcomp() directly rather than whatever autoplot() drew. The second common mix-up is scores versus loadings: the scores plot shows where samples sit, the loadings plot shows which genes pushed them there. A tight cluster on a scores plot tells you samples are similar; it tells you nothing about which genes drove that similarity until you check the loadings (pca$rotation in base R, or VizDimLoadings() in Seurat).

A concrete example

Twenty bulk RNA-seq samples, tumor vs normal, quantified into a gene-by-sample count matrix. Run PCA on variance-stabilized counts, not raw counts: on raw counts, sequencing depth alone can correlate with PC1 at r near 0.98, drowning out the tumor-vs-normal signal you actually want to see. Use vst() with blind dispersion estimation so the transformation doesn't peek at your design, then plot PC1 vs PC2 colored by condition.

r
library(DESeq2)
vsd <- vst(dds, blind = TRUE)
pca <- prcomp(t(assay(vsd)), center = TRUE, scale. = FALSE)
scores <- data.frame(pca$x[, 1:2], condition = colData(dds)$condition)
ggplot(scores, aes(PC1, PC2, color = condition)) +
  geom_point(size = 3) +
  labs(x = "PC1", y = "PC2")

Related terms

Questions people ask

What's the difference between a PCA scores plot and a PCA loadings plot?

The scores plot places samples in PC space, so each point is a sample and distance means overall expression similarity. The loadings plot places genes, showing which genes contribute most to each PC. You need the scores plot to see if samples cluster as expected, and the loadings plot to find out which genes are responsible for a separation you see.

Why do my PC1 and PC2 values look different after autoplot()?

ggfortify::autoplot() rescales PC scores by default using PCi' = PCi / sd(PCi) * sqrt(n − 1), so the axis numbers you see are not the raw prcomp() output. The clustering shape is unaffected, but if you need actual PC1/PC2 values, pull them from pca$x directly instead of reading them off the autoplot() axes.

Should I use raw counts or normalized counts for a PCA plot in RNA-seq?

Use normalized data, log2 CPM or a variance-stabilizing transform like DESeq2's vst(), not raw counts. On raw counts, PC1 often tracks library size instead of biology, and normalization is what lets the real biological signal surface on the early PCs.

My PCA plot separates samples by batch instead of condition. Should I correct it?

Not automatically. First check whether batch is already a term in your differential expression design formula; if it is, the model handles it during fitting and pre-correcting the count matrix with something like limma::removeBatchEffect() before that can strip out variance that overlaps with your condition of interest. Correcting the matrix is more defensible when batch isn't in the design at all, or for visualization only.

How many PCs should I actually look at, not just PC1 vs PC2?

Don't stop at PC1 vs PC2 by default. Check the variance explained per component, and if PC3 or PC4 still carry a meaningful share of variance, plot those too, since a batch effect or an unexpected subgroup sometimes only shows up on a later component.

Related reading on the blog

Sources

  1. Batch Effect: To Correct or Not for Bulk RNA-seq Data — the replicate-vs-condition PCA case and the removeBatchEffect decision
  2. PCA analysis on TCGA bulk RNAseq data — sequencing depth correlating with PC1 in uncorrected raw-count PCA
  3. autoplot.pca_common: Autoplot PCA-likes in ggfortify — autoplot() default parameters and rescaling behavior
  4. DESeq2 Vignette: Differential analysis of count data — vst() with blind dispersion estimation for PCA QC
  5. Seurat - Guided clustering tutorial (pbmc3k) — scores vs loadings distinction, VizDimLoadings()