MALDIassist provides a set of mathematical utilities for MALDI-TOF mass spectrometry workflows in R. It covers the full path from raw Bruker spectra to a cohort-level peak matrix:
Performance-critical routines are implemented in C++ (via Rcpp), and list-based functions support multi-core parallel processing.
The figure below shows the core workflow on real Bruker MALDI-TOF data: a raw spectrum (gray) is smoothed and baseline-corrected (red), then peaks are detected and filtered (blue).

You can install the development version from GitHub:
# install.packages("remotes")
# remotes::install_github("hiows/MALDIassist")
install.packages("MALDIassist", repos = c('https://hiows.r-universe.dev', 'https://cloud.r-project.org'))
CRAN resubmission of v1.0.2 is in preparation. Until the package is on CRAN, the latest version may be available from GitHub (main or the latest Release).
The pipeline follows six steps: load → preprocess → KDE → peak picking → peak filtering → visualization.
library(MALDIassist)
Point load_maldi_spectra() at a directory of Bruker flex data files. It returns a named list of raw spectra.
raw_spectra <- load_maldi_spectra(spectra_dir = "data/")
Apply Savitzky-Golay smoothing and baseline subtraction. List-based functions accept n_cores for parallel processing.
preprocessed_spectra <- preprocess_maldi_spectra(
spectra = raw_spectra,
hws_sg = 10, # half-window size for Savitzky-Golay
pno_sg = 3, # polynomial order
baseline_type = "snip", # baseline algorithm
iter_snip = 50, # SNIP iterations
n_cores = 4
)
Build Gaussian KDE spectra for peak filtering:
kde_spectra <- build_kde_spectra(
spectra = preprocessed_spectra,
bw = 1,
n_cores = 4
)
find_peaks_spectra() detects peaks (including shoulder peaks) using a Gaussian KDE approach.
peaks_list <- find_peaks_spectra(
spectra = preprocessed_spectra,
bw = 1, # KDE bandwidth
hws_peaks = 10,
weight_type = "raw",
cutoff_kappa_peak_strength = 0.3,
peak_retention_fraction = 0.25,
n_cores = 4
)
filter_peaks_spectra() removes low-quality peaks by intensity, prominence, and strength cutoffs.
filtered_peaks_list <- filter_peaks_spectra(
spectra = lapply(kde_spectra, `[[`, 1),
peaks_list = peaks_list,
cutoff_peak_intensity = 100,
cutoff_peak_prominence = 50,
cutoff_peak_strength = 0.5,
normalization_type = "raw",
n_cores = 4
)
Overlay a raw spectrum (gray), its preprocessed version (red), and the filtered peaks (blue) — this reproduces the figure at the top of this README:
example_range <- c(12000, 15000)
i <- 1
spec <- raw_spectra[[i]]
pp <- preprocessed_spectra[[i]]
fp <- filtered_peaks_list[[i]]
in_range <- function(df) subset(df, df[, 1] >= example_range[1] & df[, 1] <= example_range[2])
plot(
in_range(spec),
type = "l", col = "gray70", lwd = 1.5,
ylim = c(0, max(in_range(spec)[, 2])),
xlab = "m/z", ylab = "Intensity", frame = FALSE
)
lines(in_range(pp), col = "red", lwd = 2)
fp_r <- fp[fp$mz >= example_range[1] & fp$mz <= example_range[2], ]
segments(fp_r$mz, 0, fp_r$mz, fp_r$intensity, col = "blue", lwd = 1.5)
You can also overlay all spectra in a single base-R plot:
visualize_spectra(spectra = preprocessed_spectra)
The example below follows a two-species MALDI-TOF cohort from PRIDE PXD058284: load Bruker spectra and sample metadata, preprocess and pick peaks, align across samples, build a matched-peak matrix, and test for group-discriminating m/z features.
library(readxl)
raw_spectra <- load_maldi_spectra(spectra_dir = "data/raw/")
metadata <- read_xlsx("data/sample_metadata.xlsx")
# two-level species grouping (one label per sample)
sample_group <- metadata$Species[match(names(raw_spectra), metadata$SampleID)]
preprocessed_spectra <- preprocess_maldi_spectra(
spectra = raw_spectra,
hws_sg = 10,
pno_sg = 3,
baseline_type = "snip",
iter_snip = 50,
n_cores = 4
)
kde_spectra <- build_kde_spectra(
spectra = preprocessed_spectra,
bw = 1,
n_cores = 4
)
peaks_list <- find_peaks_spectra(
spectra = preprocessed_spectra,
bw = 1,
hws_peaks = 10,
weight_type = "raw",
cutoff_kappa_peak_strength = 0.3,
peak_retention_fraction = 0.25,
n_cores = 4
)
filtered_peaks_list <- filter_peaks_spectra(
spectra = lapply(kde_spectra, `[[`, 1),
peaks_list = peaks_list,
cutoff_peak_intensity = 100,
cutoff_peak_prominence = 50,
cutoff_peak_strength = 0.5,
normalization_type = "raw",
n_cores = 4
)
align_spectra() corrects m/z drift using internal standards selected from frequent, high-intensity peaks. Choose "linear" (two-point) or "lowess" (multi-point) alignment. It returns one aligned spectrum / peaks pair per sample (in alignment_results) plus the reference standard_mz values used as anchors.
aligned <- align_spectra(
spectra = lapply(kde_spectra, `[[`, 1),
peaks_list = filtered_peaks_list,
bin_width = 20,
alignment_mode = "linear", # or "lowess"
hws_alignment = 50
)
aligned_peaks <- lapply(aligned$alignment_results, `[[`, "peaks")
exclude_mz <- aligned$standard_mz # alignment anchors, excluded below
find_frequent_mz() scans pooled peak m/z values across the aligned samples and refines each bin location with Gaussian KDE. Pass the alignment anchors to exclude_mz so the internal standards are dropped from the feature set.
freq_mz <- find_frequent_mz(
peaks_list = aligned_peaks,
bin_width = 20,
exclude_mz = exclude_mz
)
build_matched_matrix() matches each sample's peaks to the frequent m/z references and returns a detection matrix (detected_matrix) and a signed m/z-difference matrix (delta_mz_matrix), both sample-by-marker.
matched <- build_matched_matrix(
peaks_list = aligned_peaks,
reference_mz = freq_mz$mz,
hws_match = 10
)
mat <- matched$detected_matrix
Visualize the matrix with heatmap_matched_matrix(), optionally annotated by a per-sample grouping:
heatmap_matched_matrix(
matched_matrix = mat,
group = sample_group, # one entry per sample (row)
hide_rownames = TRUE,
hide_colnames = TRUE
)
estimate_significance() runs a per-feature two-group comparison (t-test or Wilcoxon) on a sample-by-marker matrix and returns raw and adjusted p-values. Subset the matrix to the significant markers to highlight the discriminating features.
sig <- estimate_significance(
matched_matrix = mat,
group = sample_group, # two-level grouping, one entry per sample
stat_method = "t.test", # or "wilcox"
adj_method = "BH" # "none", "BH", or "bonferroni"
)
heatmap_matched_matrix(
matched_matrix = mat[, which(sig$adj_pvalue < 0.01)],
group = sample_group,
hide_rownames = TRUE,
hide_colnames = TRUE
)
Applied to the PXD058284 two-species cohort (E. coli vs K. pneumoniae), the significant markers cleanly separate the samples by species:

