From c0265afb353a11ed5c3bb51b79c11a769edc8427 Mon Sep 17 00:00:00 2001 From: devonjkohler Date: Wed, 15 Jul 2026 15:11:48 -0400 Subject: [PATCH 01/13] updated README --- .Rbuildignore | 4 +- .github/workflows/test-coverage.yaml | 58 ++++++++ .travis.yml | 41 ------ README.md | 195 ++++++++++++++++++++++++++- appspec.yml | 10 -- 5 files changed, 249 insertions(+), 59 deletions(-) create mode 100644 .github/workflows/test-coverage.yaml delete mode 100644 .travis.yml delete mode 100644 appspec.yml diff --git a/.Rbuildignore b/.Rbuildignore index 151285cf..5901b7bc 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -1,8 +1,6 @@ ^.*\.Rproj$ ^\.Rproj\.user$ -^\.travis\.yml$ ^codecov\.yml$ ^_pkgdown\.yml$ ^docs$ -^pkgdown$ -appspec.yml \ No newline at end of file +^pkgdown$ \ No newline at end of file diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml new file mode 100644 index 00000000..cf86b090 --- /dev/null +++ b/.github/workflows/test-coverage.yaml @@ -0,0 +1,58 @@ +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 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr, any::xml2 + needs: coverage + + - 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/.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..ca0f1008 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,194 @@ -MSstats -======= +# MSstats - [![Travis build status](https://travis-ci.org/Vitek-Lab/MSstats-dev.svg?branch=master)](https://travis-ci.org/Vitek-Lab/MSstats-dev) -[![Codecov test coverage](https://codecov.io/gh/Vitek-Lab/MSstats-dev/branch/master/graph/badge.svg)](https://codecov.io/gh/Vitek-Lab/MSstats-dev?branch=master) +[![Bioconductor Release Build](https://bioconductor.org/shields/build/release/bioc/MSstats.svg)](https://bioconductor.org/checkResults/release/bioc-LATEST/MSstats/) +[![Bioconductor Devel Build](https://bioconductor.org/shields/build/devel/bioc/MSstats.svg)](https://bioconductor.org/checkResults/devel/bioc-LATEST/MSstats/) +[![Codecov test coverage](https://codecov.io/gh/Vitek-Lab/MSstats/branch/master/graph/badge.svg)](https://codecov.io/gh/Vitek-Lab/MSstats) +[![Bioconductor Downloads Rank](https://bioconductor.org/shields/downloads/release/MSstats.svg)](https://bioconductor.org/packages/stats/bioc/MSstats/) +[![Years in Bioconductor](https://bioconductor.org/shields/years-in-bioc/MSstats.svg)](https://bioconductor.org/packages/release/bioc/html/MSstats.html#since) +[![License: Artistic-2.0](https://img.shields.io/badge/license-Artistic--2.0-blue.svg)](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 handling, run-level +summarization, and model-based statistical testing to detect differentially +abundant proteins or peptides across conditions. + +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. + +## The MSstats Ecosystem + +MSstats has grown into a family of packages that share the same statistical +framework and data model, each targeting a different experiment type or stage +of the analysis pipeline: + +| 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, MSstatsTMT, and MSstatsPTM for code-free analysis. | +| **[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. | + +## 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, Ting Huang, +and Olga Vitek. 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. + +## 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. + +## 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. Up-to-date download +statistics are available on the +[Bioconductor stats page](https://bioconductor.org/packages/stats/bioc/MSstats/), +and citation counts for MSstats and related packages can be tracked via +[Google Scholar](https://scholar.google.com/citations?hl=en&user=&q=MSstats). + +## References + +If you use MSstats or a package from the MSstats ecosystem, please cite the +relevant publication(s): + +1. 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) +2. 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) +3. 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) +4. 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) +5. 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) +6. 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) +7. 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) +8. 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) +9. 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. + +## 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 From 2513deb7e78641cf6f8b838d7c3e3e8eed041dda Mon Sep 17 00:00:00 2001 From: devonjkohler Date: Wed, 15 Jul 2026 15:20:52 -0400 Subject: [PATCH 02/13] added bioc downloads --- .github/workflows/update-download-stats.yaml | 37 ++++++ README.md | 32 ++++- scripts/update_download_stats.py | 120 +++++++++++++++++++ 3 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/update-download-stats.yaml create mode 100644 scripts/update_download_stats.py 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/README.md b/README.md index ca0f1008..ba7dc68d 100644 --- a/README.md +++ b/README.md @@ -127,12 +127,36 @@ complete, tool-specific examples. 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. Up-to-date download -statistics are available on the -[Bioconductor stats page](https://bioconductor.org/packages/stats/bioc/MSstats/), -and citation counts for MSstats and related packages can be tracked via +underlying the MSstats family of packages and MSstatsShiny. Citation counts for +MSstats and related packages can be tracked via [Google Scholar](https://scholar.google.com/citations?hl=en&user=&q=MSstats). +### 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 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() From bfe565d299ae3dfc99d9f692562c9bab43446556 Mon Sep 17 00:00:00 2001 From: devonjkohler Date: Wed, 15 Jul 2026 15:33:18 -0400 Subject: [PATCH 03/13] added cite count --- .github/workflows/update-citation-stats.yaml | 37 +++++++ README.md | 34 ++++++- scripts/update_citation_counts.py | 100 +++++++++++++++++++ 3 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/update-citation-stats.yaml create mode 100644 scripts/update_citation_counts.py 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/README.md b/README.md index ba7dc68d..3c23d913 100644 --- a/README.md +++ b/README.md @@ -127,9 +127,37 @@ complete, tool-specific examples. 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. Citation counts for -MSstats and related packages can be tracked via -[Google Scholar](https://scholar.google.com/citations?hl=en&user=&q=MSstats). +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-15 (UTC)._ + +| Paper | Citations | +| --- | --- | +| [MSstats (2014)](https://doi.org/10.1093/bioinformatics/btu305) | 1,158 | +| [MSstats 4.0 (2023)](https://doi.org/10.1021/acs.jproteome.2c00834) | 134 | +| [MSstats + FragPipe DIA workflow (2024)](https://doi.org/10.1038/s41596-024-01000-3) | 13 | +| [MSstatsTMT (2020)](https://doi.org/10.1074/mcp.RA120.002105) | 203 | +| [MSstatsTMT repeated measures (2023)](https://doi.org/10.1021/acs.jproteome.3c00155) | 14 | +| [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** | **1,726** | + + + +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 diff --git a/scripts/update_citation_counts.py b/scripts/update_citation_counts.py new file mode 100644 index 00000000..a3859df8 --- /dev/null +++ b/scripts/update_citation_counts.py @@ -0,0 +1,100 @@ +#!/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 = [ + ("MSstats (2014)", "10.1093/bioinformatics/btu305"), + ("MSstats 4.0 (2023)", "10.1021/acs.jproteome.2c00834"), + ("MSstats + FragPipe DIA workflow (2024)", "10.1038/s41596-024-01000-3"), + ("MSstatsTMT (2020)", "10.1074/mcp.RA120.002105"), + ("MSstatsTMT repeated measures (2023)", "10.1021/acs.jproteome.3c00155"), + ("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() From ad942dfaf83c01a673442c45315ae23448df51ff Mon Sep 17 00:00:00 2001 From: devonjkohler Date: Wed, 15 Jul 2026 15:49:16 -0400 Subject: [PATCH 04/13] added converters --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 3c23d913..c5278e6a 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,37 @@ 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) +and re-exported directly from MSstats, so they're available as soon as you +`library(MSstats)`: + +| 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()` | + +MSstatsConvert also provides a `MetamorpheusToMSstatsFormat()` converter +(`MSstatsConvert::MetamorpheusToMSstatsFormat()`) that isn't yet re-exported +from MSstats directly, and 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 From f433b6b78839d0443c2395e42c29481cb14f8d42 Mon Sep 17 00:00:00 2001 From: devonjkohler Date: Wed, 15 Jul 2026 15:56:38 -0400 Subject: [PATCH 05/13] README edits --- README.md | 44 ++++++++++++++++++++++++++++++++ man/figures/example_heatmap.png | Bin 0 -> 72516 bytes 2 files changed, 44 insertions(+) create mode 100644 man/figures/example_heatmap.png diff --git a/README.md b/README.md index c5278e6a..34c53966 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,42 @@ 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 +- **9 peer-reviewed publications / preprints**, [~1,700+ citations](#citations) + combined (see [Citations](#citations)) +- **Thousands of downloads per month** across the ecosystem (see + [Download statistics](#download-statistics)), tracked automatically from + Bioconductor's own logs +- Used as the statistical engine behind [Skyline](https://skyline.ms/) and the + [MSstatsShiny](https://www.msstatsshiny.com) web app + ## The MSstats Ecosystem MSstats has grown into a family of packages that share the same statistical framework and data model, each targeting 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)"] + B --> C["MSstats
DDA / DIA / SRM
label-free & label-based"] + B --> D["MSstatsTMT
isobaric labeling"] + C --> E["MSstatsPTM
post-translational mods"] + D --> E + C --> F["MSstatsLiP
limited proteolysis"] + C --> G["MSstatsBig
larger-than-memory data"] + C --> H["MSstatsBioNet
network enrichment"] + C --> I["MSstatsResponse
dose-response"] + C --> J["MSstatsShiny
point-and-click GUI"] + D --> J + E --> J +``` + | Package | Description | | --- | --- | | **[MSstats](https://bioconductor.org/packages/MSstats)** | Core package for DDA, SRM, and DIA label-free/label-based experiments. | @@ -136,6 +166,20 @@ Prospector, SpectroMine) for isobaric-labeling experiments, used with See the [End to End Workflow vignette](vignettes/MSstatsWorkflow.Rmd) for the required input files and options for each converter. +## Example Output + +MSstats produces publication-ready visualizations directly from +`groupComparison()` results via `groupComparisonPlots()` — volcano plots, +per-protein comparison plots, and heatmaps summarizing many comparisons at +once. The heatmap below is generated directly from the package's own bundled +example data (`DDARawData`, a spike-in benchmark with proteins from six +species), comparing all pairs of conditions; darker red/blue indicates a +larger, more significant increase/decrease in abundance: + +

