Proteomics

Missing Values in Proteomics: Six Imputation Methods Scored Against a Known Answer

MinDet, MinProb, Perseus-style, QRILC, kNN or no imputation at all? I ran all six on a public three-species benchmark with known ratios. With a strict filter, imputation barely mattered; with a loose one it found more changes, doubled the false discoveries, and kNN erased a real 4-fold increase.

·10 min read
#proteomics#missing values#imputation#MNAR#MinProb#QRILC#kNN#Perseus#limma#label-free quantification

Rewritten in October 2026. The earlier version of this post showed a benchmark table ("4,800 proteins, 12 samples") with no dataset, code or accession behind it. It was not a real run, so it is gone. This version scores six approaches on a public dataset where the right answer is known, and every number comes from that run.

TL;DR

  • If you filter strictly (a protein must be measured in at least 2 of 3 samples in each group), imputation barely changes anything. Leaving the few remaining gaps to limma did as well as any method, or better.
  • If you filter loosely (2 of 3 in either group), imputation lets you test hundreds more proteins and found about 15% more true changes, but the false discovery proportion roughly doubled (1.3% → 2.3–2.9%) and fold changes got less accurate.
  • kNN is the wrong tool for values missing because they are low. It pulled a real +2 log2 change down to a median of +1.05 with match-between-runs and to +0.02 without it.
  • QRILC overshot the same change (median +2.3 to +2.5). MinDet, MinProb and a Perseus-style down-shift landed closest.

Why values go missing

Two mechanisms dominate in label-free proteomics:

  • Missing because the protein is low (missing not at random, MNAR): its peptides fall below the detection limit in some runs. This is the common case for low-abundance proteins, and the missing value carries information: "low".
  • Missing by chance (missing at random): a peptide was simply not selected or not matched in one run, even though it was there.

Most real data mix both. The point of the test below is that one group of proteins is designed to be MNAR, so you can see what each method does with it.

The test

Data. The public LFQbench-style benchmark PXD028735 (Van Puyvelde et al., Sci Data 2022): six Orbitrap DDA runs, three of mixture A and three of mixture B. A contains human, yeast and E. coli proteins in proportions 65/30/5, B in 65/15/20. So the true log2(B/A) ratios are human 0, yeast −1, E. coli +2. E. coli makes up only 5% of A, so its proteins are often missing in A: exactly the "missing because low" case.

Processing. MaxQuant 2.8.1, LFQ intensities, run twice: with and without match-between-runs (MBR). The runs are the ones from my MaxQuant benchmark post. Zeros set to NA, log2.

Two filters, applied before imputation:

  • Strict: at least 2 of 3 values in both groups.
  • Loose: at least 2 of 3 values in either group. This keeps proteins that are present in one condition and absent in the other, the ones imputation is usually meant to rescue.

Six approaches:

MethodWhat it doesPackage
noneleave the NAs; limma fits each protein on the values it haslimma
MinDetfill with a low quantile of each sample (deterministic)imputeLCMD
MinProbdraw from a low-centred distribution per sampleimputeLCMD
Perseus-styledraw from a normal shifted 1.8 SD down, width 0.3 SD (Perseus defaults)8 lines of R, below
QRILCquantile regression for left-censored dataimputeLCMD
kNNaverage of the 10 most similar proteinsimpute

Scoring. For each method I ran limma (A vs B) and counted, at adjusted p < 0.05 and |log2FC| ≥ 0.5, how many of the truly changed yeast and E. coli proteins were called, and how many human proteins (truly unchanged) were called by mistake. The calls used limma's fold changes as they were. For the ratio figures (median log2 ratios and the fraction within ±0.5 of the truth), I centred fold changes on the human median, because LFQ normalisation shifts the unchanged human background slightly in this mixture. Random methods used a fixed seed.

Results

With MBR, strict filter (3,935 proteins, 2.1% of values missing):

MethodChanged proteins found (of 1,039)Human false callsFalse discovery proportionE. coli within ±0.5 of truth
none958131.3%83%
kNN959111.1%82%
MinProb923111.2%81%
MinDet918111.2%82%
Perseus-style90391.0%80%
QRILC86891.0%76%

With MBR, loose filter (4,534 proteins, 8.5% missing; "none" can only test the 3,935 that pass the strict rule):

MethodProteins testedChanged found (of 1,415)Human false callsFDPE. coli median log2FC (truth +2)
none3,935958131.3%+1.99
MinDet4,5341,102262.3%+1.96
MinProb4,5341,098302.7%+1.96
Perseus-style4,5341,052312.9%+2.02
QRILC4,5341,026282.7%+2.28
kNN4,534977292.9%+1.05

Without MBR the data are sparser (12.6% missing under the loose filter) and every pattern gets stronger:

Loose filter, no MBRChanged found (of 1,163)Human false callsFDPE. coli median log2FC
none (strict subset)701 of 733121.7%+2.10
MinDet854293.3%+2.03
MinProb855293.3%+2.05
Perseus-style808303.6%+2.11
QRILC775273.4%+2.47
kNN778303.7%+0.02

