This tutorial describes how to use Olink® Analyze to integrate Limit of Detection (LOD) into Olink® Explore HT, Olink® Reveal, and Olink® Explore 384/3072 datasets. Although it is recommended to use all Olink Explore and Olink Reveal data in downstream analyses, LOD information can be useful when performing technical evaluations of a dataset.
In this tutorial, you will learn how to use olink_lod()
to add LOD information to your Olink Explore or Olink Reveal dataset.
Note that Olink Analyze does not contain example Olink Explore HT, Olink
Reveal, or Olink Explore 384/3072 datasets within the package, so
external data will be necessary for the code below to work. The external
data should contain internal and external controls for proper
calculation and normalization. All file paths should be replaced with a
path to your data and fixed LOD reference file (if applicable).
Limit of Detection (LOD) is a metric that indicates the lowest measurable value of a protein. LOD can be helpful when performing technical evaluations of NPX™ datasets, such as calculating CVs. As a note, LOD is less important in downstream statistical analyses as values under LOD typically converge across groups. As such, including data below LOD is unlikely to increase the risk of false positive discoveries. Furthermore, data below LOD can be instrumental in downstream analyses such as biomarker discovery as a protein may be well expressed in one group and not measured in another group. In this case, this protein can be a strong biomarker candidate for specific groups.
LOD can be added to Olink Explore or Olink Reveal NPX datasets using
olink_lod(). This function can calculate LOD from an NPX
dataset using the dataset’s negative controls or a list of predetermined
fixed LOD values (available in the Document Download Center at olink.com). As the
default setting, olink_lod() will calculate LOD using a
dataset’s negative controls.
Olink Explore and Olink Reveal data are delivered as either plate
control (PC) normalized or intensity normalized (the normalization type
employed is indicated in the NPX file column Normalization), where the
latter is dependent on the assumption that the analyzed samples are
randomized. These are reported in the two respective columns, NPX and
PCNormalizedNPX. Please notice that for PC normalized datasets the
content in these two columns will be identical, while for intensity
normalized datasets the NPX column will include the intensity normalized
values. Similarly, the olink_lod() function adds two
columns to your dataset; LOD and PCNormalizedLOD, respectively. For a PC
normalized dataset, the content in these two columns will be identical,
while for an intensity normalized dataset the LOD column will contain
intensity normalized LOD values. Examples of results for plate control
and intensity normalization are shown in the tables below.
| SampleID | SampleType | OlinkID | UniProt | Assay | Count | NPX | PCNormalizedNPX | Normalization | LOD | PCNormalizedLOD |
|---|---|---|---|---|---|---|---|---|---|---|
| A1 | SAMPLE | OID01216 | O00533 | CHL1 | 1425 | 12.96 | 12.96 | Plate control | 2.37 | 2.37 |
| A2 | SAMPLE | OID01216 | O00533 | CHL1 | 1240 | 11.27 | 11.27 | Plate control | 2.37 | 2.37 |
| A3 | SAMPLE | OID01216 | O00533 | CHL1 | 2800 | 25.45 | 25.45 | Plate control | 2.37 | 2.37 |
| A4 | SAMPLE | OID01216 | O00533 | CHL1 | 1590 | 14.45 | 14.45 | Plate control | 2.37 | 2.37 |
| A5 | SAMPLE | OID01216 | O00533 | CHL1 | 839 | 7.63 | 7.63 | Plate control | 2.37 | 2.37 |
| A6 | SAMPLE | OID01216 | O00533 | CHL1 | 695 | 6.32 | 6.32 | Plate control | 2.37 | 2.37 |
| SampleID | SampleType | OlinkID | UniProt | Assay | Count | NPX | PCNormalizedNPX | Normalization | LOD | PCNormalizedLOD |
|---|---|---|---|---|---|---|---|---|---|---|
| A1 | SAMPLE | OID01216 | O00533 | CHL1 | 1425 | 17.12 | 12.96 | Intensity | 6.53 | 2.37 |
| A2 | SAMPLE | OID01216 | O00533 | CHL1 | 1240 | 15.43 | 11.27 | Intensity | 6.53 | 2.37 |
| A3 | SAMPLE | OID01216 | O00533 | CHL1 | 2800 | 29.61 | 25.45 | Intensity | 6.53 | 2.37 |
| A4 | SAMPLE | OID01216 | O00533 | CHL1 | 1590 | 18.61 | 14.45 | Intensity | 6.53 | 2.37 |
| A5 | SAMPLE | OID01216 | O00533 | CHL1 | 839 | 11.79 | 7.63 | Intensity | 6.53 | 2.37 |
| A6 | SAMPLE | OID01216 | O00533 | CHL1 | 695 | 10.48 | 6.32 | Intensity | 6.53 | 2.37 |
Olink Next Generation Sequencing (NGS) datasets are standard Olink
Explore HT, Olink Reveal, and Olink Explore 384/3072 NPX tables. The
read_NPX() function can be used to import an NPX file in
parquet form as generated by Olink Software. More information on using
read_NPX() can be found in the Olink Analyze
Overview tutorial.
# Preprocessing steps for both fixed LOD and Negative Control LOD integration
## Load NPX file
df_npx <- OlinkAnalyze::read_NPX(
filename = "Path_to/Explore_NPX_file.parquet"
)
## Check NPX data
check_log_df_npx <- OlinkAnalyze::check_npx(
df = df_npx
)
## Clean NPX data
df_npx_clean <- OlinkAnalyze::clean_npx(
df = df_npx,
check_log = check_log_df_npx,
# do not remove controls or warnings to ensure LOD can be calculated correctly
remove_control_assay = FALSE,
remove_control_sample = FALSE,
remove_assay_warning = FALSE,
remove_qc_warning = FALSE
)
## Generate check log on cleaned data
check_log_df_npx_clean <- OlinkAnalyze::check_npx(
df = df_npx_clean
)
# Cleanup intermediate objects
rm(
df_npx,
check_log_df_npx
)When the function clean_npx() is used before
calculating LOD, one should disable exclusion of internal controls,
external controls, assay warnings and sample warnings to ensure that the
LOD values are calculated correctly. This can be done by setting the
following parameters to FALSE in the clean_npx() function:
remove_control_assay, remove_control_sample,
remove_assay_warning and remove_qc_warning.
This should be done regardless of the LOD method that is used (fixed LOD
or Negative Control LOD).
By default, clean_npx() removes both internal and
external controls, which are required to calculate count-based LOD
values. If olink_lod() is run without the internal or
external controls present, the function will still execute, but all
count-based LODs will be assigned NA values. In addition,
the olink_lod() function contains code to exclude only the
relevant assay and QC warnings for the LOD calculation, so these
warnings do not need to be removed for LOD to be calculated.
The function clean_npx() can be applied with default
settings after LOD has been calculated to remove controls and warnings
from the dataset and prepare it for downstream analyses.
More information on clean_npx() can be found in the
Olink Analyze
Overview tutorial.
The negative control (NC) LOD method requires at least 10 negative controls in a dataset. Negative control data is available in the standard exported Olink NGS NPX parquet files. NCs can be identified through the SampleID and SampleType columns.
A negative control will not contribute to the minimum number of required NCs if the negative control does not pass sample QC criteria (sample QC failure or warning) in all of the data (i.e. all datapoints measured for that sample).
Negative controls are used to calculate LOD from either PC normalized NPX or counts. For assays with more than 150 counts in one of the negative controls, LOD is calculated using the median PC normalized NPX and adding 3 standard deviations, or 0.2 NPX whichever is larger. For assays with fewer than 150 counts in all negative controls, LOD is calculated using the count values which are then converted into PC normalized NPX.
Some assays will use count values as the LOD because the assay
receives very few counts in the negative controls. For the convenience
of data processing, the LOD in count values are converted to NPX values
in the olink_lod() function. The LOD value for this assay
(in counts) will become many LOD values in NPX (as extension control
counts will vary across all samples). This is due to the fact that minor
changes on the counts scale can result in significant changes on the NPX
scale when working with small counts. The reason for this is that NPX is
a relative scale, which is calculated by dividing the counts of the
assay by the counts of the extension control. For example, given that
the extension control values remain constant, if a count value were to
change from 1 count to 2 counts, this would be a change of 1 NPX, while
a change from 1000 counts to 1001 counts would be negligible on the NPX
scale.
Furthermore, due to the low number of counts, the NPX values calculated from these counts do not correlate to true background levels. The converted NPX values should not be used as LOD values for these assays.
The resulting LOD is the PC normalized negative control LOD. In the event that the Olink NGS dataset is intensity normalized, an intensity normalization adjustment factor is applied and the resulting intensity normalized LOD is reported in the LOD column and the PC normalized LOD is reported in the PCNormalizedLOD column.
# Calculate LOD from negative controls
## Calculate LOD
df_npx_nc_lod <- OlinkAnalyze::olink_lod(
data = df_npx_clean,
lod_method = "NCLOD",
check_log = check_log_df_npx_clean
)
## Generate check log on data with LOD
check_log_df_npx_nc_lod <- OlinkAnalyze::check_npx(
df = df_npx_nc_lod
)
## Clean NPX data with LOD
df_npx_nc_lod_clean <- OlinkAnalyze::clean_npx(
df = df_npx_nc_lod,
check_log = check_log_df_npx_nc_lod
)
## Generate check log on cleaned data with LOD
check_log_df_npx_nc_lod_clean <- OlinkAnalyze::check_npx(
df = df_npx_nc_lod_clean
)
# Cleanup intermediate objects
rm(
df_npx_nc_lod,
check_log_df_npx_nc_lod
)
# Rename final cleaned data with LOD for clarity
df_npx_nc_lod <- df_npx_nc_lod_clean
check_log_df_npx_nc_lod <- check_log_df_npx_nc_lod_clean
rm(
df_npx_nc_lod_clean,
check_log_df_npx_nc_lod_clean
)The fixed LOD method uses fixed LOD values that have been calculated on negative controls used in Olink reference runs using the method described above for negative control LOD. These values are specific to the Data Analysis Reference ID, which can be found in your dataset. The fixed LOD data is available in an external CSV file which can be downloaded from the Document Download Center at olink.com. The fixed LOD values reported in this CSV file are the PC normalized LODs.
The fixed LOD file is read into the olink_lod() function
to be integrated into an Olink NGS dataset. In the event that the NGS
dataset is intensity normalized, an intensity normalization adjustment
factor is applied and the resulting intensity normalized LOD is reported
in the LOD column and the PC normalized LOD is reported in the
PCNormalizedLOD column.
# Integrating fixed LOD
## Fixed LOD file path
fixedlod_filepath <- "Path_to/ExploreHT_fixedLOD.csv"
## Calculate LOD
df_npx_fixed_lod <- OlinkAnalyze::olink_lod(
data = df_npx_clean,
lod_method = "FixedLOD",
lod_file_path = fixedlod_filepath,
check_log = check_log_df_npx_clean
)
## Generate check log on data with LOD
check_log_df_npx_fixed_lod <- OlinkAnalyze::check_npx(
df = df_npx_fixed_lod
)
## Clean NPX data with LOD
df_npx_fixed_lod_clean <- OlinkAnalyze::clean_npx(
df = df_npx_fixed_lod,
check_log = check_log_df_npx_fixed_lod
)
## Generate check log on cleaned data with LOD
check_log_npx_fixed_lod_clean <- OlinkAnalyze::check_npx(
df = df_npx_fixed_lod_clean
)
# Cleanup intermediate objects
rm(
df_npx_fixed_lod,
check_log_df_npx_fixed_lod
)
# Rename final cleaned data with LOD for clarity
df_npx_fixed_lod <- df_npx_fixed_lod_clean
check_log_df_npx_fixed_lod <- check_log_npx_fixed_lod_clean
rm(
df_npx_fixed_lod_clean,
check_log_npx_fixed_lod_clean
)For smaller sized studies (<10 NCs) we recommend using fixed LOD to integrate LOD values into your NPX dataset, as LOD calculations on fewer NCs may provide non-accurate values. However, it is important to keep in mind that fixed LOD values are not specific to your project, rather these values are generated by Olink when a new lot of reagents is released.
For larger projects we recommend calculating LOD from NC to obtain LOD values that are specific to your project. However, this requires that the dataset has at least 10 NCs with passing SampleQC.
There is also the option to calculate both NC LOD and fixed LOD for a
data file by setting lod_method to “Both”. The resulting
data will have 4 additional columns, starting with NC or Fixed to
indicate the method used to calculate LOD, followed by LOD or
PCNormalizedLOD as explained above. An example of the file format is
shown below. Note that these columns will not automatically be
recognized by other functions within Olink Analyze that use LOD (for
example olink_bridgeselector()). To use these functions,
the LOD value to be used should have “LOD” as the column name.
| SampleID | SampleType | OlinkID | UniProt | Assay | Count | NPX | Normalization | PCNormalizedNPX | FixedLOD | FixedPCNormalizedLOD | NCLOD | NCPCNormalizedLOD |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| A1 | SAMPLE | OID01216 | O00533 | CHL1 | 1425 | 17.12 | Intensity | 12.96 | 6.53 | 2.37 | 4.19 | 0.03 |
| A2 | SAMPLE | OID01216 | O00533 | CHL1 | 1240 | 15.43 | Intensity | 11.27 | 6.53 | 2.37 | 4.19 | 0.03 |
| A3 | SAMPLE | OID01216 | O00533 | CHL1 | 2800 | 29.61 | Intensity | 25.45 | 6.53 | 2.37 | 4.19 | 0.03 |
| A4 | SAMPLE | OID01216 | O00533 | CHL1 | 1590 | 18.61 | Intensity | 14.45 | 6.53 | 2.37 | 4.19 | 0.03 |
| A5 | SAMPLE | OID01216 | O00533 | CHL1 | 839 | 11.79 | Intensity | 7.63 | 6.53 | 2.37 | 4.19 | 0.03 |
| A6 | SAMPLE | OID01216 | O00533 | CHL1 | 695 | 10.48 | Intensity | 6.32 | 6.53 | 2.37 | 4.19 | 0.03 |
If an Olink NGS dataset is intensity normalized, a normalization
adjustment factor is applied to the PC normalized LOD within the
olink_lod() function.
For each assay, this adjustment factor is calculated as the median NPX of all samples (excluding Olink’s external controls) within each plate. For Olink Explore 3072, overlapping assays are assessed separately, within their respective panels. The intensity normalized negative control LOD is calculated by subtracting this adjustment factor from the PC normalized negative control LOD.
The intensity normalization LOD adjustment is applied to both the negative control and fixed LOD methods.
When using the olink_normalization_bridge() function to
bridge two datasets, the reference project remains unchanged throughout
the bridging procedure, including the NC LOD (calculated from negative
controls in the reference project) and the fixed LOD. In contrast, the
LOD values (both NC LOD and fixed LOD) for the non-reference project are
adjusted using the same adjustment factor that is applied to all other
samples for the corresponding assay. This adjustment factor is the
median of the paired NPX differences per assay between the bridging
samples.
Consequently, for within-product bridging, LOD values from both the reference and non-reference projects can be used for downstream analysis. In contrast, for between-product bridging, differences in assay bridgeability between products and the use of distinct normalization methods (median centering versus quantile smoothing) should be taken into account. Therefore, we recommend applying the LOD values from the reference project to the non-reference project in between-product bridging.
Olink NGS data with LOD data can be exported using
arrow::write_parquet() to export the data as a parquet file
in long format.
# Exporting Olink NGS data with LOD information as a parquet file
## Integrate both Negative Control LOD and fixed LOD before NPX preprocessing
df_npx_both_lod <- OlinkAnalyze::olink_lod(
data = df_npx_clean,
lod_file_path = fixedlod_filepath,
lod_method = "Both",
check_log = check_log_df_npx_clean
)
## Generate check log
check_log_df_npx_both_lod <- OlinkAnalyze::check_npx(
df = df_npx_both_lod
)
## Clean NPX
df_npx_both_lod_clean <- OlinkAnalyze::clean_npx(
df = df_npx_both_lod,
check_log = check_log_df_npx_both_lod
)
## Generate check log on cleaned data
check_log_npx_both_lod_clean <- OlinkAnalyze::check_npx(
df = df_npx_both_lod_clean
)
# Cleanup intermediate objects
rm(
df_npx_both_lod,
check_log_df_npx_both_lod
)
# Rename final cleaned data with both LOD for clarity
df_npx_both_lod <- df_npx_both_lod_clean
check_log_df_npx_both_lod <- check_log_npx_both_lod_clean
rm(
df_npx_both_lod_clean,
check_log_npx_both_lod_clean
)
# Add metadata for export
df_npx_both_lod_arrow <- df_npx_both_lod |>
arrow::as_arrow_table()
df_npx_both_lod_arrow$metadata$FileVersion <- "NA"
df_npx_both_lod_arrow$metadata$ExploreVersion <- "NA"
df_npx_both_lod_arrow$metadata$ProjectName <- "NA"
df_npx_both_lod_arrow$metadata$SampleMatrix <- "NA"
df_npx_both_lod_arrow$metadata$DataFileType <- "Olink Analyze Export File"
# One of "ExploreHT", "Explore3072", or "Reveal"
df_npx_both_lod_arrow$metadata$ProductType <- "ExploreHT"
# # "ExploreHT", "Explore3072", or "Reveal"
df_npx_both_lod_arrow$metadata$Product <- "ExploreHT"
arrow::write_parquet(
x = df_npx_both_lod_arrow,
sink = "path_to_output.parquet"
)We are always happy to help. Email us with any questions:
biostat@olink.com for statistical services and general stats questions
support@olink.com for Olink lab product and technical support
info@olink.com for more information
© 2026 Olink Proteomics AB, part of Thermo Fisher Scientific.
Olink products and services are For Research Use Only. Not for use in diagnostic procedures.
All information in this document is subject to change without notice. This document is not intended to convey any warranties, representations and/or recommendations of any kind, unless such warranties, representations and/or recommendations are explicitly stated.
Olink assumes no liability arising from a prospective reader’s actions based on this document.
OLINK, NPX, PEA, PROXIMITY EXTENSION, INSIGHT and the Olink logotype are trademarks registered, or pending registration, by Olink Proteomics AB. All third-party trademarks are the property of their respective owners.
Olink products and assay methods are covered by several patents and patent applications https://www.olink.com/patents/.