Changes

Bug Fixes

  • plot_plateLayout() - The plate PNG now colors well type j with colors[j + 1], matching the HTML table. Plates without SC wells previously drew IPC and NC with colors[3] and colors[5] (issue #784).
  • targetBoxplot() - axis_limits now sets the value (NPQ) axis when horizontal = TRUE. It previously set the target axis (issue #785).
  • splitXML() - Now resolves the requested covariate name to the attribute actually in the XML: an exact match first, then the insertCovariatesXML(sanitize_names = TRUE) lower-camel form (so "Visit Week" finds visitWeek). The name is no longer pasted into an XPath expression, so names such as "Visit (week)" no longer leak xml2 warnings. It stops with a specific message instead of silently returning a zip of unsplit XML copies with meaningless names when the XML has no covariates, the covariate is not found, or the name matches more than one covariate (issue #535).

Changes

New Features

  • omnibusNULISAseq() - New exported function: joint (omnibus) significance test for model terms that span more than one coefficient, such as an interaction involving a factor with more than 2 levels, where per-coefficient t-tests cannot answer whether the term matters overall. It works on the fits lmNULISAseq()/lmerNULISAseq() already return (return_model_fits = TRUE), so no refit is needed and any number of terms can be tested in one call. lm fits get an exact F test via car::Anova(); linear mixed fits get an F test with Satterthwaite denominator df via lmerTest, reported in a new DenDF column; glm fits get a likelihood-ratio chi-square, the same test glmNULISAseq_predict() reports as Chisq_stat; glmerMod fits get a Wald chi-square, since an LR test there would need the reduced-model refit this function exists to avoid. stat_type names which statistic each row holds. FDR and Bonferroni adjustment are computed within each term across targets. Unlike the existing reduced_modelFormula refit mechanism, it does not silently degenerate when the tested term is nested inside a higher-order term that remains in the model. Requested term names are matched by component set, so "group:sex:time" resolves even though terms() canonicalizes the label to "sex:group:time". Aliased (rank-deficient) coefficients from empty interaction cells are tolerated: the term is tested on its estimable coefficients with Df reduced to match, and a term with no estimable coefficient left is skipped for that target with a warning. When terms is NULL, terms are selected structurally from the design matrix, unioned across all fits, so an aliased coefficient or a factor level missing from one target cannot decide what is tested for the rest. Input is validated at the entry point: modelFits must be a uniquely named list of fits, and a missing or NULL value reports that lmNULISAseq()/lmerNULISAseq() need to be re-run with return_model_fits = TRUE (their default is FALSE) rather than failing later with a message about model terms. An unnamed or partially named list is now rejected instead of silently returning results for the named fits only.
  • XML-driven QC thresholds - QC thresholds and criteria can now be defined directly in the panel XML via <QCThresholds> (and processing parameters via <Parameters>), overriding the hardcoded defaults in QC.R on a per-flag basis (issue #670). Flags absent from the XML fall back to their hardcoded defaults. New helper mergeQCCriteria() overlays XML thresholds onto the QC*Criteria() defaults (overriding only value-like fields; flag set, ordering, and thresholdNames stay code-defined). Supports per-matrix sample Detectability keys (Detectability_PLASMA → Detectability.plasma), appliesWhen="TAP" gating, and operator="none" informational-only rows (displayed, never flag). A new forceDefaultQC argument to loadNULISAseq() forces the hardcoded defaults and ignores the XML.
  • loadNULISAseq() - Added optional AQ outlier-removal controls: AQ_NC_outlier_removal and AQ_IPC_outlier_removal (both default FALSE), plus AQ_IPC_outlier_threshold (default 3) and AQ_IPC_mad_floor (default 0.3). When enabled, a maximum of one NC and/or one IPC/CAL outlier is removed per-target during absolute quantification. Default behavior is unchanged. These options only take effect when AQ is computed via NULISAseqAQ::applyAQ; they are ignored in the XML-embedded fallback path.
  • mergeNULISAseq() - CNS and Inflammation panel version variants (e.g. CNS Disease Panel 220 v1 and v2) can now be merged (#3263). extract_assay_names() also now treats any unreported or blank <Assay> value as the literal name “NULISAseq” rather than “unknown, compatible with anything.”
  • insertCovariatesXML() - Added opt-in attribute-name sanitization for covariate names written into XML.
  • insertAlamarTargetIDXML() - New exported function: sets an AlamarTargetID attribute on every BarcodeA/Barcode element that lacks one, from a caller-supplied targetName -> alamarTargetID table (data.frame or CSV). The call succeeds for every input or fails for all: each file is resolved first, and if any target name is missing from the table, listed under several ids, or carries an id that contradicts the table, the function stops with a message listing every offending target and writes nothing. On success a file with insertions is re-serialized by xml2; other outputs receive a byte-for-byte copy; existing ids are never changed.
  • Canonical target names - readNULISAseq() (and loadNULISAseq() / importNULISAseq(), which pass through) gains canonicalTargets = TRUE and targetMap = NULL. With canonicalTargets = TRUE (the default), every parsed targetName is rewritten to the current spelling the new alamar_target_map() table lists for its AlamarTargetID, before any matrix is keyed by name, so differently-spelled exports of the same target merge as one row across plates. A row with no id is backfilled from the table (current or alias spelling); an id-less spelling the table lists under more than one id takes the id whose species (optional table column) matches the plate’s species, derived from ExecutionDetails$Assay (guessed from the plate’s resolved ids when the assay name is the generic NULISAseq); when no single id remains, the row keeps its parsed spelling with AlamarTargetID = NA and a warning names the file and every candidate id. The as-parsed spelling is kept in targets$targetName_raw. targetMap layers caller-supplied targetName -> alamarTargetID aliases on top of the shipped table for one read (ids must already be rows of the table). IC and excludeTargets (and the QC-report YAML that feeds them) accept either the canonical spelling or the spelling parsed from the same file, and the default IC (mCherry, when IC = NULL on an XML without target types) follows a rename of that target too. IC target names are now matched exactly rather than as a regular expression, so a name such as LTA|LTB no longer marks LTA and LTB as controls too, and the default mCherry no longer marks a target whose name merely contains mCherry. A multi-element IC on an XML without target types now marks every listed target as a control; ifelse() truncated it to the first, so sample QC and detectability change for such calls. An IC or excludeTargets value that is one target’s canonical name and another target’s parsed spelling warns and uses the canonical match. A merge of plates whose targets carry different alamarTargetID values under one name warns, because the rows pool into one. writeNULISAseq() gains canonicalTargets and targetMap pass-through arguments; its target_info_file may name targets by the parsed spelling, and rows that match no target warn instead of vanishing from the workbook. The long tables (Data_NPQ_long, Data_AQ_long) and writeNULISAseq() carry the canonical Target and the resolved AlamarTargetID (populated for id-less XMLs where the table knows the spelling); targetName_raw stays in targets and is not exported. Set canonicalTargets = FALSE to keep names exactly as parsed. The long tables and writeNULISAseq() gain an opt-in include_target_provenance argument that adds a Target_raw column holding the parsed spelling next to the canonical Target; default off.
  • writeUpdatedXML() - New insertTargetIDs argument (default FALSE) writes AlamarTargetID onto each BarcodeA/Barcode node that has no id. The ids come from data$targets, as readNULISAseq() resolved them. An existing id is never changed. If an existing id disagrees with data, the call stops and returns no XML. Hidden targets and targets in excludeTargets are not rows of loadNULISAseq() output, so they get no id. A target with no resolved id (an ambiguous or unknown name) also gets no id. A target whose id came from the plate’s species guess (its parsed spelling sits under several ids in the table) gets no id either, unless targetMap names that id. canonicalTargets/targetMap pass through when data is NULL. When data is supplied, an explicit canonicalTargets or targetMap gives a warning, because the function ignores it. A written id is permanent: a re-read of the output takes it without the alias table.
  • QC Report skeleton (batch effect) - The batch-effect section (ANOVA F test, ICC, pairwise post hoc, target means, and target boxplots) now excludes exact-zero NPQ wells (“no signal”) from its per-target statistics instead of treating them as real quantitative reads, so a block of zeros no longer distorts a target’s F-test/ICC/mean. With 3+ plates, a target with one plate that has no signal at all still gets tested on its other plates; with exactly 2 plates, or if every plate but one lacks signal, that target’s F-test/ICC report NA rather than a spurious result – a target with no detectable signal on an entire plate is a real batch effect this exclusion does not flag, and is not yet caught by any other check in the report. target_mean_pca and the sample-level PCA intentionally keep reading the full, zero-inclusive matrix, since a joint multi-target PCA cannot drop scattered cells without imputing values or dropping whole targets/samples.
  • importNULISAseq() long format - Data_NPQ_long and Data_AQ_long now carry plate (run) level QC from qcPlate as Plate_QC_Status plus Plate_QC_<metric> and Plate_QC_<metric>_Status columns, repeated on every row of a plate (issue #641). Controlled by the existing include_qc argument. Target QC columns are still absent for RQ runs whose XML has no <TargetQC> and that were not processed with advancedQC; this change does not alter that.

Performance

  • lmNULISAseq() / lmerNULISAseq() - Hoisted invariant per-target merge out of the differential-expression fit loop, reducing redundant work on each target iteration.

Changes

  • Detectability QC no longer flags - The hardcoded defaults for all three Detectability checks (sample-level per-matrix, plate-level, target-level Target_Detectability) are now threshold=0 and operator="none": they display but never produce a pass/warning verdict. This is a permanent, non-overridable setting - mergeQCCriteria() explicitly excludes Detectability from panel-XML overrides, since many existing panel XML files carry a <QCThresholds> entry for it that mirrors the old hardcoded default and would otherwise silently re-enable flagging.

Bug Fixes

  • mergeNULISAseq() / mergeProcessedNULISAseq() / detectability_summary() - A sample covariate whose type differs between plates (for example numeric on one plate, factor on another) no longer errors (issue #727). All three paths, and the two per-plate sample binds in the QC report skeleton, now bind such columns as text and re-type them: a covariate becomes numeric (or logical) only if at least one plate supplied that type and every other value converts without loss; anything else, including zero-padded IDs such as "001" and a literal "NA" category, becomes a factor with its values intact. Real NA values stay NA. The result no longer depends on plate order.
  • mergeProcessedNULISAseq() - Adding a dataset whose sample names collide with the existing dataset no longer fails with “Join columns in x must be present in the data. Problem with plateID.” The renaming step skips data frames that have no plateID column, such as detectability.
  • QC Report skeleton (report mode) - AbsData (the report’s Absolute-Quant-vs-Relative-Quant mode) was assigned inside the per-file for loop in the load-data chunk, so it leaked out as whatever the last plate’s AbsAssay happened to be. A batch mixing an AQ run with an RQ run of the same panel silently changed report mode (target QC criteria, AQSC/CAL vs SC/IPC labels) depending on plate order (issue #3936). AbsData is now computed once after all runs are loaded and requires every run to agree; a new AQ parameter to render_QC_report() lets a caller that already knows the merge stripped AQ fields (e.g. NAS) force RQ explicitly.
  • writeUpdatedXML() - No longer crashes with “subscript out of bounds” when the input XML has ReadCount nodes for a target or sample barcode that was dropped from data$targets/data$samples (e.g. a hidden target, modifiers="hide"). .annotate_readcounts() looked up barcodes with [[ on a plain named vector, which errors on an unmatched name instead of returning NA; it now uses single-bracket indexing so unmatched barcodes are skipped like any other unresolved lookup.
  • writeUpdatedXML() - A 1.3.0 input now keeps its QCThresholds, PlateQC, SampleQC and TargetQC sections. The function writes a section only when it is absent. It does not recompute an existing section. Previously, one call on a 1.3.0 file added a second copy of each section. A re-read of such an AQ file could multiply the targetAQ_param rows, and a second call could crash. A repeat call now adds nothing. An input that already has more than one copy of a section stops the call. The message names the sections. Every ExecutionDetails/XMLversion node now gets the value 1.3.0. A call adds a node only when none exists, so a repeat call adds none. Previously, each call added one more node, and an older value stayed first. Every call still recomputes the ReadCount attributes that data has a value for, also on a 1.3.0 input.
  • mergeQCCriteria() - Now returns NULL immediately when defaults is NULL (e.g. QCTargetCriteria(advancedQC=FALSE) on a plain RQ run), instead of proceeding to warn that every panel-XML <TargetQC> threshold key has “no matching computation.” Previously, any plain RQ run with panel-XML target thresholds warned on every QCFlagTarget() recompute (issue #702).
  • loadNULISAseq() - The AQ result structure is now consistent between the NULISAseqAQ and XML-embedded fallback modes: the fallback path includes the blank_outlier_table and IPC_outlier_table elements (as NULL) that applyAQ() returns.
  • loadNULISAseq() - Outlier-removal arguments are now passed to NULISAseqAQ::applyAQ only when the installed version accepts them (filtered against formals()), preventing an “unused arguments” error and aborted AQ processing when an older NULISAseqAQ is installed.
  • QC Report skeleton (batch effect) - Replaced regex/substring matching of target and plate names with exact/bounded matching in the batch-effect section (issue #655), fixing incorrect batch-effect warnings and per-plate “Warning/Pass” verdicts. Site A: per-target significance now tests exact membership in each plate’s sig_targets list instead of grepl(target, ...) (which matched substrings like CCL1→CCL14 and regex metacharacters like LTA|LTB). Site B: per-plate pairwise fraction now matches plate names with a bounded pattern instead of grep(plate, ...) (which matched Plate_1→Plate_10), while still matching emmeans’ parenthesized factor levels.
  • QC Report skeleton (batch effect) - The pairwise post-hoc test now drops targets whose mixed model failed to fit (NULL) before calling emmeans::emmeans() (issue #655), which previously aborted the entire report with “Can’t handle an object of class NULL”.
  • QC Report skeleton (read summary) - Zero-read / IC-zero sample removal now prunes IC_normed$normData alongside Data/samples/SampleNames (issue #655), keeping columns aligned so the IC-normalized control CV% is computed on the correct wells.
  • QC Report skeleton (inter-plate normalization) - Zero-read / IC-zero sample removal now also prunes the excluded well names from IPC/NC/SC/Bridge/Calibrator (issue #697). Previously, if the excluded well was itself an IPC/NC/SC/Bridge control (e.g. an IPC well with zero internal-control reads), its name lingered in those lists after its column was dropped from Data, causing interPlateNorm() to fail with “subscript out of bounds” and aborting QC report generation entirely.
  • interPlateNorm() - Column subsets used for IPC and intensity normalization now use drop=FALSE, and the function now errors clearly (“No IPC wells remain…” / “No samples remain for intensity normalization…”) when a plate’s IPC list or resolved intensity-normalization sample set (which covers NC and Bridge-based normalization) is empty, rather than silently treating a missing control as “no normalization needed.” It also now validates that IPC well names/indices actually exist in the data, raising a specific “not found in the data” error instead of the generic “subscript out of bounds.” Previously, a plate left with exactly one control well after exclusion crashed with the opaque dim(X) must have a positive length, and a plate left with zero wells of a given type silently skipped that normalization step (e.g. IPC factors defaulted to 1) instead of failing - producing a report that looked normalized but wasn’t. This also closes the same hole for loadNULISAseq(excludeSamples=...), which routes through the same function. Also fixed IPC_method='mean', which previously errored on any input due to an argument passed positionally into the wrong parameter.
  • lod() - Behavior change: with fewer than 2 NC/blank wells, lod() now emits a warning and returns NA for LOD and an all-NA aboveLOD matrix (with blank_outlier_table = NULL) instead of calling stop(). Callers that caught the error, for example with tryCatch(), must now check for NA in the result. Downstream QC and detectability treat an NA aboveLOD as unknown, not as not-detected. It also uses drop=FALSE when subsetting blanks, which fixes the dim(X) must have a positive length crash when exactly one blank well remains after sample exclusion.
  • QC Report skeleton (sample QC / Bridge labeling) - Sample-type relabeling for SC/Bridge wells now checks that the plate actually has wells of that type before matching by name; previously, if a plate’s Bridge list became empty (all Bridge wells excluded), grepl("", ...) matched every sample name and mislabeled all samples on that plate as “Bridge”. Also gave the sample-QC dotchart’s label index (inds) an explicit default so it can no longer be left undefined when neither the SC nor Bridge relabeling block runs for a plate.
  • readNULISAseq() - Now builds aboveLOD and detectability matrices for csv_long (Counts Report) input, which previously lacked these fields.
  • readNULISAseq() - Hidden-target reads are now excluded from the RunSummary ParseableMatch count, which previously inflated the reported match rate.
  • readNULISAseq() - Sample matching is now consistent when hide targets are dropped; previously, dropping hidden targets could desynchronize sample column alignment.
  • loadNULISAseq() - IC (internal control) targets are now kept in sync with target exclusion. Previously, excluding a target that was also the IC could cause a subscript crash. The function now warns on partial IC loss and names the target on full loss.
  • readNULISAseq() - Numeric IC indices are now resolved to target names before exclusion tracking, preventing misidentification when targets are reordered.
  • sampleBoxplot() - Guards the IC reference line when the IC target is absent from the normalized matrix, instead of crashing. filter_run_data() now uses drop=FALSE when subsetting samples/targets to preserve data.frame structure.
  • generate_heatmap() - Default row_split is now gated on cluster_rows; previously, passing cluster_rows=FALSE with the default row_split could produce an error (#669).
  • QCFlagTarget() / QCFlagPlate() - Guards against dimension-drop when only a single SC or IPC well remains after exclusion, which previously collapsed the matrix to a vector and crashed downstream computations.
  • interCV() - Now handles the empty replicate-set case (no replicates found) without crashing.
  • insertCovariatesXML() - Covariate attributes are now written correctly when sampleName is plate-suffixed; previously, the match was not anchored to the _ delimiter, causing mismatches (#644).
  • QC Report - Now guards against empty or missing template paths before calling render(), instead of producing an opaque error (#691).
  • QC Report - CV Average row now computes correctly, and table columns sort numerically instead of lexicographically (#646). Zero-padded sample IDs are preserved during render_table coercion instead of being silently converted to integers.
  • QC Report - AQ Run QC explanations now use “CAL” terminology consistently instead of “IPC” for calibrator-based absolute quantification.

Changes

New Features

  • insertCovariatesXML() - Added sanitize_names argument (default FALSE). When TRUE, covariate column names are passed through clean_covariate_names(case = "lower_camel") before being written as XML attributes, matching NAS’s covariate-upload sanitation so attribute names stay stable across NAS export → NULISAseqR import → NAS re-import (follow-up to Alamar-Biosciences/NULISA-Analysis-Software#3199). Default FALSE preserves the historical write-verbatim contract for direct callers. Errors with a clear message if sanitation would collide two original columns onto the same attribute name (e.g. sampleName + "Sample Name" both targeting sampleName)

Bug Fixes

  • format_wide_to_long() - Renamed conflicting user covariate columns that share reserved names (e.g. PlateID) by appending _covar suffix; emits a message when renaming occurs to inform users of the change
  • detectability_summary() - Replaced do.call(rbind) with dplyr::bind_rows() when aggregating samples across plates, fixing crashes when importing plates with different XML schemas (e.g. mismatched metadata columns)
  • mergeNULISAseq() - Replaced do.call(rbind) with dplyr::bind_rows() for RunSummary aggregation; drops NA-named slots from each plate’s RunSummary list to eliminate spurious NA. columns when plates have no balancers in the XML
  • QC Report skeleton - Fixed sample boxplot axis ticks and NPQ axis label; corrected instrument subsetting for plate effect test when some plates are excluded; removed ANOVA instrument effect test section from internal report output
  • processXML() - Used is.na() instead of is.null() for XML attribute checks, fixing silent failures when attributes return NA rather than NULL
  • insertCovariatesXML() - Fixed silent skip of barcodes whose covariates$sampleName is plate-suffixed (e.g. LowQC_Plasma_begin_<plate>.xml), which previously produced NA for the injected covariate on every QC sample on re-import (issue Alamar-Biosciences/NULISA-Analysis-Software#3199). Match now anchors on either exact equality or the _<plate>.xml suffix used by NAS, avoiding both the prior silent-skip and the substring over-match it could regress into. Empty or whitespace-only barcode text is now skipped explicitly

Documentation

  • User Guide - Updated high-abundance and rare case target description: added information regarding the new Neuro 220 panel, clarified language for high abundance and noDetectability target behavior

Infrastructure

  • DESCRIPTION - Added XML and fields to Imports, enabling automatic installation of dependencies

Changes

New Features

  • loadNULISAseq() - Now accepts a pre-built list structure in addition to file paths, enabling reprocessing of data with sample exclusions without re-parsing XML files
  • get_reverse_curve_targets() - New exported helper to identify reverse curve targets (Curve_Quant starting with “R”)
  • get_noDetectability_targets() - New exported helper to identify targets with the XML noDetectability modifier

Enhancements

  • Reverse curve & noDetectability target handling - Reverse curve targets are now fully excluded from detectability and labeled “High Abundance”; rare-case targets with XML noDetectability modifier have individual detectability computed but are excluded from summary statistics (mean, sd, median, min, max, # detectable targets)
  • detectability_summary() - Added exclude_targets parameter; consolidated and centralized detectability formatting logic to avoid duplication; “High Abundance” label now shown only for PLASMA/SERUM matrix types; detectability set to NA for non-plasma/serum sample types; returns numeric columns by default (format=FALSE) to preserve downstream computation
  • writeNULISAseq() - IC target(s) now placed at the bottom rows of the RQ data sheet when include_IC_counts = TRUE
  • Target name sorting - Applied case-insensitive sorting (tolower) in quantifiability() and the QC report skeleton to ensure consistent ordering across platforms
  • Batch effect QC - Revised batch effect messaging; non-RC noDetectability targets kept in batch effect assessment; guarded against edge cases
  • DESCRIPTION - Minimum R version now declared (required for native pipe usage in lmNULISAseq.R); removed LazyData field; added withr to Suggests

Bug Fixes

  • Batch effect table - Fixed crash on pagination and PCA legend truncation in QC report
  • detectability_summary() - Fixed apply() dimension drop in detectability output table; fixed rowSums NA handling and guarded against empty target sets in aggregation
  • QCFlagTarget - Fixed incorrect exclusion of non-RC noDetectability targets from detectability calculations
  • Failed_Targets Run QC - Non-RC noDetectability targets now correctly included in Failed_Targets for CV criterion only
  • Well position - Corrected zero-padding for well position values
  • loadNULISAseq() - Fixed handling of AQ projects with list input; properly recreates numericCovariates for list inputs
  • Namespace fixes - Added explicit tibble:: prefix for column_to_rownames()

Testing

  • test-loadNULISAseq.R - New tests for list input support, AQ project equivalence, and coverage of all output fields
  • test-reverse-curve-detectability.R - New test suite for reverse curve and noDetectability target handling
  • test-writeNULISAseq.R - Expanded to test entire sheets for both RQ and AQ output; added case-insensitive formatting test; improved robustness to platform differences in string handling and floating point arithmetic

Infrastructure

  • Removed vignettes folder from build
  • Added Neuro220 XML files for testing
  • Updated CI workflows for hybrid branch pattern

Changes

Enhancements

  • render_QC_report() - Improved function parameter ordering and defaults:
    • xml_files parameter moved to first position for more intuitive usage
    • Added default values for output_filename (“NULISAseq_QC_Report.html”) and output_dir (current working directory)
    • Added default value for dataDir (current working directory)
    • Simplified Rmd_input_file path construction using system.file()
  • lod() - Enhanced documentation and parameter handling:
    • Improved parameter ordering (moved data_matrix before blanks)
    • Enhanced roxygen documentation with clearer return value descriptions
    • Added filtering to ensure targetNoOutlierDetection only includes targets present in data_matrix

Bug Fixes

  • quantifiability() - Fixed sample subsetting issue that could cause errors when sample lists don’t match between AQ data and sample information:
    • Now uses intersect() to find common samples between Data_AQ_aM and SampleNames
    • Correctly calculates sample counts for overall and subgroup quantifiability
    • Prevents errors when processing data with mismatched sample lists
  • loadNULISAseq() - Added calculation of LOD_pgmL (limit of detection in pg/mL units) from XML data for AQ assays
  • targetBoxplot() - Fixed parameter naming in lod() function call to use data_matrix= explicitly

Testing

  • New comprehensive test suites added to ensure code quality and reliability:
    • test-importNULISAseq.R - Tests for importNULISAseq() function with and without NULISAseqAQ package, including fallback mode validation and AQ data consistency checks
    • test-reverse-curve.R - Tests for reverse curve target handling, including correlation validation, data transformation verification, and NPQ value consistency between loadNULISAseq() and importNULISAseq()
    • test-writeNULISAseq.R - Tests for Excel output generation with both RQ-only and AQ data, including validation of sheet structure, column names, and specific data values
  • Test infrastructure improvements:
    • Moved test fixtures from inst/rmarkdown/templates/nulisaseq/skeleton/ to tests/testthat/fixtures/ for better organization
    • Removed unnecessary .gitignore file from skeleton template directory

Changes

Enhancements

  • importNULISAseq() - Improved robustness and flexibility for handling NULISAseq data files:
    • Added validation of internal AUTO_PLATE IDs with duplicate detection before processing
    • Enhanced parameter mapping for excludeSamples, excludeTargets, and control parameters (IC, IPC, SC, NC, Bridge, Calibrator) using prioritized keys (user-provided plate names, internal IDs, or fallback names)
    • Improved error handling with clear messages when duplicate plate IDs are detected with named exclusions
  • get_internal_plate_id() - New utility function to extract AUTO_PLATE ID from NULISAseq XML file headers

Overview

Version 1.4.0 represents a major expansion of the NULISAseqR package, introducing new analytical capabilities, enhanced visualization tools, and improved data processing functions.

New Features

Documentation & Installation

  • Added pkgdown website for package documentation
  • Added comprehensive vignette covering data loading, QC, differential expression, visualization, and case studies
  • Added MacOS and Windows installation instructions to documentation

Data Import & Export

  • importNULISAseq() - New streamlined function to import NULISAseq data from multiple XML files with improved error handling and validation
  • getXMLVersion() - Retrieve XML version information from NULISAseq files
  • Added support for XML v1.3.0 format compatibility in loadNULISAseq() which accommodates absolute quantification (AQ) NULISAseq assay panels

Quality Control

  • render_QC_report() - Generate automated quality control reports in HTML format
  • detectability_summary() - Summarize detectability across multiple runs and sample matrix types

Statistical Analysis

  • permutation_anova() - Perform permutation-based ANOVA testing for robust statistical inference
Single-Protein Prediction Models

Four new functions enable using single-target NPQ as a predictor in covariate-adjusted linear and logistic regression models:

  • lmNULISAseq_predict() - Predictions for continuous outcomes using linear regression models
  • lmerNULISAseq_predict() - Predictions for continuous outcomes from linear mixed-effects models for hierarchical data
  • glmNULISAseq_predict() - Predictions for binary/count outcomes using generalized linear models
  • glmerNULISAseq_predict() - Predictions for binary/count outcomes using generalized linear mixed-effects models for hierarchical data

Visualization Suite

Heatmaps
  • generate_heatmap() - Create publication-quality protein abundance heatmaps with ComplexHeatmap integration, supporting clustering, annotations, and custom color schemes
  • QCplateHeatmap() - Plate-level quality control heatmaps for identifying spatial patterns
Sample & Target Visualization
  • sampleBoxplot() - Boxplots showing sample distributions
  • sampleQCplot() - Comprehensive sample quality control plots with multiple metrics
  • plot_plateLayout() - Visual representation of plate layouts for experimental design
Dimensionality Reduction
  • generate_pca() - Principal component analysis with biplot generation and customizable aesthetics

NULISAseq Absolute Quantification (AQ) Analysis

  • targetQCplot() - Target-level QC visualizations for AQ assay performance monitoring
  • CV_AQ() - Calculate intra-plate and inter-plate coefficient of variation for AQ runs, with automatic handling of values outside the dynamic range
  • CV_AQ_Hist() - Visualize CV distributions for quality control monitoring
  • quantifiability() - Calculate quantifiability metrics across multiple runs
  • quantHist() - Histogram plots of quantifiability distributions
  • Added withinDR matrix to AQ output for dynamic range filtering

Enhanced Functions

Data Import/Export

  • readNULISAseq() - Improved XML parsing for better compatibility across file versions, enhanced error handling and validation
  • writeNULISAseq() - Refactored to utilize importNULISAseq function
  • writeUpdatedXML() - Now loads XML internally instead of requiring pre-loaded data

Visualization

  • volcanoPlot() - Major enhancements including:
    • Dual plotting mode: plot both unadjusted and FDR-adjusted p-values simultaneously with color coding (light colors for unadjusted significance, darker colors for FDR significance)
    • Flexible p-value input: accepts either single vector or named list with ‘unadj’ and ‘fdr’ p-values
    • Fold change thresholds: upper_log2FC_threshold and lower_log2FC_threshold parameters for labeling targets based on effect size
    • Automatic axis label adjustment based on p-value type (unadjusted vs FDR-adjusted)
    • Enhanced customization options for colors, fonts, and plot dimensions
  • targetBoxplot() - Better handling of normalized vs. unnormalized data
  • Target detectability boxplots margins adjusted to prevent clipping of long target names
  • alamarColorPalette() - Expanded color palette options

QC Report Improvements

  • Added batch effect QC section with configurable significance thresholds
  • Added ICC (Intraclass Correlation Coefficient) scatterplot for batch effect assessment
  • Added combined detectability calculation across plates when common sample matrix types exist
  • Both unnormalized and normalized sample boxplots now shown on all reports to better enable internal control QC checking
  • Improved QC plot formatting and spacing
  • Intra-plate CV “Overall” column renamed to “Average” for clarity
  • Detectability summary tables now show denominator (total targets per plate)
  • Interactive reactable tables allow row sorting by clicking on column names

Other Improvements

  • License updated to GPL-3

Bug Fixes

Data Processing

  • Fixed sample tag search to only search within Data subnodes, preventing false matches
  • Fixed NA handling in writeNULISAseq (empty cells vs “NA” string)
  • Fixed numeric covariate detection in linear model functions to properly identify NA values stored as strings

Quality Control

  • Fixed subsetting errors when data contains only 1 row or column (added drop=FALSE)
  • Fixed Sample QC percentage values (now multiplied by 100)
  • Fixed Target QC and CV table display issues

Statistical Models

  • Fixed drop=FALSE placement bug in predict model functions to prevent errors when dataset contains only 1 row or column

Visualization

  • Fixed special characters displaying as “|” in tables

Data Management

  • Fixed lazy-load database corruption by disabling LazyData
  • In QC report, fixed overall detectability weight calculation when sample matrices differ between plates

Breaking Changes

  • Version number updated from 1.2.0 to 1.4.0 (skipping 1.3.0 as standalone release)
  • Some function parameters may have changed order or names - please review documentation

Getting Help


Full Changelog: https://github.com/Alamar-Biosciences/NULISAseqR/compare/main…1.4