Quantification of Intact Monoclonal Antibody with LC-DAD
Ricardo Cunha
cunha@iuta.de13 June, 2025
Source:vignettes/articles/demo_lc_dad_quantification_antibody.Rmd
demo_lc_dad_quantification_antibody.Rmd
Introduction
This document demonstrates the quantification of the intact monoclonal antibody Bevacizumab using Liquid Chromatography with Diode Array Detection (LC-DAD). The files include a blank, a calibration set with 10 points from 0.5 to 5 mg/L, a QC triplicate with 2.5 mg/L, and a sample in triplicate to be quantified. The data was acquired with an Agilent 1260 Infinity II system coupled to a DAD detecter. The raw data files are in Agilent’s .d format.
basename(files)
[1] "0.5.d" "1.5.d"
[3] "1.d" "2.5.d"
[5] "2.d" "20240815_67_BVCZ_1.d"
[7] "20240815_67_BVCZ_2.d" "20240815_67_BVCZ_3.d"
[9] "3.5.d" "3.d"
[11] "4.5.d" "4.d"
[13] "5.d" "Blank.d"
[15] "QC_BVCZ_1608_2_5mgmL_1.d" "QC_BVCZ_1608_2_5mgmL_2.d"
[17] "QC_BVCZ_1608_2_5mgmL_3.d"
Processing Engine
The StreamFind
package provides a flexible and powerful
framework for processing mass spectrometry data. In this example, we
will use the MassSpecEngine
class to handle the LC-DAD
data. The engine will be initialized with the raw files, and we will
specify that the data should be centroided and processed at level 1.
Note that The msconvert
from ProteoWizard was used in
the background to convert the files to the open format mzML.
# Starts engine for quantification
ms <- StreamFind::MassSpecEngine$new(
metadata = list(name = "Quantification of Bevacizumab"),
analyses = files,
centroid = TRUE,
levels = 1
)
# Edit to assign analysis replicate groups
ms$add_replicate_names(
c(
"cal_0.5",
"cal_1",
"cal_1.5",
"cal_2",
"cal_2.5",
rep("Sample", 3),
"cal_3",
"cal_3.5",
"cal_4",
"cal_4.5",
"cal_5",
"Blank",
rep("QC_2.5", 3)
)
)
# Concentration was obtained by attempting to convert the file names to numeric
# Alternatively, you can use the setter method ms$Analyses$concentrations to manually add the
# concentration values with NA_Real_ when the concentration is to be calculated
ms$Analyses$info[, c(1:2, 4, 8)]
analysis replicate type concentration
<char> <char> <char> <num>
1: 0.5 cal_0.5 MS 0.5
2: 1 cal_1 MS 1.0
3: 1.5 cal_1.5 MS 1.5
4: 2 cal_2 MS 2.0
5: 2.5 cal_2.5 MS 2.5
6: 20240815_67_BVCZ_1 Sample MS NA
7: 20240815_67_BVCZ_2 Sample MS NA
8: 20240815_67_BVCZ_3 Sample MS NA
9: 3 cal_3 MS 3.0
10: 3.5 cal_3.5 MS 3.5
11: 4 cal_4 MS 4.0
12: 4.5 cal_4.5 MS 4.5
13: 5 cal_5 MS 5.0
14: Blank Blank MS NA
15: QC_BVCZ_1608_2_5mgmL_1 QC_2.5 MS NA
16: QC_BVCZ_1608_2_5mgmL_2 QC_2.5 MS NA
17: QC_BVCZ_1608_2_5mgmL_3 QC_2.5 MS NA
Available Chromatograms
chroms = data.frame("name" = unique(ms$get_chromatograms_headers()[["id"]]))
chroms
name
1 TIC
2 DAD1 A: Sig=214,4 Ref=off
3 DAD1 B: Sig=254,4 Ref=off
4 DAD1 C: Sig=280,4 Ref=off
5 DAD1 D: Sig=194,4 Ref=off
6 QuatPump1 A: Pressure
7 QuatPump1 B: Flow
The chromatogram of interest is the DAD1 A: Sig=214,4 Ref=off, which is the second, and we know that our analyte elutes between 250 and 400 seconds.
Workflow
ms$run(
MassSpecMethod_LoadChromatograms_native(
chromatograms = chroms$name[2],
rtmin = 250,
rtmax = 400
)
)
ms$plot_chromatograms(colorBy = "replicates", normalized = FALSE)
ms$run(
MassSpecMethod_SmoothChromatograms_movingaverage(
windowSize = 3
)
)
ms$run(
MassSpecMethod_CorrectChromatogramsBaseline_airpls(
lambda = 25,
differences = 1,
itermax = 20
)
)
ms$plot_chromatograms(colorBy = "replicates", normalized = FALSE)
ms$run(
MassSpecMethod_IntegrateChromatograms_pracma(
merge = TRUE,
closeByThreshold = 5,
minPeakHeight = 10,
minPeakDistance = 3,
minPeakWidth = 5,
maxPeakWidth = 50,
minSN = 1
)
)
ms$plot_chromatograms_peaks(colorBy = "replicates+targets")
# Note that concentration values of the calibration curve
# were automatically obtained by the file name
ms$run(
MassSpecMethod_QuantifyChromatographicPeaks_native(
calibration = NA_real_,
value = "area",
model = "linear"
)
)
model <- ms$Chromatograms$calibration_model
fitted_values <- stats::predict(model)
r_squared <- summary(model)$r.squared
dt_cal <- data.table::data.table(
fitted_values = fitted_values,
calibration_values = model$model$calibration,
values = model$model$values
)
fig <- plotly::plot_ly(
model$model,
x = ~calibration_values,
y = ~values,
type = 'scatter',
mode = 'markers',
name = "Data"
) %>%
add_lines(x = ~fitted_values, y = ~values, name = "Fitted Line") %>%
layout(
title = paste("Linear Regression (R² =", round(r_squared, 4), ")"),
xaxis = list(title = "mg/L"),
yaxis = list(title = "Intensity / counts"),
showlegend = TRUE
)
fig
peaks <- ms$get_chromatograms_peaks()
peaks[, c(1, 5, 17, 18, 19)]
analysis peak sn calibration concentration
<char> <char> <num> <num> <num>
1: 0.5 C1_P1_RT344 163.7 0.5 0.4694340
2: 1 C1_P1_RT344 182.9 1.0 0.9553192
3: 1.5 C1_P1_RT343 165.5 1.5 1.4402263
4: 2 C1_P1_RT343 1152.3 2.0 2.1156837
5: 2.5 C1_P1_RT343 632.0 2.5 2.5065439
6: 20240815_67_BVCZ_1 C1_P1_RT343 911.3 NA 2.5607074
7: 20240815_67_BVCZ_2 C1_P1_RT343 942.3 NA 2.6073833
8: 20240815_67_BVCZ_3 C1_P1_RT343 772.7 NA 2.5706359
9: 3 C1_P1_RT343 692.9 3.0 3.0291738
10: 3.5 C1_P1_RT343 713.8 3.5 3.6045620
11: 4 C1_P1_RT342 841.4 4.0 4.0153155
12: 4.5 C1_P1_RT342 724.4 4.5 4.4464318
13: 5 C1_P1_RT342 670.5 5.0 4.9173097
14: QC_BVCZ_1608_2_5mgmL_1 C1_P1_RT343 675.3 NA 2.5316959
15: QC_BVCZ_1608_2_5mgmL_2 C1_P1_RT343 771.4 NA 2.7308170
16: QC_BVCZ_1608_2_5mgmL_3 C1_P1_RT343 988.2 NA 2.5849270