+ Example MSstats heatmap of differential abundance across pairwise comparisons +

+ ## Documentation - [MSstats: Protein/Peptide significance analysis](vignettes/MSstats.Rmd) — overview of all functionality diff --git a/man/figures/example_heatmap.png b/man/figures/example_heatmap.png new file mode 100644 index 0000000000000000000000000000000000000000..8eef9cd1ad851224543d27800b236181ebdade9a GIT binary patch literal 72516 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 literal 0 HcmV?d00001 From 5c6aec0da4ff0b987f24a146afc0c1512e085784 Mon Sep 17 00:00:00 2001 From: devonjkohler Date: Wed, 15 Jul 2026 15:58:43 -0400 Subject: [PATCH 06/13] updated diagram --- README.md | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 34c53966..268f1641 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,6 @@ on its regular 6-month release cycle. - **Thousands of downloads per month** across the ecosystem (see [Download statistics](#download-statistics)), tracked automatically from Bioconductor's own logs -- Used as the statistical engine behind [Skyline](https://skyline.ms/) and the - [MSstatsShiny](https://www.msstatsshiny.com) web app ## The MSstats Ecosystem @@ -64,6 +62,10 @@ flowchart LR C --> J["MSstatsShiny
point-and-click GUI"] D --> J E --> J + F --> J + G --> J + H --> J + I --> J ``` | Package | Description | @@ -166,20 +168,6 @@ Prospector, SpectroMine) for isobaric-labeling experiments, used with See the [End to End Workflow vignette](vignettes/MSstatsWorkflow.Rmd) for the required input files and options for each converter. -## Example Output - -MSstats produces publication-ready visualizations directly from -`groupComparison()` results via `groupComparisonPlots()` — volcano plots, -per-protein comparison plots, and heatmaps summarizing many comparisons at -once. The heatmap below is generated directly from the package's own bundled -example data (`DDARawData`, a spike-in benchmark with proteins from six -species), comparing all pairs of conditions; darker red/blue indicates a -larger, more significant increase/decrease in abundance: - -

