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.
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:
| Method | What it does | Package |
|---|---|---|
| none | leave the NAs; limma fits each protein on the values it has | limma |
| MinDet | fill with a low quantile of each sample (deterministic) | imputeLCMD |
| MinProb | draw from a low-centred distribution per sample | imputeLCMD |
| Perseus-style | draw from a normal shifted 1.8 SD down, width 0.3 SD (Perseus defaults) | 8 lines of R, below |
| QRILC | quantile regression for left-censored data | imputeLCMD |
| kNN | average of the 10 most similar proteins | impute |
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):
| Method | Changed proteins found (of 1,039) | Human false calls | False discovery proportion | E. coli within ±0.5 of truth |
|---|---|---|---|---|
| none | 958 | 13 | 1.3% | 83% |
| kNN | 959 | 11 | 1.1% | 82% |
| MinProb | 923 | 11 | 1.2% | 81% |
| MinDet | 918 | 11 | 1.2% | 82% |
| Perseus-style | 903 | 9 | 1.0% | 80% |
| QRILC | 868 | 9 | 1.0% | 76% |
With MBR, loose filter (4,534 proteins, 8.5% missing; "none" can only test the 3,935 that pass the strict rule):
| Method | Proteins tested | Changed found (of 1,415) | Human false calls | FDP | E. coli median log2FC (truth +2) |
|---|---|---|---|---|---|
| none | 3,935 | 958 | 13 | 1.3% | +1.99 |
| MinDet | 4,534 | 1,102 | 26 | 2.3% | +1.96 |
| MinProb | 4,534 | 1,098 | 30 | 2.7% | +1.96 |
| Perseus-style | 4,534 | 1,052 | 31 | 2.9% | +2.02 |
| QRILC | 4,534 | 1,026 | 28 | 2.7% | +2.28 |
| kNN | 4,534 | 977 | 29 | 2.9% | +1.05 |
Without MBR the data are sparser (12.6% missing under the loose filter) and every pattern gets stronger:
| Loose filter, no MBR | Changed found (of 1,163) | Human false calls | FDP | E. coli median log2FC |
|---|---|---|---|---|
| none (strict subset) | 701 of 733 | 12 | 1.7% | +2.10 |
| MinDet | 854 | 29 | 3.3% | +2.03 |
| MinProb | 855 | 29 | 3.3% | +2.05 |
| Perseus-style | 808 | 30 | 3.6% | +2.11 |
| QRILC | 775 | 27 | 3.4% | +2.47 |
| kNN | 778 | 30 | 3.7% | +0.02 |
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.
Related
- limma vs DEqMS at n = 3, on the same known-answer data
- MaxQuant 2.8 proteinGroups.txt, scored against a known answer: where the MBR on/off runs come from
- Differential expression in proteomics: a complete R pipeline
- After DIA-NN 2.x: report.parquet and pg_matrix on 210 public runs
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)
관련 글
A MaxQuant Run Took Me 10+ Hours on a Windows PC — the Checks I Now Do Before Pressing Start
10월 1일 · 7 min read
ProteomicsDifferential Expression Analysis in Proteomics: A Complete R Pipeline (limma, t-test, ANOVA)
2월 25일 · 9 min read
ProteomicsDIA-NN Quantification Modes Scored on Known Ratios: QuantUMS, Legacy MaxLFQ and Fragment-Level Re-Quantification, With and Without MBR
10월 17일 · 17 min read
ProteomicsProteomics CV: What Replicate CVs Look Like in MaxQuant, FragPipe and DIA-NN (Measured on a Known-Ratio Benchmark)
10월 16일 · 8 min read