Version 2 of iq (Pham et al. 2026) introduces the
maxlfq-bit algorithm and other algorithms for robust and
large-scale protein quantification, and a data format for datasets too
large to hold in memory. The examples below use the dataset
spikeins that comes with the package to illustrate their
use.
library("iq")
data("spikeins")
head(spikeins, 3)
#> R.Condition PG.ProteinGroups EG.ModifiedSequence FG.Charge
#> 1 C01 P02754 _VYVEELKPTPEGDLEILLQK_ 3
#> 2 C01 P02754 _VYVEELKPTPEGDLEILLQK_ 3
#> 3 C01 P02754 _VYVEELKPTPEGDLEILLQK_ 3
#> F.FrgIon F.Charge F.PeakArea PG.Genes PG.ProteinNames
#> 1 y13 1 38685.53 LGB LACB_BOVIN
#> 2 y18 2 19320.07 LGB LACB_BOVIN
#> 3 y17 2 13800.84 LGB LACB_BOVINThe data are a Spectronaut export of 12 spike-in proteins in 24
samples (Bruderer et al. 2015), with fragment ion intensities in
F.PeakArea.
process()process() reads a long-format table, filters the rows,
removes low intensities (by default those of 1 or less,
log2_intensity_cutoff = 0), performs median normalization,
quantifies every protein and writes a table with one row per protein and
one column per sample (log2 values). The input can be a data frame, as
here, a tab-separated text file, or a .parquet file. The
example data have already been normalized, so we turn off the
normalization with normalization = "none".
output_file <- file.path(tempdir(), "spikeins-maxlfq_bit.tsv")
iq::process(spikeins, output_file,
sample_id = "R.Condition",
primary_id = "PG.ProteinGroups",
secondary_id = c("EG.ModifiedSequence", "FG.Charge", "F.FrgIon", "F.Charge"),
intensity_col = "F.PeakArea",
annotation_col = c("PG.Genes", "PG.ProteinNames"),
filter_double_less = NULL,
normalization = "none",
pdf_out = NULL,
verbose = FALSE)
result <- read.delim(output_file, check.names = FALSE)
result[1:3, 1:6]
#> PG.ProteinGroups PG.Genes PG.ProteinNames C01 C02
#> 1 P02754 LGB LACB_BOVIN 12.88991 12.84917
#> 2 P80025 LPO PERL_BOVIN 10.51810 10.61247
#> 3 P00921 CA2 CAH2_BOVIN 11.37282 11.32456
#> C03
#> 1 12.87497
#> 2 10.55833
#> 3 11.33720The default method is maxlfq-bit; others are chosen with
method, e.g. method = "median_polish" or
method = "weighted_rlm". By default, the estimates of each
protein are shifted so that their mean equals the mean of its input data
in log2 space (rescale_method = "to_mean"); with
rescale_method = "to_sum", their intensities add up to
those of its input data.
The defaults of process() are those of a DIA-NN report:
the precursor quantities Precursor.Normalised per
Run, with Q.Value and PG.Q.Value
below 0.01. For a DIA-NN search with match-between-runs (MBR), one can
use the following filters given with the filter_double_less
parameter.
iq::process("report.parquet", "report-protein-group.tsv",
sample_id = "Run",
intensity_col = "Precursor.Normalised",
annotation_col = c("Protein.Names", "Genes"),
filter_double_less = c("Q.Value" = "0.01",
"PG.Q.Value" = "0.05",
"Lib.Peptidoform.Q.Value" = "0.01",
"Global.Q.Value" = "0.01",
"Global.PG.Q.Value" = "0.01"))The following is for quantification using MS2 fragment intensities of
DIA-NN 2 when run with the --export-quant option.
iq::process("report.parquet", "report-protein-group.tsv",
sample_id = "Run",
intensity_col = paste0("Fr.", 0:11, ".Quantity"),
intensity_col_id = paste0("Fr.", 0:11, ".Id"),
annotation_col = c("Protein.Names", "Genes"),
filter_double_less = c("Q.Value" = "0.01",
"PG.Q.Value" = "0.05",
"Lib.Peptidoform.Q.Value" = "0.01",
"Global.Q.Value" = "0.01",
"Global.PG.Q.Value" = "0.01"))process_matrix()The quantification methods work on the data matrix of one protein: log2 intensities with ions in rows and samples in columns.
X <- matrix(c(10, 12, 12, NA, NA, 10), nrow = 2,
dimnames = list(c("ion1", "ion2"), c("sample1", "sample2", "sample3")))
X
#> sample1 sample2 sample3
#> ion1 10 12 NA
#> ion2 12 NA 10
iq::process_matrix(X, method = "maxlfq_bit", rescale_method = NULL)
#> [1] 11 13 9Sample 2 is 4-fold higher than sample 1 (a difference of 2 in log2
space), the difference of ion1, the only ion they share; similarly,
sample 1 is 4-fold higher than sample 3. Here the mean of the estimates
already equals the mean of the data (11), so the default rescaling,
rescale_method = "to_mean", changes nothing. With
rescale_method = "to_sum", the differences stay the same,
and the intensities of the estimates add up to those of the data:
For data too large to hold in memory,
long_format_to_iq_format() reads a text or parquet file
chunk by chunk and writes it to a folder, one file per protein;
process_iq_format() then quantifies the proteins in
batches. The result is the same as that of process().
input_file <- file.path(tempdir(), "spikeins.tsv")
write.table(spikeins, input_file, sep = "\t", row.names = FALSE, quote = FALSE)
iq_folder <- tempfile("spikeins.iq")
iq::long_format_to_iq_format(input_file, iq_folder,
sample_id = "R.Condition",
primary_id = "PG.ProteinGroups",
secondary_id = c("EG.ModifiedSequence", "FG.Charge", "F.FrgIon", "F.Charge"),
intensity_col = "F.PeakArea",
annotation_col = c("PG.Genes", "PG.ProteinNames"),
filter_double_less = NULL,
normalization = "none",
pdf_out = NULL,
verbose = FALSE)
iq::process_iq_format(iq_folder, file.path(tempdir(), "spikeins-iq.tsv"), verbose = FALSE)
result_iq <- read.delim(file.path(tempdir(), "spikeins-iq.tsv"), check.names = FALSE)
result_iq <- result_iq[match(result$PG.ProteinGroups, result_iq$PG.ProteinGroups), ]
samples <- unique(as.character(spikeins$R.Condition))
max(abs(as.matrix(result_iq[, samples]) - as.matrix(result[, samples])), na.rm = TRUE)
#> [1] 0The protein files can also be processed in parts, for example on the nodes of a computing cluster (see the package project page https://github.com/tvpham/iq).
To look at the data of a protein in the iq format,
iq_format_to_protein_list() reads it into the same
structure as create_protein_list(), a matrix of log2
intensities with ions in rows and samples in columns, which can be
plotted with plot_protein():
protein_list <- iq::iq_format_to_protein_list(iq_folder, "P00366")
iq::plot_protein(protein_list$P00366, main = "Protein P00366", cex = 0.4)