E. coli log2 fold change by imputation method, loose filter, with and without MBR Each box is the E. coli proteins under the loose filter; the dashed line is the true +2. Boxes for "none" contain only the proteins that also pass the strict filter.

What the numbers say

1. A strict filter makes the choice nearly irrelevant. Under the strict filter only 2–3% of values are missing, and every method found 84–92% of the true changes with MBR (80–96% without) at 1–2% false discoveries. Leaving the gaps to limma was at the top. If your question can live with the proteins that pass a strict filter, you may not need to impute at all.

2. A loose filter buys proteins and costs accuracy. Imputing after a loose filter let MinDet find 144 more true changes than "none" with MBR (1,102 vs 958) and 153 more without. The price: false discoveries doubled, and fewer fold changes landed close to the truth. That can be a fair trade for discovery, as long as you know you are making it.

3. kNN erased a real change. E. coli proteins are missing in A because they are scarce there. kNN fills those gaps with values borrowed from similar proteins that were measured, so A suddenly looks as high as B and the fold change collapses toward zero. Without MBR, the median E. coli change went from the true +2 to +0.02. kNN assumes values are missing at random; this test violates that on purpose, and real low-abundance proteins violate it all the time.

4. QRILC pushed the other way. It imputed values low enough to exaggerate the change (median +2.28 with MBR, +2.47 without).

5. MinDet, MinProb and the Perseus-style down-shift behaved alike and stayed closest to the truth among the imputation methods.

What I would do

  • Default: a strict valid-value filter and no imputation; limma handles the remaining gaps.
  • If you need proteins that disappear in one condition (often the most interesting ones): a loose filter plus a left-censored method (MinDet, MinProb or a Perseus-style down-shift), and report that you did so. Check the volcano plot for proteins whose significance comes entirely from imputed values.
  • Avoid kNN for intensity data with many low-abundance gaps. It is reasonable only when values really are missing at random.
  • Don't impute before log2, and don't impute on raw intensities. Everything above happened on log2 values.

Code

The core of the run (the full script is longer only because it loops over methods and filters):

library(limma); library(imputeLCMD); library(impute)

# m: proteins x samples, log2 LFQ with NA for missing; groups: A A A B B B
keep_strict <- rowSums(!is.na(m[, 1:3])) >= 2 & rowSums(!is.na(m[, 4:6])) >= 2
keep_loose  <- rowSums(!is.na(m[, 1:3])) >= 2 | rowSums(!is.na(m[, 4:6])) >= 2

perseus <- function(x, width = 0.3, shift = 1.8) {    # per sample, like Perseus' defaults
  for (j in seq_len(ncol(x))) {
    v <- x[, j]; mu <- mean(v, na.rm = TRUE); s <- sd(v, na.rm = TRUE)
    x[is.na(v), j] <- rnorm(sum(is.na(v)), mu - shift * s, width * s)
  }
  x
}

set.seed(1)
x <- m[keep_loose, ]
x_mindet  <- impute.MinDet(x, q = 0.01)
x_minprob <- impute.MinProb(x, q = 0.01, tune.sigma = 1)
x_perseus <- perseus(x)
x_qrilc   <- impute.QRILC(x)[[1]]
x_knn     <- impute.knn(x, k = 10, rowmax = 0.9, colmax = 0.9)$data   # see finding 3 before using

grp <- factor(rep(c("A", "B"), each = 3))
fit <- eBayes(lmFit(x_mindet, model.matrix(~ grp)))
res <- topTable(fit, coef = 2, number = Inf)

Limits

  • One dataset, one instrument type (Orbitrap DDA), three replicates per group, MaxQuant LFQ. DIA data have far fewer gaps and may behave differently.
  • The MNAR pattern here is clean by design: E. coli is scarce in one condition. Real experiments mix mechanisms.
  • I did not test msImpute (its installation failed on this R 4.3 setup) or BPCA. Treat this as a test of six common choices, not of all of them.
  • "False discovery proportion" counts called human proteins; a few may carry small real shifts from the normalisation, so the true error rate could be slightly lower.

Code and outputs: the scripts and result tables for this post are on GitHub in sbmlab-benchmarks/imputation-hye.

Preprint: this benchmark, from search engine to enrichment background, is written up as a preprint: Kang B. (2026), Scoring the downstream chain of small-n label-free proteomics against known ratios, Research Square, doi:10.21203/rs.3.rs-11370244/v1. It has not been peer reviewed.

References

  • Van Puyvelde B. et al. (2022). A comprehensive LFQ benchmark dataset on modern day acquisition strategies in proteomics. Scientific Data 9:126. (PXD028735)
  • Lazar C. et al. (2016). Accounting for the multiple natures of missing values in label-free quantitative proteomics data sets to compare imputation strategies. J Proteome Res 15:1116–1125. (imputeLCMD methods)
  • Tyanova S. et al. (2016). The Perseus computational platform for comprehensive analysis of (prote)omics data. Nat Methods 13:731–740.
  • Troyanskaya O. et al. (2001). Missing value estimation methods for DNA microarrays. Bioinformatics 17:520–525. (kNN imputation)

관련 글