Skip to content

Olink NPX

Olink’s proximity extension assay measures proteins with pairs of antibodies, each carrying a DNA oligo. When both antibodies bind their target, the oligos extend into a barcode, and the barcode is quantified by qPCR or by sequencing. The number that comes out is NPX, Normalized Protein eXpression, a relative concentration on the log2 scale. One unit of NPX difference is a two-fold difference in protein concentration.

Olink’s software exports the results of a run as a long table: one row per sample per assay. The export comes as .xlsx, .csv or .parquet, and the columns are the same in each.

Column Content
SampleID The sample identifier
OlinkID The assay identifier, e.g. OID00101
UniProt The UniProt accession of the target protein
Assay The assay name, usually the gene symbol
Panel The panel name, e.g. Inflammation
PlateID The plate the sample ran on
QC_Warning PASS or WARN, from the built-in controls
LOD The limit of detection for the assay on this plate
NPX The measurement itself, log2 scale
MissingFreq The fraction of samples missing for the assay

The example below reads a synthetic export with the same layout: two plates, 16 samples, 6 assays.

library(OlinkAnalyze)
npx <- read_NPX("/opt/data/npx_export.csv")
dim(npx)
head(as.data.frame(npx)[, c("SampleID", "Assay", "PlateID", "QC_Warning",
"LOD", "NPX")], 4)
[1] 96 12
SampleID Assay PlateID QC_Warning LOD NPX
1 Sample_01 IL6 Plate1 PASS 0.8 4.59
2 Sample_01 TNF Plate1 PASS 1.2 2.91
3 Sample_01 CXCL8 Plate1 PASS 0.5 6.09
4 Sample_01 LEP Plate1 PASS 2.5 3.33

read_NPX detects the export layout, checks the columns and returns a tibble. It reads the xlsx and parquet variants with the same call. Real exports add project columns the core facility merged in, such as treatment groups and time points, and read_NPX carries them through untouched.

Two columns do the quality control work, and both come from the file rather than from analysis. QC_Warning flags samples whose built-in controls failed: Olink plates carry extension and detection controls, and a WARN means the measurement for that sample is suspect. NPX values below the LOD are reported, not censored, and the convention is to keep them and flag them rather than delete them.

library(dplyr)
npx %>% count(QC_Warning)
# Values below the LOD are reported in the file.
below_lod <- npx %>% filter(NPX < LOD)
nrow(below_lod)
# Per assay: the median and the count below LOD.
npx %>%
group_by(Assay) %>%
summarise(median_npx = median(NPX),
below_lod_n = sum(NPX < LOD),
.groups = "drop")
# A tibble: 2 × 2
QC_Warning n
<chr> <int>
1 PASS 90
2 WARN 6
[1] 16
# A tibble: 6 × 3
Assay median_npx below_lod_n
<chr> <dbl> <int>
1 CXCL8 5.94 0
2 GCG 5.6 0
3 IL6 4.47 0
4 INS 2.84 9
5 LEP 2.59 7
6 TNF 2.94 0

Six rows carry the WARN flag, which is one sample failing across all six assays. Sixteen rows sit below the LOD, and they concentrate in two assays, INS and LEP. An assay with most of its values below LOD is a candidate for exclusion, and the per-assay table is where that decision starts.

A study larger than one plate needs bridging. Shared bridge samples run on every plate, and the per-plate difference on those samples corrects the rest. OlinkAnalyze implements this as olink_normalization. The export format stays the same; the normalisation adds columns rather than changing the layout. On the newer Explore HT platform the readout is sequencing rather than qPCR, the export arrives as parquet, and the same reader applies.

  • Olink measures proteins as NPX, a relative concentration on the log2 scale.
  • The export is a long table, one row per sample per assay, as xlsx, csv or parquet, read by OlinkAnalyze::read_NPX.
  • QC_Warning and LOD come from the file. Keep below-LOD values and flag them; do not delete them silently.
  • Multi-plate studies need bridging normalisation across shared samples.