- Example MSstats heatmap of differential abundance across pairwise comparisons -

- ## Documentation - [MSstats: Protein/Peptide significance analysis](vignettes/MSstats.Rmd) — overview of all functionality From 14f8af281c4f6e166a163cf77d1910d5f836af09 Mon Sep 17 00:00:00 2001 From: devonjkohler Date: Thu, 16 Jul 2026 09:18:21 -0400 Subject: [PATCH 07/13] additional README info --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 268f1641..b525a1fb 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,11 @@ identified and quantified peaks from upstream tools (Skyline, MaxQuant, Proteome Discoverer, Spectronaut, DIA-NN, FragPipe, OpenSWATH, and others), MSstats performs normalization, missing value handling, run-level summarization, and model-based statistical testing to detect differentially -abundant proteins or peptides across conditions. +abundant proteins or peptides across conditions. Because the underlying +statistical framework operates on generic quantitative features rather than +anything protein-specific, it is general enough to apply to other +targeted/SRM- or DIA-style quantitative signals as well, 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 @@ -80,6 +84,13 @@ flowchart LR | **[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/) @@ -97,6 +108,11 @@ and Olga Vitek. 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 @@ -304,6 +320,13 @@ 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/) +program (Cycle 1, 2019), through the award *"MSstats and Cardinal: Next +Generation Statistical Mass Spectrometry in R"* (PI: Olga Vitek). + ## License MSstats is released under the [Artistic-2.0](https://opensource.org/licenses/Artistic-2.0) license. From 06ec0e229883adee69ea766a6c8e35b96059cf1f Mon Sep 17 00:00:00 2001 From: devonjkohler Date: Thu, 16 Jul 2026 09:51:25 -0400 Subject: [PATCH 08/13] more pubs --- README.md | 130 +++++++++++++++++------------- scripts/update_citation_counts.py | 4 + 2 files changed, 76 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index b525a1fb..71540d96 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,10 @@ 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 handling, run-level +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 rather than -anything protein-specific, it is general enough to apply to other -targeted/SRM- or DIA-style quantitative signals as well, such as targeted -metabolomics data. +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 @@ -40,36 +37,38 @@ on its regular 6-month release cycle. - **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 -- **9 peer-reviewed publications / preprints**, [~1,700+ citations](#citations) +- **13 peer-reviewed publications / preprints**, [~2,000+ citations](#citations) combined (see [Citations](#citations)) -- **Thousands of downloads per month** across the ecosystem (see +- **~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 share the same statistical -framework and data model, each targeting a different experiment type or stage +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"] - B --> D["MSstatsTMT
isobaric labeling"] + G --> C + C --> D["MSstatsTMT
isobaric labeling"] C --> E["MSstatsPTM
post-translational mods"] - D --> E C --> F["MSstatsLiP
limited proteolysis"] - C --> G["MSstatsBig
larger-than-memory data"] - C --> H["MSstatsBioNet
network enrichment"] 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 - G --> J - H --> J I --> J + H --> J ``` | Package | Description | @@ -79,7 +78,7 @@ flowchart LR | **[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, MSstatsTMT, and MSstatsPTM for code-free analysis. | +| **[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. | @@ -103,8 +102,7 @@ Current developers: - Mateusz Staniak - Sarah Szvetecz -Former developers include Meena Choi, Deril Raju, Tsung-Heng Tsai, Ting Huang, -and Olga Vitek. See the full author list in [DESCRIPTION](DESCRIPTION) and the +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. @@ -158,9 +156,7 @@ complete, tool-specific examples. 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) -and re-exported directly from MSstats, so they're available as soon as you -`library(MSstats)`: +implemented in [MSstatsConvert](https://bioconductor.org/packages/MSstatsConvert): | Search tool / format | Converter function | | --- | --- | @@ -174,10 +170,9 @@ and re-exported directly from MSstats, so they're available as soon as you | FragPipe | `FragPipetoMSstatsFormat()` | | OpenMS | `OpenMStoMSstatsFormat()` | | OpenSWATH | `OpenSWATHtoMSstatsFormat()` | +| Metamorpheus | `MetamorpheusToMSstatsFormat()` | -MSstatsConvert also provides a `MetamorpheusToMSstatsFormat()` converter -(`MSstatsConvert::MetamorpheusToMSstatsFormat()`) that isn't yet re-exported -from MSstats directly, and a set of `*toMSstatsTMTFormat()` converters +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. @@ -212,20 +207,24 @@ underlying the MSstats family of packages and MSstatsShiny. -_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-15 (UTC)._ +_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 | | --- | --- | -| [MSstats (2014)](https://doi.org/10.1093/bioinformatics/btu305) | 1,158 | -| [MSstats 4.0 (2023)](https://doi.org/10.1021/acs.jproteome.2c00834) | 134 | +| [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 | -| [MSstatsTMT (2020)](https://doi.org/10.1074/mcp.RA120.002105) | 203 | +| [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** | **1,726** | +| **Total** | **2,035** | @@ -269,52 +268,69 @@ for every package in the ecosystem. If you use MSstats or a package from the MSstats ecosystem, please cite the relevant publication(s): -1. Choi M, Chang CY, Clough T, Broudy D, Killeen T, MacLean B, Vitek O. +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) -2. Kohler D, Staniak M, Tsai TH, Huang T, Shulman N, Bernhardt OM, MacLean BX, +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) -3. Kohler D, Vitek O, et al. **An MSstats workflow for detecting differentially +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) -4. Huang T, Choi M, Tzouros M, Golling S, Pandya NJ, Banfai B, Dunkley T, +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) -5. Huang T, Staniak M, da Veiga Leprevost F, Figueroa-Navedo AM, Ivanov AR, +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) -6. 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) -7. 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) -8. 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) -9. 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) +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) @@ -323,9 +339,7 @@ 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/) -program (Cycle 1, 2019), through the award *"MSstats and Cardinal: Next -Generation Statistical Mass Spectrometry in R"* (PI: Olga Vitek). +[Essential Open Source Software for Science](https://chanzuckerberg.com/eoss/proposals/). ## License diff --git a/scripts/update_citation_counts.py b/scripts/update_citation_counts.py index a3859df8..7b7f9e79 100644 --- a/scripts/update_citation_counts.py +++ b/scripts/update_citation_counts.py @@ -23,11 +23,15 @@ # (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"), From 959f8b7b80c275bb81a0bf853fef1d17e0d570ca Mon Sep 17 00:00:00 2001 From: devonjkohler Date: Thu, 16 Jul 2026 09:53:42 -0400 Subject: [PATCH 09/13] minor typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71540d96..f6581a96 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,7 @@ _Citation counts from [OpenAlex](https://openalex.org), updated monthly. Google | [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 & 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 | From a9ec0b679b83fa894504497c6c2c0b16e4c84e1f Mon Sep 17 00:00:00 2001 From: devonjkohler Date: Thu, 16 Jul 2026 10:02:49 -0400 Subject: [PATCH 10/13] fixing text coverage --- .github/workflows/test-coverage.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index cf86b090..812f733b 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -17,9 +17,10 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: r-lib/actions/setup-r@v2 + - name: Setup R and Bioconductor + uses: grimbough/bioc-actions/setup-bioc@v1 with: - use-public-rspm: true + bioc-version: devel - uses: r-lib/actions/setup-r-dependencies@v2 with: From e1ddc94fd0c2b11a3358c6fe4c0c12ea19cb78ac Mon Sep 17 00:00:00 2001 From: devonjkohler Date: Thu, 16 Jul 2026 10:13:29 -0400 Subject: [PATCH 11/13] another test coverage fix --- .github/workflows/test-coverage.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 812f733b..dfcc4eb6 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -22,6 +22,25 @@ jobs: with: bioc-version: devel + - name: Persist CRAN + Bioconductor repos for later steps + run: | + # setup-bioc only sets options(repos=...) inside its own transient + # R process (via BiocManager::install()); it never writes that to + # ~/.Rprofile, so later steps' fresh R sessions (like pak's + # dependency resolution below) don't see the Bioconductor repos and + # fail to resolve CRAN-only transitive dependencies (e.g. log4r, + # via MSstatsConvert). Write the full repos list explicitly so it + # persists for the rest of the job. + repos <- BiocManager::repositories() + repos["CRAN"] <- "https://cloud.r-project.org" + line <- paste0( + "options(repos = c(", + paste(sprintf('"%s" = "%s"', names(repos), repos), collapse = ", "), + "))" + ) + cat(line, "\n", file = "~/.Rprofile", append = TRUE) + shell: Rscript {0} + - uses: r-lib/actions/setup-r-dependencies@v2 with: extra-packages: any::covr, any::xml2 From 93b50c371f4f614d708ee8b4f291658d990f9fed Mon Sep 17 00:00:00 2001 From: devonjkohler Date: Thu, 16 Jul 2026 10:18:02 -0400 Subject: [PATCH 12/13] code cov --- .github/workflows/test-coverage.yaml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index dfcc4eb6..9779fb3a 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -24,21 +24,29 @@ jobs: - name: Persist CRAN + Bioconductor repos for later steps run: | - # setup-bioc only sets options(repos=...) inside its own transient - # R process (via BiocManager::install()); it never writes that to - # ~/.Rprofile, so later steps' fresh R sessions (like pak's - # dependency resolution below) don't see the Bioconductor repos and - # fail to resolve CRAN-only transitive dependencies (e.g. log4r, - # via MSstatsConvert). Write the full repos list explicitly so it - # persists for the rest of the job. + # setup-bioc's use-public-rspm behavior sets RENV_CONFIG_REPOS_OVERRIDE + # to a CRAN-only Posit Package Manager URL. pak/pkgcache treat that + # override as authoritative, so it wins over anything set via + # options(repos=...) or ~/.Rprofile -- which is why pak could resolve + # MSstatsConvert (via its own Bioconductor-aware logic) but not + # log4r, a plain CRAN dependency missing from the override. Replace + # the override with the full CRAN + Bioconductor repo set so later + # steps' pak subprocess sees everything. repos <- BiocManager::repositories() repos["CRAN"] <- "https://cloud.r-project.org" + line <- paste0( "options(repos = c(", paste(sprintf('"%s" = "%s"', names(repos), repos), collapse = ", "), "))" ) cat(line, "\n", file = "~/.Rprofile", append = TRUE) + + cat( + sprintf("RENV_CONFIG_REPOS_OVERRIDE=%s\n", paste(unname(repos), collapse = ",")), + file = Sys.getenv("GITHUB_ENV"), + append = TRUE + ) shell: Rscript {0} - uses: r-lib/actions/setup-r-dependencies@v2 From 6fec41c19df227bd5712bba90301cf473245c1a4 Mon Sep 17 00:00:00 2001 From: devonjkohler Date: Thu, 16 Jul 2026 10:34:15 -0400 Subject: [PATCH 13/13] another test fix try --- .github/workflows/test-coverage.yaml | 53 +++++++++++++++------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 9779fb3a..ebec380e 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -22,38 +22,41 @@ jobs: with: bioc-version: devel - - name: Persist CRAN + Bioconductor repos for later steps + - name: Install dependencies run: | - # setup-bioc's use-public-rspm behavior sets RENV_CONFIG_REPOS_OVERRIDE - # to a CRAN-only Posit Package Manager URL. pak/pkgcache treat that - # override as authoritative, so it wins over anything set via - # options(repos=...) or ~/.Rprofile -- which is why pak could resolve - # MSstatsConvert (via its own Bioconductor-aware logic) but not - # log4r, a plain CRAN dependency missing from the override. Replace - # the override with the full CRAN + Bioconductor repo set so later - # steps' pak subprocess sees everything. + # 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) - line <- paste0( - "options(repos = c(", - paste(sprintf('"%s" = "%s"', names(repos), repos), collapse = ", "), - "))" - ) - cat(line, "\n", file = "~/.Rprofile", append = TRUE) - - cat( - sprintf("RENV_CONFIG_REPOS_OVERRIDE=%s\n", paste(unname(repos), collapse = ",")), - file = Sys.getenv("GITHUB_ENV"), - append = TRUE + 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} - - uses: r-lib/actions/setup-r-dependencies@v2 - with: - extra-packages: any::covr, any::xml2 - needs: coverage - - name: Test coverage run: | cov <- covr::package_coverage(