diff --git a/.Rbuildignore b/.Rbuildignore
index da54524e..51faea84 100644
--- a/.Rbuildignore
+++ b/.Rbuildignore
@@ -1,6 +1,5 @@
^.*\.Rproj$
^\.Rproj\.user$
-^\.travis\.yml$
^codecov\.yml$
^_pkgdown\.yml$
^docs$
diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml
new file mode 100644
index 00000000..ebec380e
--- /dev/null
+++ b/.github/workflows/test-coverage.yaml
@@ -0,0 +1,89 @@
+name: test-coverage
+
+on:
+ push:
+ branches: [master, devel]
+ pull_request:
+ branches: [master, devel]
+
+permissions: read-all
+
+jobs:
+ test-coverage:
+ runs-on: ubuntu-latest
+ env:
+ GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup R and Bioconductor
+ uses: grimbough/bioc-actions/setup-bioc@v1
+ with:
+ bioc-version: devel
+
+ - name: Install dependencies
+ run: |
+ # log4r (a dependency of our Imports:: MSstatsConvert) was archived
+ # (removed) from CRAN on 2026-07-14 for unresolved check issues --
+ # see https://cran.r-project.org/web/packages/log4r/index.html.
+ # That's an upstream removal, not a repo-configuration problem, so
+ # no amount of options(repos=...) tinkering fixes it. Install the
+ # last released version straight from the CRAN Archive first, so
+ # it's already satisfied when we resolve the rest of the
+ # dependencies below.
+ #
+ # We also skip r-lib/actions/setup-r-dependencies@v2 (which
+ # delegates to pak). pak resolves dependencies in a persistent
+ # background subprocess (pak:::remote()) that only forwards options
+ # matching pkg.*/async_http_*/Ncpus/BioC_mirror and env vars
+ # matching PKG_*/R_BIOC_VERSION/PATH/PACKAGEMANAGER_* -- `repos`
+ # itself is never forwarded, so options(repos=...), ~/.Rprofile,
+ # and RENV_CONFIG_REPOS_OVERRIDE (an renv-only variable pak doesn't
+ # read at all) are all invisible to it, making it awkward to inject
+ # an already-installed/archived package like this. Installing
+ # directly with remotes::install_deps() runs in this same R
+ # session, so it just sees whatever we configure here directly.
+ repos <- BiocManager::repositories()
+ repos["CRAN"] <- "https://cloud.r-project.org"
+ options(repos = repos)
+
+ install.packages(c("remotes", "sessioninfo", "xml2"))
+ install.packages(
+ "https://cran.r-project.org/src/contrib/Archive/log4r/log4r_0.4.4.tar.gz",
+ repos = NULL,
+ type = "source"
+ )
+ remotes::install_deps(dependencies = TRUE, upgrade = "never")
+ shell: Rscript {0}
+
+ - name: Test coverage
+ run: |
+ cov <- covr::package_coverage(
+ quiet = FALSE,
+ clean = FALSE,
+ install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package")
+ )
+ covr::to_cobertura(cov)
+ shell: Rscript {0}
+
+ - uses: codecov/codecov-action@v4
+ with:
+ file: ./cobertura.xml
+ plugin: noop
+ disable_search: true
+ token: ${{ secrets.CODECOV_TOKEN }}
+ fail_ci_if_error: false
+
+ - name: Show testthat output
+ if: always()
+ run: |
+ find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true
+ shell: bash
+
+ - name: Upload test results
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: coverage-test-failures
+ path: ${{ runner.temp }}/package
diff --git a/.github/workflows/update-citation-stats.yaml b/.github/workflows/update-citation-stats.yaml
new file mode 100644
index 00000000..26008853
--- /dev/null
+++ b/.github/workflows/update-citation-stats.yaml
@@ -0,0 +1,37 @@
+name: update-citation-stats
+
+on:
+ schedule:
+ # 06:00 UTC on the 1st of every month
+ - cron: "0 6 1 * *"
+ workflow_dispatch: {}
+
+permissions:
+ contents: write
+
+jobs:
+ update-citation-stats:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: master
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.x"
+
+ - name: Refresh citation-count table in README
+ run: python scripts/update_citation_counts.py
+
+ - name: Commit and push if changed
+ run: |
+ if git diff --quiet -- README.md; then
+ echo "No change in citation counts."
+ exit 0
+ fi
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add README.md
+ git commit -m "Update MSstats ecosystem citation counts"
+ git push origin HEAD:master
diff --git a/.github/workflows/update-download-stats.yaml b/.github/workflows/update-download-stats.yaml
new file mode 100644
index 00000000..f50bd830
--- /dev/null
+++ b/.github/workflows/update-download-stats.yaml
@@ -0,0 +1,37 @@
+name: update-download-stats
+
+on:
+ schedule:
+ # 06:00 UTC on the 1st of every month
+ - cron: "0 6 1 * *"
+ workflow_dispatch: {}
+
+permissions:
+ contents: write
+
+jobs:
+ update-download-stats:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: master
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.x"
+
+ - name: Refresh download-statistics table in README
+ run: python scripts/update_download_stats.py
+
+ - name: Commit and push if changed
+ run: |
+ if git diff --quiet -- README.md; then
+ echo "No change in download statistics."
+ exit 0
+ fi
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add README.md
+ git commit -m "Update MSstats ecosystem download statistics"
+ git push origin HEAD:master
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 4ae81311..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-# R for travis: see documentation at https://docs.travis-ci.com/user/languages/r
-
-language: r
-os: linux
-dist: xenial
-cache: packages
-warnings_are_errors: false
-
-after_success:
- - Rscript -e 'covr::codecov()'
-branches:
- only:
- - master
- - develop
-script:
- - zip -r latest *
- - mkdir -p deployment/msstats-dev
- - mv latest.zip deployment/msstats-dev/latest.zip
-
-deploy:
-- provider: s3
- access_key_id: $AWS_MASTER_ACCESS_ID
- secret_access_key: $AWS_MASTER_SECRET_KEY
- local_dir: deployment/msstats-dev
- skip_cleanup: true
- on:
- branch: develop
- repo: Vitek-Lab/MSstats-dev
- bucket: $AWS_MASTER_S3_BUCKET
- region: $AWS_REGION
-- provider: codedeploy
- access_key_id: $AWS_MASTER_ACCESS_ID
- secret_access_key: $AWS_MASTER_SECRET_KEY
- bucket: $AWS_MASTER_S3_BUCKET
- key: latest.zip
- bundle_type: zip
- application: $AWS_MASTER_DEPLOYMENT_APPLICATION_NAME
- deployment_group: $AWS_MASTER_DEPLOYMENT_GROUP_NAME
- region: $AWS_REGION
- on:
- branch: develop
diff --git a/README.md b/README.md
index 2098239a..f6581a96 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,346 @@
-MSstats
-=======
+# MSstats
- [](https://travis-ci.org/Vitek-Lab/MSstats-dev)
-[](https://codecov.io/gh/Vitek-Lab/MSstats-dev?branch=master)
+[](https://bioconductor.org/checkResults/release/bioc-LATEST/MSstats/)
+[](https://bioconductor.org/checkResults/devel/bioc-LATEST/MSstats/)
+[](https://codecov.io/gh/Vitek-Lab/MSstats)
+[](https://bioconductor.org/packages/stats/bioc/MSstats/)
+[](https://bioconductor.org/packages/release/bioc/html/MSstats.html#since)
+[](https://opensource.org/licenses/Artistic-2.0)
-MSstats is an R-based/Bioconductor package for statistical relative quantification of peptides and proteins in mass spectrometry-based proteomic experiments. It is applicable to multiple types of sample preparation, including label-free workflows, workflows that use stable isotope labeled reference proteins and peptides, and work-flows that use fractionation. It is applicable to targeted Selected Reactin Monitoring(SRM), Data-Dependent Acquisiton(DDA or shotgun), and Data-Independent Acquisition(DIA or SWATH-MS). This github page is for sharing source and testing. The official webpage is msstats.org
+MSstats is an R/Bioconductor package for statistical relative quantification of
+proteins and peptides in mass spectrometry-based proteomics experiments. It
+supports label-free and label-based (isotope-labeled reference peptide/protein)
+workflows, and is applicable to targeted Selected Reaction Monitoring (SRM),
+Data-Dependent Acquisition (DDA/shotgun), and Data-Independent Acquisition
+(DIA/SWATH-MS) experiments, including designs with fractionation. Given
+identified and quantified peaks from upstream tools (Skyline, MaxQuant,
+Proteome Discoverer, Spectronaut, DIA-NN, FragPipe, OpenSWATH, and others),
+MSstats performs normalization, missing value imputation, feature selection, run-level
+summarization, and model-based statistical testing to detect differentially
+abundant proteins or peptides across conditions. Because the underlying
+statistical framework operates on generic quantitative features, it can be easily extended to other quantitative signals, such as targeted metabolomics data.
+
+MSstats has been developed and maintained by the [Vitek Lab](https://olga-vitek-lab.khoury.northeastern.edu/)
+at the Khoury College of Computer Sciences, Northeastern University, since
+2012. The package and its documentation are also available at
+[msstats.org](http://msstats.org).
+
+This repository is used for active development and testing of MSstats. The
+package is released through [Bioconductor](https://bioconductor.org/packages/MSstats)
+on its regular 6-month release cycle.
+
+## At a Glance
+
+- **13+ years** in Bioconductor (since release 2.13, 2013)
+- **9 packages** in the MSstats ecosystem, covering DDA/DIA/SRM, TMT, PTMs,
+ LiP-MS, large-scale/out-of-memory data, network analysis, dose-response, and
+ a no-code GUI
+- **13 peer-reviewed publications / preprints**, [~2,000+ citations](#citations)
+ combined (see [Citations](#citations))
+- **~6,0000 monthly downloads** across the ecosystem (see
+ [Download statistics](#download-statistics)), tracked automatically from
+ Bioconductor's own logs
+
+## The MSstats Ecosystem
+
+MSstats has grown into a family of packages that address distinct needs in MS-based proteomics analysis. Each package targets a different experiment type or stage
+of the analysis pipeline:
+
+```mermaid
+flowchart LR
+ A["Upstream search tools
Skyline · MaxQuant · Spectronaut
DIA-NN · FragPipe · OpenMS/OpenSWATH · ..."] --> B["MSstatsConvert
(format converters)"]
+ A --> G["MSstatsBig
(larger-than-memory converters)"]
+ B --> C["MSstats
DDA / DIA / SRM
label-free & label-based"]
+ G --> C
+ C --> D["MSstatsTMT
isobaric labeling"]
+ C --> E["MSstatsPTM
post-translational mods"]
+ C --> F["MSstatsLiP
limited proteolysis"]
+ C --> I["MSstatsResponse
dose-response"]
+ D --> H["MSstatsBioNet
network enrichment"]
+ E --> H
+ F --> H
+ I --> H
+ C --> H
+ C --> J["MSstatsShiny
point-and-click GUI"]
+ D --> J
+ E --> J
+ F --> J
+ I --> J
+ H --> J
+```
+
+| Package | Description |
+| --- | --- |
+| **[MSstats](https://bioconductor.org/packages/MSstats)** | Core package for DDA, SRM, and DIA label-free/label-based experiments. |
+| **[MSstatsTMT](https://bioconductor.org/packages/MSstatsTMT)** ([GitHub](https://github.com/Vitek-Lab/MSstatsTMT)) | Statistical analysis of experiments with isobaric (TMT) labeling and multiple mixtures, including repeated-measures designs. |
+| **[MSstatsPTM](https://bioconductor.org/packages/MSstatsPTM)** ([GitHub](https://github.com/Vitek-Lab/MSstatsPTM)) | Quantitative analysis of post-translational modifications (PTMs), jointly modeling PTM-site and protein-level abundance. |
+| **[MSstatsLiP](https://bioconductor.org/packages/MSstatsLiP)** ([GitHub](https://github.com/Vitek-Lab/MSstatsLiP)) | Analysis of limited proteolysis mass spectrometry (LiP-MS) data to detect protein structural changes. |
+| **[MSstatsBig](https://bioconductor.org/packages/MSstatsBig)** ([GitHub](https://github.com/Vitek-Lab/MSstatsBig)) | Converters and tooling for processing larger-than-memory quantitative datasets. |
+| **[MSstatsShiny](https://bioconductor.org/packages/MSstatsShiny)** ([GitHub](https://github.com/Vitek-Lab/MSstatsShiny) · [web app](https://www.msstatsshiny.com)) | Point-and-click R-Shiny GUI integrating MSstats family of packages. |
+| **[MSstatsBioNet](https://bioconductor.org/packages/MSstatsBioNet)** ([GitHub](https://github.com/Vitek-Lab/MSstatsBioNet)) | Network analysis and enrichment of MSstats differential abundance results using prior-knowledge networks (e.g., INDRA). |
+| **[MSstatsResponse](https://bioconductor.org/packages/MSstatsResponse)** ([GitHub](https://github.com/Vitek-Lab/MSstatsResponse)) | Semi-parametric dose-response modeling for chemoproteomics experiments (drug-protein interaction / IC50 estimation). |
+| **[MSstatsConvert](https://bioconductor.org/packages/MSstatsConvert)** ([GitHub](https://github.com/Vitek-Lab/MSstatsConvert)) | Shared converters that translate output from Skyline, MaxQuant, Proteome Discoverer, Spectronaut, DIA-NN, FragPipe, OpenSWATH, and more into MSstats format. |
+
+MSstatsResponse's semi-parametric curve-fitting approach to dose-response data
+generalizes beyond drug-protein interaction/IC50 estimation: the same concept
+applies to related experiment types such as Thermal Proteome Profiling (TPP)
+and protein turnover kinetics, where abundance is likewise modeled as a smooth
+function of a continuous variable (temperature or time) rather than a fixed
+curve shape.
+
+## Developers
+
+MSstats is developed and maintained out of the [Vitek Lab](https://olga-vitek-lab.khoury.northeastern.edu/)
+at Northeastern University.
+
+Current developers:
+
+- Devon Kohler
+- Anthony Wu
+- Mateusz Staniak
+- Sarah Szvetecz
+
+Former developers include Meena Choi, Deril Raju, Tsung-Heng Tsai, and Ting Huang. See the full author list in [DESCRIPTION](DESCRIPTION) and the
+lab's [publications page](https://olga-vitek-lab.khoury.northeastern.edu/publications/)
+for the wider set of contributors across the MSstats ecosystem.
+
+The lab also organizes the annual [May Institute](https://computationalproteomics.khoury.northeastern.edu/),
+a computational proteomics training program at Northeastern University
+covering mass spectrometry, statistics, and bioinformatics, which regularly
+features MSstats developers as instructors.
+
+## Installation
+
+```r
+if (!requireNamespace("BiocManager", quietly = TRUE))
+ install.packages("BiocManager")
+
+BiocManager::install("MSstats")
+```
+
+The development version can be installed directly from this repository:
+
+```r
+BiocManager::install("Vitek-Lab/MSstats", ref = "devel")
+```
+
+## Quick Start
+
+```r
+library(MSstats)
+
+# Use one of the datasets bundled with the package (label-based SRM example)
+data("SRMRawData")
+
+# Pre-process: log-transform, normalize, and summarize to protein level
+QuantData <- dataProcess(SRMRawData, use_log_file = FALSE)
+
+# Test all pairwise comparisons between conditions
+testResults <- groupComparison(contrast.matrix = "pairwise",
+ data = QuantData,
+ use_log_file = FALSE)
+
+head(testResults$ComparisonResult)
+```
+
+Real experiments typically start by converting a search engine/tool's output
+(e.g., Skyline, MaxQuant, Spectronaut, DIA-NN, FragPipe) into MSstats format
+with a `*toMSstatsFormat` converter from `MSstatsConvert`, then proceeding with
+`dataProcess()` and `groupComparison()` as above. See the vignettes below for
+complete, tool-specific examples.
+
+## Supported Converters
+
+MSstats does not read raw search-tool output directly. Instead, a converter
+translates each tool's report into MSstats format (one row per feature, run,
+and condition) before `dataProcess()` is called. These converters are
+implemented in [MSstatsConvert](https://bioconductor.org/packages/MSstatsConvert):
+
+| Search tool / format | Converter function |
+| --- | --- |
+| Skyline | `SkylinetoMSstatsFormat()` |
+| MaxQuant | `MaxQtoMSstatsFormat()` |
+| Progenesis | `ProgenesistoMSstatsFormat()` |
+| Spectronaut | `SpectronauttoMSstatsFormat()` |
+| Proteome Discoverer | `PDtoMSstatsFormat()` |
+| DIA-NN | `DIANNtoMSstatsFormat()` |
+| DIA-Umpire | `DIAUmpiretoMSstatsFormat()` |
+| FragPipe | `FragPipetoMSstatsFormat()` |
+| OpenMS | `OpenMStoMSstatsFormat()` |
+| OpenSWATH | `OpenSWATHtoMSstatsFormat()` |
+| Metamorpheus | `MetamorpheusToMSstatsFormat()` |
+
+MSstatsConvert also provides a set of `*toMSstatsTMTFormat()` converters
+(MaxQuant, OpenMS, Proteome Discoverer, Philosopher/FragPipe, Protein
+Prospector, SpectroMine) for isobaric-labeling experiments, used with
+[MSstatsTMT](https://bioconductor.org/packages/MSstatsTMT) instead of MSstats.
+See the [End to End Workflow vignette](vignettes/MSstatsWorkflow.Rmd) for the
+required input files and options for each converter.
+
+## Documentation
+
+- [MSstats: Protein/Peptide significance analysis](vignettes/MSstats.Rmd) — overview of all functionality
+- [MSstats: End to End Workflow](vignettes/MSstatsWorkflow.Rmd) — full worked example from raw data to results
+- [MSstats+ vignette](vignettes/MSstatsPlus.Rmd)
+- [Official website: msstats.org](http://msstats.org)
+- [Bioconductor package page and reference manual](https://bioconductor.org/packages/MSstats)
+
+## Getting Help / Reporting Bugs
+
+- **Questions about usage, statistical methods, or troubleshooting:** please
+ post to the [MSstats Google Group](https://groups.google.com/forum/#!forum/msstats).
+ This is monitored by the development team and searchable, so it's the
+ fastest way to get help and to see if your question has already been
+ answered.
+- **Bug reports and feature requests for this repository:** please open a
+ [GitHub issue](https://github.com/Vitek-Lab/MSstats/issues).
+
+## Usage & Impact
+
+MSstats has been part of Bioconductor since release 2.13 (2013) and is used
+across the proteomics community, integrated as an external tool in Skyline and
+underlying the MSstats family of packages and MSstatsShiny.
+
+### Citations
+
+
+
+_Citation counts from [OpenAlex](https://openalex.org), updated monthly. Google Scholar counts are typically higher, since Scholar indexes a broader range of sources (theses, gray literature, etc.); OpenAlex has a free, stable, official API. Last updated 2026-07-16 (UTC)._
+
+| Paper | Citations |
+| --- | --- |
+| [Statistical design of MS proteomics experiments (2009)](https://doi.org/10.1021/pr8010099) | 265 |
+| [MSstats (2014)](https://doi.org/10.1093/bioinformatics/btu305) | 1,161 |
+| [MSstats feature selection (2020)](https://doi.org/10.1074/mcp.RA119.001792) | 37 |
+| [MSstats 4.0 (2023)](https://doi.org/10.1021/acs.jproteome.2c00834) | 135 |
+| [MSstats & FragPipe DIA workflow (2024)](https://doi.org/10.1038/s41596-024-01000-3) | 13 |
+| [MSstats+ / longitudinal peak quality (2025, preprint)](https://doi.org/10.1101/2025.09.11.675573) | 1 |
+| [MSstatsTMT (2020)](https://doi.org/10.1074/mcp.RA120.002105) | 204 |
+| [MSstatsTMT repeated measures (2023)](https://doi.org/10.1021/acs.jproteome.3c00155) | 14 |
+| [MSstatsTMT for Thermal Proteome Profiling (2025)](https://doi.org/10.1016/j.mcpro.2025.100999) | 1 |
+| [MSstatsPTM (2022)](https://doi.org/10.1016/j.mcpro.2022.100477) | 53 |
+| [MSstatsLiP / LiP-MS protocol (2023)](https://doi.org/10.1038/s41596-022-00771-x) | 127 |
+| [MSstatsShiny (2023)](https://doi.org/10.1021/acs.jproteome.2c00603) | 23 |
+| [MSstatsResponse (2026, preprint)](https://doi.org/10.64898/2026.03.09.710598) | 1 |
+| **Total** | **2,035** |
+
+
+
+This table is regenerated automatically alongside the download statistics
+below by
+[`.github/workflows/update-citation-stats.yaml`](.github/workflows/update-citation-stats.yaml),
+using [OpenAlex](https://openalex.org) rather than Google Scholar — Scholar
+has no official API and blocks automated requests, which makes it unreliable
+for an unattended scheduled job. If you'd like exact Google Scholar counts,
+see the [MSstats citations search](https://scholar.google.com/scholar?q=MSstats+Vitek+proteomics)
+directly.
+
+### Download statistics
+
+
+
+_Average monthly downloads over the last 6 complete months, computed directly from Bioconductor's download logs. Last updated 2026-07-15 (UTC)._
+
+| Package | Avg. monthly downloads |
+| --- | --- |
+| [MSstats](https://bioconductor.org/packages/stats/bioc/MSstats/) | 1,566 |
+| [MSstatsTMT](https://bioconductor.org/packages/stats/bioc/MSstatsTMT/) | 796 |
+| [MSstatsPTM](https://bioconductor.org/packages/stats/bioc/MSstatsPTM/) | 632 |
+| [MSstatsLiP](https://bioconductor.org/packages/stats/bioc/MSstatsLiP/) | 442 |
+| [MSstatsBig](https://bioconductor.org/packages/stats/bioc/MSstatsBig/) | 384 |
+| [MSstatsShiny](https://bioconductor.org/packages/stats/bioc/MSstatsShiny/) | 437 |
+| [MSstatsBioNet](https://bioconductor.org/packages/stats/bioc/MSstatsBioNet/) | 311 |
+| [MSstatsResponse](https://bioconductor.org/packages/stats/bioc/MSstatsResponse/) | 298 |
+| [MSstatsConvert](https://bioconductor.org/packages/stats/bioc/MSstatsConvert/) | 1,100 |
+
+
+
+This table is regenerated automatically once a month by
+[`.github/workflows/update-download-stats.yaml`](.github/workflows/update-download-stats.yaml),
+which pulls the latest numbers from
+[Bioconductor's download logs](https://bioconductor.org/packages/stats/bioc/MSstats/)
+for every package in the ecosystem.
+
+## References
+
+If you use MSstats or a package from the MSstats ecosystem, please cite the
+relevant publication(s):
+
+1. Oberg AL, Vitek O. **Statistical design of quantitative mass spectrometry-based
+ proteomic experiments.** *J Proteome Res*. 2009;8(5):2144-2156.
+ [DOI: 10.1021/pr8010099](https://doi.org/10.1021/pr8010099)
+2. Choi M, Chang CY, Clough T, Broudy D, Killeen T, MacLean B, Vitek O.
+ **MSstats: an R package for statistical analysis of quantitative mass
+ spectrometry-based proteomic experiments.** *Bioinformatics*. 2014;30(17):2524-2526.
+ [DOI: 10.1093/bioinformatics/btu305](https://doi.org/10.1093/bioinformatics/btu305)
+3. Tsai TH, Choi M, Banfai B, Liu Y, MacLean BX, Dunkley T, Vitek O.
+ **Selection of Features with Consistent Profiles Improves Relative Protein
+ Quantification in Mass Spectrometry Experiments.** *Mol Cell Proteomics*.
+ 2020;19(6):944-959.
+ [DOI: 10.1074/mcp.RA119.001792](https://doi.org/10.1074/mcp.RA119.001792)
+4. Kohler D, Staniak M, Tsai TH, Huang T, Shulman N, Bernhardt OM, MacLean BX,
+ Nesvizhskii AI, Reiter L, Sabido E, Choi M, Vitek O. **MSstats Version 4.0:
+ Statistical Analyses of Quantitative Mass Spectrometry-Based Proteomic
+ Experiments with Chromatography-Based Quantification at Scale.**
+ *J Proteome Res*. 2023;22(5):1466-1482.
+ [DOI: 10.1021/acs.jproteome.2c00834](https://doi.org/10.1021/acs.jproteome.2c00834)
+5. Kohler D, Vitek O, et al. **An MSstats workflow for detecting differentially
+ abundant proteins in large-scale data-independent acquisition mass
+ spectrometry experiments with FragPipe processing.** *Nat Protoc*.
+ 2024;19:2915-2938.
+ [DOI: 10.1038/s41596-024-01000-3](https://doi.org/10.1038/s41596-024-01000-3)
+6. Kohler D, Dogu E, Bhattacharya M, Karayel O, Magana M, Wu A, Anania VG,
+ Vitek O. **Accounting for longitudinal peak quality metrics with MSstats+
+ enhances differential analysis in proteomic experiments with
+ data-independent acquisition** (introduces MSstats+). *bioRxiv*. 2025.
+ [DOI: 10.1101/2025.09.11.675573](https://doi.org/10.1101/2025.09.11.675573)
+7. Huang T, Choi M, Tzouros M, Golling S, Pandya NJ, Banfai B, Dunkley T,
+ Vitek O. **MSstatsTMT: Statistical Detection of Differentially Abundant
+ Proteins in Experiments with Isobaric Labeling and Multiple Mixtures.**
+ *Mol Cell Proteomics*. 2020;19(10):1706-1723.
+ [DOI: 10.1074/mcp.RA120.002105](https://doi.org/10.1074/mcp.RA120.002105)
+8. Huang T, Staniak M, da Veiga Leprevost F, Figueroa-Navedo AM, Ivanov AR,
+ Nesvizhskii AI, Choi M, Vitek O. **Statistical Detection of Differentially
+ Abundant Proteins in Experiments with Repeated Measures Designs and
+ Isobaric Labeling.** *J Proteome Res*. 2023;22(8):2641-2659.
+ [DOI: 10.1021/acs.jproteome.3c00155](https://doi.org/10.1021/acs.jproteome.3c00155)
+9. Figueroa-Navedo AM, Kapre R, Gupta T, Xu Y, Phaneuf CG, Jean Beltran PM,
+ Xue L, Ivanov AR, Vitek O. **MSstatsTMT Improves Accuracy of Thermal
+ Proteome Profiling.** *Mol Cell Proteomics*. 2025;24(8):100999.
+ [DOI: 10.1016/j.mcpro.2025.100999](https://doi.org/10.1016/j.mcpro.2025.100999)
+10. Kohler D, Tsai TH, Verschueren E, Huang T, Hinkle T, Phu L, Choi M, Vitek O.
+ **MSstatsPTM: Statistical Relative Quantification of Posttranslational
+ Modifications in Bottom-Up Mass Spectrometry-Based Proteomics.**
+ *Mol Cell Proteomics*. 2022;22(1):100477.
+ [DOI: 10.1016/j.mcpro.2022.100477](https://doi.org/10.1016/j.mcpro.2022.100477)
+11. Malinovska L, Cappelletti V, Kohler D, Piazza I, Tsai TH, Pepelnjak M,
+ Stalder P, Dörig C, Sesterhenn F, Elsässer F, Kralickova L, Beaton N,
+ Reiter L, de Souza N, Vitek O, Picotti P. **Proteome-wide structural
+ changes measured with limited proteolysis-mass spectrometry: an advanced
+ protocol for high-throughput applications** (introduces MSstatsLiP).
+ *Nat Protoc*. 2023;18(3):659-682.
+ [DOI: 10.1038/s41596-022-00771-x](https://doi.org/10.1038/s41596-022-00771-x)
+12. Kohler D, Kaza M, Pasi C, Huang T, Staniak M, Mohandas D, Sabido E, Choi M,
+ Vitek O. **MSstatsShiny: A GUI for Versatile, Scalable, and Reproducible
+ Statistical Analyses of Quantitative Proteomic Experiments.**
+ *J Proteome Res*. 2023;22(2):551-556.
+ [DOI: 10.1021/acs.jproteome.2c00603](https://doi.org/10.1021/acs.jproteome.2c00603)
+13. Szvetecz S, Kohler D, Vitek O. **MSstatsResponse: Semi-parametric
+ statistical model enhances detection of drug-protein interactions in
+ chemoproteomics experiments.** *bioRxiv*. 2026.
+ [DOI: 10.64898/2026.03.09.710598](https://doi.org/10.64898/2026.03.09.710598)
+
+MSstatsBioNet does not yet have a dedicated publication; see the
+[Bioconductor package page](https://bioconductor.org/packages/MSstatsBioNet)
+for details and how to cite the software directly.
+
+## Funding
+
+MSstats development has been supported by the Chan Zuckerberg Initiative's
+[Essential Open Source Software for Science](https://chanzuckerberg.com/eoss/proposals/).
+
+## License
+
+MSstats is released under the [Artistic-2.0](https://opensource.org/licenses/Artistic-2.0) license.
diff --git a/appspec.yml b/appspec.yml
deleted file mode 100644
index a671e502..00000000
--- a/appspec.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-version: 0.0
-os: linux
-files:
- - source: ./
- destination: /home/rstudio/code/deployment/msstats-dev
-# Executes After install lifecycle during deployment
-hooks:
- AfterInstall:
- - location: inst/scripts/after_install.sh
- runas: ubuntu
\ No newline at end of file
diff --git a/man/figures/example_heatmap.png b/man/figures/example_heatmap.png
new file mode 100644
index 00000000..8eef9cd1
Binary files /dev/null and b/man/figures/example_heatmap.png differ
diff --git a/scripts/update_citation_counts.py b/scripts/update_citation_counts.py
new file mode 100644
index 00000000..7b7f9e79
--- /dev/null
+++ b/scripts/update_citation_counts.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+"""Refresh the citation-count table in README.md using OpenAlex.
+
+OpenAlex (https://openalex.org) is used instead of Google Scholar because it
+has a free, stable, official API. Google Scholar has no API and actively
+blocks automated/datacenter-IP requests, which makes it unsuitable for an
+unattended, scheduled job like this one. Counts will run a little lower than
+Google Scholar's, since Scholar indexes a broader (and less curated) set of
+sources.
+
+Uses only the standard library so it can run on a bare GitHub Actions runner
+with no extra setup.
+"""
+import datetime
+import json
+import pathlib
+import urllib.request
+
+README_PATH = pathlib.Path(__file__).resolve().parent.parent / "README.md"
+START_MARKER = ""
+END_MARKER = ""
+
+# (short label, doi) for every paper in the References section, in the same
+# order they appear there.
+PAPERS = [
+ ("Statistical design of MS proteomics experiments (2009)", "10.1021/pr8010099"),
+ ("MSstats (2014)", "10.1093/bioinformatics/btu305"),
+ ("MSstats feature selection (2020)", "10.1074/mcp.RA119.001792"),
+ ("MSstats 4.0 (2023)", "10.1021/acs.jproteome.2c00834"),
+ ("MSstats + FragPipe DIA workflow (2024)", "10.1038/s41596-024-01000-3"),
+ ("MSstats+ / longitudinal peak quality (2025, preprint)", "10.1101/2025.09.11.675573"),
+ ("MSstatsTMT (2020)", "10.1074/mcp.RA120.002105"),
+ ("MSstatsTMT repeated measures (2023)", "10.1021/acs.jproteome.3c00155"),
+ ("MSstatsTMT for Thermal Proteome Profiling (2025)", "10.1016/j.mcpro.2025.100999"),
+ ("MSstatsPTM (2022)", "10.1016/j.mcpro.2022.100477"),
+ ("MSstatsLiP / LiP-MS protocol (2023)", "10.1038/s41596-022-00771-x"),
+ ("MSstatsShiny (2023)", "10.1021/acs.jproteome.2c00603"),
+ ("MSstatsResponse (2026, preprint)", "10.64898/2026.03.09.710598"),
+]
+
+OPENALEX_URL = "https://api.openalex.org/works/https://doi.org/{doi}"
+
+
+def fetch_cited_by_count(doi: str):
+ req = urllib.request.Request(
+ OPENALEX_URL.format(doi=doi),
+ headers={"User-Agent": "msstats-readme-stats-bot (mailto:none)"},
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=30) as response:
+ data = json.load(response)
+ except Exception:
+ return None
+ return data.get("cited_by_count")
+
+
+def format_table(today: datetime.date) -> str:
+ lines = [
+ START_MARKER,
+ "",
+ "_Citation counts from [OpenAlex](https://openalex.org), updated "
+ "monthly. Google Scholar counts are typically higher, since Scholar "
+ "indexes a broader range of sources (theses, gray literature, etc.); "
+ f"OpenAlex has a free, stable, official API. Last updated {today.isoformat()} (UTC)._",
+ "",
+ "| Paper | Citations |",
+ "| --- | --- |",
+ ]
+ total = 0
+ any_missing = False
+ for label, doi in PAPERS:
+ count = fetch_cited_by_count(doi)
+ if count is None:
+ any_missing = True
+ cell = "n/a"
+ else:
+ total += count
+ cell = f"{count:,}"
+ lines.append(f"| [{label}](https://doi.org/{doi}) | {cell} |")
+
+ total_cell = f"{total:,}" + ("+" if any_missing else "")
+ lines.append(f"| **Total** | **{total_cell}** |")
+ lines.append("")
+ lines.append(END_MARKER)
+ return "\n".join(lines)
+
+
+def main() -> None:
+ today = datetime.datetime.now(datetime.timezone.utc).date()
+ new_block = format_table(today)
+
+ text = README_PATH.read_text()
+ if START_MARKER not in text or END_MARKER not in text:
+ raise SystemExit(
+ f"Could not find {START_MARKER} / {END_MARKER} markers in README.md"
+ )
+ before, rest = text.split(START_MARKER, 1)
+ _old_block, after = rest.split(END_MARKER, 1)
+ updated_text = before + new_block + after
+ README_PATH.write_text(updated_text)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/update_download_stats.py b/scripts/update_download_stats.py
new file mode 100644
index 00000000..5f97a4f9
--- /dev/null
+++ b/scripts/update_download_stats.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+"""Refresh the download-statistics table in README.md from Bioconductor's
+per-package download stats, and write it between the
+DOWNLOAD-STATS:START/END markers.
+
+Uses only the standard library so it can run on a bare GitHub Actions
+runner with no extra setup.
+"""
+import datetime
+import pathlib
+import urllib.request
+
+README_PATH = pathlib.Path(__file__).resolve().parent.parent / "README.md"
+START_MARKER = ""
+END_MARKER = ""
+MONTHS_TO_AVERAGE = 6
+
+# (package name, display label) in the order they should appear in the table.
+PACKAGES = [
+ ("MSstats", "MSstats"),
+ ("MSstatsTMT", "MSstatsTMT"),
+ ("MSstatsPTM", "MSstatsPTM"),
+ ("MSstatsLiP", "MSstatsLiP"),
+ ("MSstatsBig", "MSstatsBig"),
+ ("MSstatsShiny", "MSstatsShiny"),
+ ("MSstatsBioNet", "MSstatsBioNet"),
+ ("MSstatsResponse", "MSstatsResponse"),
+ ("MSstatsConvert", "MSstatsConvert"),
+]
+
+MONTH_NUMBERS = {
+ "Jan": 1, "Feb": 2, "Mar": 3, "Apr": 4, "May": 5, "Jun": 6,
+ "Jul": 7, "Aug": 8, "Sep": 9, "Oct": 10, "Nov": 11, "Dec": 12,
+}
+
+STATS_URL = "https://bioconductor.org/packages/stats/bioc/{pkg}/{pkg}_stats.tab"
+
+
+def fetch_stats(pkg: str) -> str:
+ req = urllib.request.Request(
+ STATS_URL.format(pkg=pkg),
+ headers={"User-Agent": "msstats-readme-stats-bot/1.0"},
+ )
+ with urllib.request.urlopen(req, timeout=30) as response:
+ return response.read().decode("utf-8")
+
+
+def completed_months(raw_tab: str, today: datetime.date):
+ """Yield (year, month_num, downloads) for months strictly before the
+ current, still-in-progress month, most recent first."""
+ rows = []
+ for line in raw_tab.splitlines()[1:]:
+ parts = line.split("\t")
+ if len(parts) != 4:
+ continue
+ year_str, month_str, _ips, downloads_str = parts
+ if month_str == "all":
+ continue
+ year = int(year_str)
+ month = MONTH_NUMBERS[month_str]
+ if (year, month) >= (today.year, today.month):
+ continue
+ rows.append((year, month, int(downloads_str)))
+ rows.sort(reverse=True)
+ return rows
+
+
+def average_recent_downloads(pkg: str, today: datetime.date):
+ raw_tab = fetch_stats(pkg)
+ rows = completed_months(raw_tab, today)[:MONTHS_TO_AVERAGE]
+ if not rows:
+ return None, 0
+ avg = round(sum(downloads for _, _, downloads in rows) / len(rows))
+ return avg, len(rows)
+
+
+def format_table(today: datetime.date) -> str:
+ lines = [
+ START_MARKER,
+ "",
+ f"_Average monthly downloads over the last {MONTHS_TO_AVERAGE} complete "
+ f"months, computed directly from Bioconductor's download logs. Last "
+ f"updated {today.isoformat()} (UTC)._",
+ "",
+ "| Package | Avg. monthly downloads |",
+ "| --- | --- |",
+ ]
+ for pkg, label in PACKAGES:
+ avg, n_months = average_recent_downloads(pkg, today)
+ if avg is None:
+ cell = "n/a (no completed months yet)"
+ elif n_months < MONTHS_TO_AVERAGE:
+ cell = f"{avg:,} (avg. over {n_months} available month{'s' if n_months != 1 else ''})"
+ else:
+ cell = f"{avg:,}"
+ lines.append(
+ f"| [{label}](https://bioconductor.org/packages/stats/bioc/{pkg}/) | {cell} |"
+ )
+ lines.append("")
+ lines.append(END_MARKER)
+ return "\n".join(lines)
+
+
+def main() -> None:
+ today = datetime.datetime.now(datetime.timezone.utc).date()
+ new_block = format_table(today)
+
+ text = README_PATH.read_text()
+ if START_MARKER not in text or END_MARKER not in text:
+ raise SystemExit(
+ f"Could not find {START_MARKER} / {END_MARKER} markers in README.md"
+ )
+ before, rest = text.split(START_MARKER, 1)
+ _old_block, after = rest.split(END_MARKER, 1)
+ updated_text = before + new_block + after
+ README_PATH.write_text(updated_text)
+
+
+if __name__ == "__main__":
+ main()