| Function | Purpose |
|---|---|
load_maldi_spectra() |
Load Bruker raw spectra from a directory |
preprocess_maldi_spectra() |
Smooth and baseline-correct spectra |
find_peaks() / find_peaks_spectra()
|
Detect ordinary and shoulder peaks (single / list) |
find_peaks_fast() / find_peaks_spectra_fast()
|
Fast local peak detection (single / list) |
filter_peaks() / filter_peaks_spectra()
|
Filter peaks by intensity, prominence, and strength |
build_kde_spectrum() / build_kde_spectra()
|
Build Gaussian KDE spectra (single / list) |
find_frequent_mz() |
Find frequent m/z values across a cohort |
filter_overlap_mz() |
Remove overlapping nearby frequent m/z features |
align_spectra() |
Align spectra to internal standards (linear / lowess) |
build_matched_matrix() |
Assemble a cohort peak intensity matrix |
estimate_significance() |
Two-group significance testing per m/z feature |
visualize_spectrum() / visualize_spectra()
|
Visualize spectra with base-R graphics |
heatmap_matched_matrix() |
Heatmap of a matched-peak matrix (requires pheatmap) |
Some functions load optional packages only when used:
visualize_spectra() → colorspace
heatmap_matched_matrix() → pheatmap
Wonseok Oh (ORCID: 0009-0002-0687-8466)
If you use MALDIassist in your research, please cite it. From R you can run:
citation("MALDIassist")
A BibTeX entry:
@Manual{maldiassist,
title = {MALDIassist: Mathematical Utilities for MALDI-TOF Mass Spectrometry},
author = {Wonseok Oh},
year = {2026},
note = {R package version 1.0.2},
url = {https://github.com/hiows/MALDIassist},
doi = {10.5281/zenodo.21492329}
}
Archived on Zenodo: 10.5281/zenodo.21492329 (v1.0.1). To cite the software regardless of version, use the concept DOI 10.5281/zenodo.21219451.
See NEWS.md for release notes (v1.0.0 onward).
MIT © 2026 Wonseok Oh. See LICENSE.md for details.