diff --git a/DESCRIPTION b/DESCRIPTION index 8a8f1a6..7aa9607 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,13 +1,34 @@ Package: curatedBladderData Type: Package Title: Bladder Cancer Gene Expression Analysis -Version: 1.19.2 -Date: 2019-03-28 +Version: 1.49.1 +Date: 2026-10-06 Author: Markus Riester Maintainer: Markus Riester -Description: The curatedBladderData package provides relevant functions and data for gene expression analysis in patients with bladder cancer. -Depends: R (>= 2.10.0), affy -Suggests: BiocStyle, survival, xtable, sva, genefilter, logging +Description: Manually curated bladder cancer ExpressionSet objects from 10 + studies (12 objects, since two studies contributed data on two platforms + each), with uniformly processed expression data and curated clinical + metadata. Datasets are hosted on Zenodo and downloaded on first use, + then cached locally with BiocFileCache. Includes a configurable script + for rule-based creation of filtered dataset lists for meta-analysis. +Depends: + R (>= 4.0.0), + Biobase +Imports: + BiocFileCache, + tools, + utils +Suggests: + BiocStyle, + genefilter, + logging, + survival, + sva, + testthat (>= 3.0.0), + xtable License: Artistic-2.0 URL: https://github.com/lima1/curatedBladderData -biocViews: ExperimentData, CancerData, OvarianCancerData, MicroarrayData, ExpressionData +BugReports: https://github.com/lima1/curatedBladderData/issues +biocViews: ExperimentData, CancerData, MicroarrayData, ExpressionData, GEO +BuildResaveData: no +Config/testthat/edition: 3 diff --git a/NAMESPACE b/NAMESPACE index 40ea8e2..0e3864a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1 +1,5 @@ -exportPattern("^[[:alpha:]]+") +export(curatedBladderData) +import(Biobase) +importFrom(BiocFileCache, BiocFileCache, bfcrpath, bfcquery, bfcremove) +importFrom(utils, read.csv) +importFrom(tools, md5sum, R_user_dir) diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..5752b6e --- /dev/null +++ b/NEWS.md @@ -0,0 +1,39 @@ +# curatedBladderData 1.50.0 + +## SIGNIFICANT USER-VISIBLE CHANGES + +* The 12 datasets are no longer stored inside the package. They are hosted + on Zenodo and downloaded individually on first use, then cached locally + with `BiocFileCache`. This shrinks the installed package from about + 75 MB to a few MB. +* New exported function `curatedBladderData()`: call it with no arguments + to list the available datasets, with one dataset name to get an + `ExpressionSet`, or with several names to get a named list. `test = TRUE` + loads small offline subsets bundled with the package, used by examples, + tests and the vignette. +* Legacy `data(GSE89_eset)` access still works. It downloads through the + same cache, deferred until the object is first used, but is deprecated + and will be removed in a future release. +* `inst/extdata/createEsetList.R` now loads datasets through the getter. + A new `test.mode` option in the patientselection config selects the + offline subsets. +* The cache is package-specific, at + `tools::R_user_dir("curatedBladderData", "cache")`. Downloads are + verified against md5 checksums recorded in + `inst/extdata/zenodo-manifest.csv`. + +## BUG FIXES + +* `biocViews` listed `OvarianCancerData`; removed, and `GEO` added. +* The package-level help page described ovarian cancer, listed the + curatedOvarianData author list, and named a different maintainer than + DESCRIPTION. Corrected to match DESCRIPTION. +* Replaced the unused `affy` dependency with `Biobase`, which is where + `ExpressionSet`, `exprs()` and `pData()` actually come from. +* `createEsetList.R` no longer errors when the filters exclude every + dataset (`which()` on an empty list). + +## INTERNAL + +* Added testthat tests and `inst/scripts/` documenting how the Zenodo + files, the offline fixtures and the data stubs are generated. diff --git a/R/getData.R b/R/getData.R new file mode 100644 index 0000000..4524c41 --- /dev/null +++ b/R/getData.R @@ -0,0 +1,118 @@ +## Data retrieval for curatedBladderData: datasets are hosted on Zenodo and +## downloaded on first use into a package-specific BiocFileCache. +## +## NOTE: this file is intentionally duplicated (modulo package name) in +## curatedCRCData and curatedOvarianData. Keep the copies in sync. + +.pkgname <- "curatedBladderData" + +## session flags (e.g., show the data() deprecation note only once) +.pkgenv <- new.env(parent = emptyenv()) + +## Manifest mapping dataset names to Zenodo file URLs, md5 checksums, and +## sizes. Regenerated by inst/scripts/make-data.R; the manifest is the ONLY +## place download URLs live. +.manifest <- function() { + fname <- system.file("extdata", "zenodo-manifest.csv", + package = .pkgname, mustWork = TRUE) + utils::read.csv(fname, stringsAsFactors = FALSE) +} + +.cache <- function() { + BiocFileCache::BiocFileCache(tools::R_user_dir(.pkgname, "cache")) +} + +## Return the local path to one dataset's .rda, downloading into the cache +## on first use. The md5 checksum is verified after a fresh download only; +## on mismatch the corrupt entry is removed and the download retried once. +.getDataFile <- function(name, retry = TRUE) { + meta <- .manifest() + i <- match(name, meta$dataset) + stopifnot(!is.na(i)) + bfc <- .cache() + known <- BiocFileCache::bfcquery(bfc, meta$url[i], field = "fpath", + exact = TRUE) + fresh <- nrow(known) == 0L + if (fresh) + message("Downloading ", name, " (", + round(meta$size_bytes[i] / 2^20, 1), " MB) from Zenodo...") + path <- unname(BiocFileCache::bfcrpath(bfc, meta$url[i])) + if (!fresh && !isTRUE(file.size(path) == meta$size_bytes[i])) { + # cached file was truncated or modified after download: evict and + # re-download (cheap size check; full md5 runs after each download) + res <- BiocFileCache::bfcquery(bfc, meta$url[i], field = "fpath", + exact = TRUE) + if (nrow(res)) + BiocFileCache::bfcremove(bfc, res$rid) + if (retry) { + message("Cached copy of ", name, + " has the wrong size; re-downloading...") + return(.getDataFile(name, retry = FALSE)) + } + stop("Cached file for '", name, "' has the wrong size and could ", + "not be refreshed.") + } + if (fresh) { + md5 <- unname(tools::md5sum(path)) + if (!identical(md5, meta$md5[i])) { + res <- BiocFileCache::bfcquery(bfc, meta$url[i], field = "fpath", + exact = TRUE) + if (nrow(res)) + BiocFileCache::bfcremove(bfc, res$rid) + if (retry) { + message("Checksum mismatch; retrying the download once...") + return(.getDataFile(name, retry = FALSE)) + } + stop("md5 checksum mismatch for '", name, "' downloaded from ", + meta$url[i], + "; the corrupt copy was removed from the cache.") + } + } + path +} + +.loadOne <- function(name, test = FALSE) { + path <- if (test) { + system.file("extdata", "testdata", paste0(name, ".rda"), + package = .pkgname, mustWork = TRUE) + } else { + .getDataFile(name) + } + env <- new.env(parent = emptyenv()) + objname <- load(path, envir = env) + get(objname[1L], envir = env) +} + +## Called by the data/*.R stubs so that legacy data(X) usage keeps working. +## During this package's own R CMD check, resolve to the bundled test +## fixture so checking needs no network access; checks of OTHER packages +## (reverse dependencies) get the real data. +.stubLoad <- function(name) { + if (identical(Sys.getenv("_R_CHECK_PACKAGE_NAME_"), .pkgname)) + return(.loadOne(name, test = TRUE)) + if (!isTRUE(.pkgenv$stub_noted)) { + .pkgenv$stub_noted <- TRUE + message("Note: loading ", .pkgname, " datasets via data() is ", + "deprecated and will be removed in a future release; use ", + .pkgname, "(\"", name, "\") instead. Datasets are now ", + "downloaded on first use and cached locally with ", + "BiocFileCache. (This note is shown once per session.)") + } + .loadOne(name) +} + +#' @export +curatedBladderData <- function(datasets, test = FALSE) { + meta <- .manifest() + if (missing(datasets)) + return(meta$dataset) + stopifnot(is.character(datasets), !anyNA(datasets)) + bad <- setdiff(datasets, meta$dataset) + if (length(bad)) + stop("Unknown dataset(s): ", paste(bad, collapse = ", "), + ". Call curatedBladderData() with no arguments to list ", + "available datasets.") + res <- lapply(datasets, .loadOne, test = test) + names(res) <- datasets + if (length(res) == 1L) res[[1L]] else res +} diff --git a/data/GSE13507_eset.R b/data/GSE13507_eset.R new file mode 100644 index 0000000..408cca9 --- /dev/null +++ b/data/GSE13507_eset.R @@ -0,0 +1,3 @@ +delayedAssign("GSE13507_eset", + curatedBladderData:::.stubLoad("GSE13507_eset"), + assign.env = environment()) diff --git a/data/GSE13507_eset.rda b/data/GSE13507_eset.rda deleted file mode 100644 index 5594ced..0000000 Binary files a/data/GSE13507_eset.rda and /dev/null differ diff --git a/data/GSE1827_eset.R b/data/GSE1827_eset.R new file mode 100644 index 0000000..c5f3f01 --- /dev/null +++ b/data/GSE1827_eset.R @@ -0,0 +1,3 @@ +delayedAssign("GSE1827_eset", + curatedBladderData:::.stubLoad("GSE1827_eset"), + assign.env = environment()) diff --git a/data/GSE1827_eset.rda b/data/GSE1827_eset.rda deleted file mode 100644 index 85d4a74..0000000 Binary files a/data/GSE1827_eset.rda and /dev/null differ diff --git a/data/GSE19915.GPL3883_eset.R b/data/GSE19915.GPL3883_eset.R new file mode 100644 index 0000000..5baf288 --- /dev/null +++ b/data/GSE19915.GPL3883_eset.R @@ -0,0 +1,3 @@ +delayedAssign("GSE19915.GPL3883_eset", + curatedBladderData:::.stubLoad("GSE19915.GPL3883_eset"), + assign.env = environment()) diff --git a/data/GSE19915.GPL3883_eset.rda b/data/GSE19915.GPL3883_eset.rda deleted file mode 100644 index bd9b996..0000000 Binary files a/data/GSE19915.GPL3883_eset.rda and /dev/null differ diff --git a/data/GSE19915.GPL5186_eset.R b/data/GSE19915.GPL5186_eset.R new file mode 100644 index 0000000..2d61d16 --- /dev/null +++ b/data/GSE19915.GPL5186_eset.R @@ -0,0 +1,3 @@ +delayedAssign("GSE19915.GPL5186_eset", + curatedBladderData:::.stubLoad("GSE19915.GPL5186_eset"), + assign.env = environment()) diff --git a/data/GSE19915.GPL5186_eset.rda b/data/GSE19915.GPL5186_eset.rda deleted file mode 100644 index 2c0a6ec..0000000 Binary files a/data/GSE19915.GPL5186_eset.rda and /dev/null differ diff --git a/data/GSE31189_eset.R b/data/GSE31189_eset.R new file mode 100644 index 0000000..0411de3 --- /dev/null +++ b/data/GSE31189_eset.R @@ -0,0 +1,3 @@ +delayedAssign("GSE31189_eset", + curatedBladderData:::.stubLoad("GSE31189_eset"), + assign.env = environment()) diff --git a/data/GSE31189_eset.rda b/data/GSE31189_eset.rda deleted file mode 100644 index 11b24c5..0000000 Binary files a/data/GSE31189_eset.rda and /dev/null differ diff --git a/data/GSE31684_eset.R b/data/GSE31684_eset.R new file mode 100644 index 0000000..4290ce0 --- /dev/null +++ b/data/GSE31684_eset.R @@ -0,0 +1,3 @@ +delayedAssign("GSE31684_eset", + curatedBladderData:::.stubLoad("GSE31684_eset"), + assign.env = environment()) diff --git a/data/GSE31684_eset.rda b/data/GSE31684_eset.rda deleted file mode 100644 index 3c817f9..0000000 Binary files a/data/GSE31684_eset.rda and /dev/null differ diff --git a/data/GSE32894_eset.R b/data/GSE32894_eset.R new file mode 100644 index 0000000..f7a9042 --- /dev/null +++ b/data/GSE32894_eset.R @@ -0,0 +1,3 @@ +delayedAssign("GSE32894_eset", + curatedBladderData:::.stubLoad("GSE32894_eset"), + assign.env = environment()) diff --git a/data/GSE32894_eset.rda b/data/GSE32894_eset.rda deleted file mode 100644 index 0453aa3..0000000 Binary files a/data/GSE32894_eset.rda and /dev/null differ diff --git a/data/GSE37317_eset.R b/data/GSE37317_eset.R new file mode 100644 index 0000000..c216283 --- /dev/null +++ b/data/GSE37317_eset.R @@ -0,0 +1,3 @@ +delayedAssign("GSE37317_eset", + curatedBladderData:::.stubLoad("GSE37317_eset"), + assign.env = environment()) diff --git a/data/GSE37317_eset.rda b/data/GSE37317_eset.rda deleted file mode 100644 index 9a9f596..0000000 Binary files a/data/GSE37317_eset.rda and /dev/null differ diff --git a/data/GSE5287_eset.R b/data/GSE5287_eset.R new file mode 100644 index 0000000..f393743 --- /dev/null +++ b/data/GSE5287_eset.R @@ -0,0 +1,3 @@ +delayedAssign("GSE5287_eset", + curatedBladderData:::.stubLoad("GSE5287_eset"), + assign.env = environment()) diff --git a/data/GSE5287_eset.rda b/data/GSE5287_eset.rda deleted file mode 100644 index d426b69..0000000 Binary files a/data/GSE5287_eset.rda and /dev/null differ diff --git a/data/GSE89_eset.R b/data/GSE89_eset.R new file mode 100644 index 0000000..35466fa --- /dev/null +++ b/data/GSE89_eset.R @@ -0,0 +1,3 @@ +delayedAssign("GSE89_eset", + curatedBladderData:::.stubLoad("GSE89_eset"), + assign.env = environment()) diff --git a/data/GSE89_eset.rda b/data/GSE89_eset.rda deleted file mode 100644 index 981a00e..0000000 Binary files a/data/GSE89_eset.rda and /dev/null differ diff --git a/data/PMID17099711.GPL8300_eset.R b/data/PMID17099711.GPL8300_eset.R new file mode 100644 index 0000000..74b9bdd --- /dev/null +++ b/data/PMID17099711.GPL8300_eset.R @@ -0,0 +1,3 @@ +delayedAssign("PMID17099711.GPL8300_eset", + curatedBladderData:::.stubLoad("PMID17099711.GPL8300_eset"), + assign.env = environment()) diff --git a/data/PMID17099711.GPL8300_eset.rda b/data/PMID17099711.GPL8300_eset.rda deleted file mode 100644 index aff7e6a..0000000 Binary files a/data/PMID17099711.GPL8300_eset.rda and /dev/null differ diff --git a/data/PMID17099711.GPL91_eset.R b/data/PMID17099711.GPL91_eset.R new file mode 100644 index 0000000..ba4c076 --- /dev/null +++ b/data/PMID17099711.GPL91_eset.R @@ -0,0 +1,3 @@ +delayedAssign("PMID17099711.GPL91_eset", + curatedBladderData:::.stubLoad("PMID17099711.GPL91_eset"), + assign.env = environment()) diff --git a/data/PMID17099711.GPL91_eset.rda b/data/PMID17099711.GPL91_eset.rda deleted file mode 100644 index 6ca90b7..0000000 Binary files a/data/PMID17099711.GPL91_eset.rda and /dev/null differ diff --git a/external_data_store.txt b/external_data_store.txt deleted file mode 100644 index 9e67bd4..0000000 --- a/external_data_store.txt +++ /dev/null @@ -1,2 +0,0 @@ -data - diff --git a/inst/extdata/createEsetList.R b/inst/extdata/createEsetList.R index 561646e..daf9ee7 100644 --- a/inst/extdata/createEsetList.R +++ b/inst/extdata/createEsetList.R @@ -62,7 +62,10 @@ intersectMany <- function(lst){ } ##load the esets -data(list=data(package=package.name)[[3]][,3]) +if (!exists("test.mode")) test.mode <- FALSE +for (.nm in do.call(package.name, list())) + assign(.nm, do.call(package.name, list(datasets = .nm, test = test.mode))) +rm(.nm) strEsets <- ls(pattern="^.*_eset$") @@ -142,8 +145,8 @@ if(keep.common.only){ }) } -ids.with.missing.data <- which(sapply(esets, function(X) -sum(!complete.cases(exprs(X))) > 0)) +ids.with.missing.data <- which(vapply(esets, function(X) +sum(!complete.cases(exprs(X))) > 0, logical(1))) loginfo(paste("Ids with missing data:", paste(names(ids.with.missing.data), collapse=", "))) diff --git a/inst/extdata/patientselection_all.config b/inst/extdata/patientselection_all.config index 9f36f90..336f3e3 100755 --- a/inst/extdata/patientselection_all.config +++ b/inst/extdata/patientselection_all.config @@ -2,6 +2,11 @@ ##settings for createEsetList.R ##----------------------------- + +#use the small offline test subsets bundled with the package instead of +#downloading the full datasets from Zenodo? (for examples/vignettes/tests) +test.mode <- FALSE + package.name <- "curatedBladderData" #rescale each gene to z-score? diff --git a/inst/extdata/testdata/GSE13507_eset.rda b/inst/extdata/testdata/GSE13507_eset.rda new file mode 100644 index 0000000..96a4bb1 Binary files /dev/null and b/inst/extdata/testdata/GSE13507_eset.rda differ diff --git a/inst/extdata/testdata/GSE1827_eset.rda b/inst/extdata/testdata/GSE1827_eset.rda new file mode 100644 index 0000000..9aade87 Binary files /dev/null and b/inst/extdata/testdata/GSE1827_eset.rda differ diff --git a/inst/extdata/testdata/GSE19915.GPL3883_eset.rda b/inst/extdata/testdata/GSE19915.GPL3883_eset.rda new file mode 100644 index 0000000..6181a69 Binary files /dev/null and b/inst/extdata/testdata/GSE19915.GPL3883_eset.rda differ diff --git a/inst/extdata/testdata/GSE19915.GPL5186_eset.rda b/inst/extdata/testdata/GSE19915.GPL5186_eset.rda new file mode 100644 index 0000000..764fec4 Binary files /dev/null and b/inst/extdata/testdata/GSE19915.GPL5186_eset.rda differ diff --git a/inst/extdata/testdata/GSE31189_eset.rda b/inst/extdata/testdata/GSE31189_eset.rda new file mode 100644 index 0000000..6eeceb3 Binary files /dev/null and b/inst/extdata/testdata/GSE31189_eset.rda differ diff --git a/inst/extdata/testdata/GSE31684_eset.rda b/inst/extdata/testdata/GSE31684_eset.rda new file mode 100644 index 0000000..ad399f4 Binary files /dev/null and b/inst/extdata/testdata/GSE31684_eset.rda differ diff --git a/inst/extdata/testdata/GSE32894_eset.rda b/inst/extdata/testdata/GSE32894_eset.rda new file mode 100644 index 0000000..ad8a1fa Binary files /dev/null and b/inst/extdata/testdata/GSE32894_eset.rda differ diff --git a/inst/extdata/testdata/GSE37317_eset.rda b/inst/extdata/testdata/GSE37317_eset.rda new file mode 100644 index 0000000..a860629 Binary files /dev/null and b/inst/extdata/testdata/GSE37317_eset.rda differ diff --git a/inst/extdata/testdata/GSE5287_eset.rda b/inst/extdata/testdata/GSE5287_eset.rda new file mode 100644 index 0000000..0d3cee4 Binary files /dev/null and b/inst/extdata/testdata/GSE5287_eset.rda differ diff --git a/inst/extdata/testdata/GSE89_eset.rda b/inst/extdata/testdata/GSE89_eset.rda new file mode 100644 index 0000000..90ed848 Binary files /dev/null and b/inst/extdata/testdata/GSE89_eset.rda differ diff --git a/inst/extdata/testdata/PMID17099711.GPL8300_eset.rda b/inst/extdata/testdata/PMID17099711.GPL8300_eset.rda new file mode 100644 index 0000000..bab4d62 Binary files /dev/null and b/inst/extdata/testdata/PMID17099711.GPL8300_eset.rda differ diff --git a/inst/extdata/testdata/PMID17099711.GPL91_eset.rda b/inst/extdata/testdata/PMID17099711.GPL91_eset.rda new file mode 100644 index 0000000..34dd886 Binary files /dev/null and b/inst/extdata/testdata/PMID17099711.GPL91_eset.rda differ diff --git a/inst/extdata/zenodo-manifest.csv b/inst/extdata/zenodo-manifest.csv new file mode 100644 index 0000000..c28220a --- /dev/null +++ b/inst/extdata/zenodo-manifest.csv @@ -0,0 +1,13 @@ +dataset,filename,url,md5,size_bytes +GSE13507_eset,GSE13507_eset.rda,https://zenodo.org/records/23192472/files/GSE13507_eset.rda?download=1,9df8c8a2c77600fb3346d7a9d6b34b20,6289556 +GSE1827_eset,GSE1827_eset.rda,https://zenodo.org/records/23192472/files/GSE1827_eset.rda?download=1,3a195c532c1067c1204fd963583d4b2c,3157656 +GSE19915.GPL3883_eset,GSE19915.GPL3883_eset.rda,https://zenodo.org/records/23192472/files/GSE19915.GPL3883_eset.rda?download=1,6d433ad595361f1a893d0f3e07a0f4c3,5705184 +GSE19915.GPL5186_eset,GSE19915.GPL5186_eset.rda,https://zenodo.org/records/23192472/files/GSE19915.GPL5186_eset.rda?download=1,d78aa0d949148cd5a30868453896d058,7141496 +GSE31189_eset,GSE31189_eset.rda,https://zenodo.org/records/23192472/files/GSE31189_eset.rda?download=1,feb209ae4826e1691b7c4d39048b9635,8670296 +GSE31684_eset,GSE31684_eset.rda,https://zenodo.org/records/23192472/files/GSE31684_eset.rda?download=1,3deda3de9e45b169271f472d8c2c2e62,7548196 +GSE32894_eset,GSE32894_eset.rda,https://zenodo.org/records/23192472/files/GSE32894_eset.rda?download=1,43100e65e99bf474f00cb018500df411,14435224 +GSE37317_eset,GSE37317_eset.rda,https://zenodo.org/records/23192472/files/GSE37317_eset.rda?download=1,b0dd231adb053574f487f832e38ad5f5,1791700 +GSE5287_eset,GSE5287_eset.rda,https://zenodo.org/records/23192472/files/GSE5287_eset.rda?download=1,93666b747360690461639477e535ef70,2803624 +GSE89_eset,GSE89_eset.rda,https://zenodo.org/records/23192472/files/GSE89_eset.rda?download=1,93e550b942226b5958f7dad7abefcaa1,1542464 +PMID17099711.GPL8300_eset,PMID17099711.GPL8300_eset.rda,https://zenodo.org/records/23192472/files/PMID17099711.GPL8300_eset.rda?download=1,acd04f2130e55e221919f6491bf60e7b,1923304 +PMID17099711.GPL91_eset,PMID17099711.GPL91_eset.rda,https://zenodo.org/records/23192472/files/PMID17099711.GPL91_eset.rda?download=1,8c88b6bf8f2834efa022e5b3d7fd026a,1994320 diff --git a/inst/scripts/data-integrity-report.Rmd b/inst/scripts/data-integrity-report.Rmd new file mode 100644 index 0000000..0a2e7fb --- /dev/null +++ b/inst/scripts/data-integrity-report.Rmd @@ -0,0 +1,101 @@ +--- +title: "Data integrity report: Zenodo offloading" +output: html_document +params: + package: "curatedBladderData" + upload_dir: "../../../curatedBladderData-zenodo-upload" +--- + + + +```{r setup, include=FALSE} +knitr::opts_chunk$set(echo = TRUE) +suppressPackageStartupMessages(library(Biobase)) +pkg <- params$package +stopifnot(requireNamespace(pkg)) +``` + +# 1. Uploaded objects vs pre-refactor package objects + +```{r compare-objects} +pre <- tempfile("pre-refactor-") +system2("git", c("worktree", "add", "--detach", pre, "pre-zenodo-refactor"), + stdout = FALSE) + +manifest <- read.csv(file.path("..", "extdata", "zenodo-manifest.csv")) +loadOne <- function(path) { + env <- new.env(); nm <- load(path, envir = env); get(nm[1], envir = env) +} +cmp <- vapply(manifest$dataset, function(nm) { + old <- loadOne(file.path(pre, "data", paste0(nm, ".rda"))) + new <- loadOne(file.path(params$upload_dir, paste0(nm, ".rda"))) + isTRUE(all.equal(exprs(old), exprs(new))) && + isTRUE(all.equal(pData(old), pData(new))) && + isTRUE(all.equal(fData(old), fData(new))) && + identical(annotation(old), annotation(new)) && + isTRUE(all.equal(experimentData(old), experimentData(new))) +}, logical(1)) +system2("git", c("worktree", "remove", "--force", pre)) +data.frame(dataset = manifest$dataset, identical = cmp) +stopifnot(all(cmp)) +``` + +# 2. Manifest md5s vs local uploads and Zenodo + +```{r compare-md5} +local_path <- file.path(params$upload_dir, manifest$filename) +stopifnot(identical(unname(tools::md5sum(local_path)), manifest$md5)) +# size_bytes is not cosmetic: R/getData.R compares it against the cached +# file on every cache hit, so a wrong size silently evicts and re-downloads +# a perfectly good file on every call. +stopifnot(identical(unname(file.size(local_path)), as.numeric(manifest$size_bytes))) + +record <- unique(sub(".*records/([0-9A-Za-z_]+)/.*", "\\1", manifest$url)) +stopifnot(length(record) == 1L) +api <- jsonlite::fromJSON(paste0("https://zenodo.org/api/records/", record)) +zen <- data.frame(filename = api$files$key, + md5 = sub("^md5:", "", api$files$checksum), + size = as.numeric(api$files$size)) +merged <- merge(manifest[, c("filename", "md5", "size_bytes")], zen, + by = "filename", suffixes = c(".manifest", ".zenodo")) +stopifnot(nrow(merged) == nrow(manifest), + identical(merged$md5.manifest, merged$md5.zenodo), + identical(as.numeric(merged$size_bytes), merged$size)) +merged +``` + +# 3. Download smoke test + +```{r smoke-test} +getter <- get(pkg, envir = asNamespace(pkg)) +smallest <- manifest$dataset[which.min(manifest$size_bytes)] +# use a throwaway cache so the user's real package cache is untouched +old_cache <- Sys.getenv("R_USER_CACHE_DIR", unset = NA) +Sys.setenv(R_USER_CACHE_DIR = tempfile("cache-")) +eset <- getter(smallest) # downloads +stopifnot(is(eset, "ExpressionSet")) +eset2 <- getter(smallest) # must hit the cache silently +stopifnot(identical(dim(eset), dim(eset2))) +if (is.na(old_cache)) Sys.unsetenv("R_USER_CACHE_DIR") else + Sys.setenv(R_USER_CACHE_DIR = old_cache) +dim(eset) +``` + +```{r session-info} +sessionInfo() +``` diff --git a/inst/scripts/make-data.R b/inst/scripts/make-data.R new file mode 100644 index 0000000..75485e4 --- /dev/null +++ b/inst/scripts/make-data.R @@ -0,0 +1,69 @@ +## Prepare the per-dataset .rda files uploaded to Zenodo, along with the +## download manifest, the data/ stub files, and data/datalist. +## +## The datasets themselves were produced by the curatedBladderData curation +## pipeline (see the package vignette); +## this script re-serializes the .rda files that shipped inside the package +## through version 1.49.x (git tag: pre-zenodo-refactor) with xz compression +## for hosting on Zenodo. +## +## Run from the package root with the full data/*.rda files present: +## Rscript inst/scripts/make-data.R # before upload (placeholder URLs) +## Rscript inst/scripts/make-data.R # after publishing, to finalize URLs +## Already-compressed files in the output directory are left untouched, so +## re-running with the record ID only rewrites the manifest. Do NOT +## regenerate the .rda files after uploading (checksums must match Zenodo). +## +## NOTE: this script is intentionally duplicated in curatedCRCData and +## curatedOvarianData. Keep the copies in sync. + +pkg <- unname(read.dcf("DESCRIPTION")[, "Package"]) +record <- commandArgs(trailingOnly = TRUE) +record <- if (length(record)) record[1L] else "RECORD_ID" + +outdir <- file.path("..", paste0(pkg, "-zenodo-upload")) +stubdir <- file.path(outdir, "stubs") +dir.create(stubdir, recursive = TRUE, showWarnings = FALSE) + +## enumerate from data/ before the refactor, or from the already-compressed +## upload directory when re-running to finalize URLs +rdas <- sort(list.files("data", pattern = "\\.rda$")) +if (length(rdas) == 0L) + rdas <- sort(list.files(outdir, pattern = "\\.rda$")) +stopifnot(length(rdas) > 0L) + +rows <- lapply(rdas, function(f) { + nm <- sub("\\.rda$", "", f) + out <- file.path(outdir, f) + if (!file.exists(out)) { + env <- new.env(parent = emptyenv()) + obj <- load(file.path("data", f), envir = env) + stopifnot(identical(obj, nm)) + save(list = nm, envir = env, file = out, compress = "xz") + message("compressed ", f) + } + writeLines(sprintf( + 'delayedAssign("%s",\n %s:::.stubLoad("%s"),\n assign.env = environment())', + nm, pkg, nm), + file.path(stubdir, paste0(nm, ".R"))) + data.frame(dataset = nm, filename = f, + url = sprintf("https://zenodo.org/records/%s/files/%s?download=1", + record, f), + md5 = unname(tools::md5sum(out)), size_bytes = file.size(out)) +}) +manifest <- do.call(rbind, rows) + +write.csv(manifest, file.path(outdir, "zenodo-manifest.csv"), + row.names = FALSE, quote = FALSE) +writeLines(manifest$dataset, file.path(stubdir, "datalist")) +writeLines(sprintf("%s %12d %s", manifest$md5, manifest$size_bytes, + manifest$filename), + file.path(outdir, "upload-manifest.txt")) + +message(nrow(manifest), " datasets; total upload size ", + round(sum(manifest$size_bytes) / 2^20), " MB; written to ", outdir) +if (identical(record, "RECORD_ID")) + message("Upload the .rda files to a new Zenodo record, publish it, ", + "verify the md5s shown by Zenodo against upload-manifest.txt, ", + "then re-run this script with the record ID and copy ", + "zenodo-manifest.csv to inst/extdata/.") diff --git a/inst/scripts/make-test-data.R b/inst/scripts/make-test-data.R new file mode 100644 index 0000000..1650e0e --- /dev/null +++ b/inst/scripts/make-test-data.R @@ -0,0 +1,40 @@ +## Build the small offline test fixtures in inst/extdata/testdata/: one per +## dataset, subset to the first <=200 features and <=100 samples with full +## phenoData retained, so that examples, tests, vignettes, and R CMD check +## run without network access (see the test= argument of the getter and +## .stubLoad in R/getData.R). +## +## Run from the package root with the full data/*.rda files present +## (git tag: pre-zenodo-refactor): +## Rscript inst/scripts/make-test-data.R +## +## NOTE: this script is intentionally duplicated in curatedCRCData and +## curatedOvarianData. Keep the copies in sync. + +suppressPackageStartupMessages(library(Biobase)) + +dir.create("inst/extdata/testdata", recursive = TRUE, showWarnings = FALSE) +rdas <- sort(list.files("data", pattern = "\\.rda$")) +stopifnot(length(rdas) > 0L) + +for (f in rdas) { + nm <- sub("\\.rda$", "", f) + env <- new.env(parent = emptyenv()) + obj <- load(file.path("data", f), envir = env) + stopifnot(identical(obj, nm)) + eset <- get(nm, envir = env) + ## keep the first ~200 features plus genes used in the vignettes + ## (survival forest plots, probe-expansion examples) + fn <- featureNames(eset) + keep <- unique(c(intersect(c("CXCL12", "CXCR4"), fn), + head(grep("///", fn, value = TRUE), 5L), + head(fn, 200L))) + fx <- eset[keep, seq_len(min(100L, ncol(eset)))] + assign(nm, fx) + save(list = nm, file = file.path("inst/extdata/testdata", f), + compress = "xz") + message("fixture ", nm, ": ", nrow(fx), " x ", ncol(fx)) +} +message("total fixture size: ", + round(sum(file.size(list.files("inst/extdata/testdata", + full.names = TRUE))) / 2^10), " KB") diff --git a/man/curatedBladderData-package.Rd b/man/curatedBladderData-package.Rd index 18f6d53..d6424da 100755 --- a/man/curatedBladderData-package.Rd +++ b/man/curatedBladderData-package.Rd @@ -1,33 +1,26 @@ \name{curatedBladderData-package} -\alias{curatedBladderData} +\alias{curatedBladderData-package} \docType{package} \title{Clinically Annotated Data for the Bladder Cancer Transcriptome} \description{The curatedBladderData package provides manually curated - clinical data, uniformly processed expression data, and convenience - functions for gene expression analysis in patients with ovarian cancer.} + clinical data and uniformly processed expression data for gene + expression analysis in patients with bladder cancer.} \details{ -\tabular{ll}{ -Package: \tab curatedBladderData\cr -Type: \tab Package\cr -Version: \tab \Sexpr{packageDescription("curatedBladderData")$Version}\cr -Date: \tab \Sexpr{packageDescription("curatedBladderData")$Date}\cr -License: \tab Artistic-2.0\cr -Depends: \tab R (>= 2.10.0), affy\cr -} + Datasets are hosted on Zenodo and downloaded on first use with + \code{\link{curatedBladderData}()}, which caches them locally with + \pkg{BiocFileCache}. Legacy \code{data()} access still works but is + deprecated and downloads through the same mechanism. See the package + vignette for rule-based creation of a list of filtered datasets. } \author{ -Benjamin F. Ganzfried, Steve Skates, Markus Riester, -Victoria Wang, Thomas Risch, Benjamin Haibe-Kains, Curtis -Huttenhower, Svitlana Tyekucheva, Jie Ding, Ina Jazic, -Michael Birrer, Giovanni Parmigiani, Levi Waldron - -Department of Biostatistics and Computational Biology, Dana-Farber Cancer Institute, Harvard School of Public Health +Markus Riester -Maintainer: Levi Waldron +Maintainer: Markus Riester } - \examples{ -##List all datasets: -data(package="curatedBladderData") -## +## List all datasets: +curatedBladderData() +## Load one dataset (test=TRUE uses a small offline subset): +eset <- curatedBladderData("GSE89_eset", test = TRUE) } +\keyword{package} diff --git a/man/curatedBladderData.Rd b/man/curatedBladderData.Rd new file mode 100644 index 0000000..bf42804 --- /dev/null +++ b/man/curatedBladderData.Rd @@ -0,0 +1,40 @@ +\name{curatedBladderData} +\alias{curatedBladderData} +\title{Download curated bladder cancer ExpressionSets} +\description{ + Datasets are hosted on Zenodo and downloaded individually on first use, + then cached locally with \pkg{BiocFileCache}; later calls load from the + cache without network access. Loading datasets with \code{data()} still + works but is deprecated in favor of this function. +} +\usage{ +curatedBladderData(datasets, test = FALSE) +} +\arguments{ + \item{datasets}{Character vector of dataset names (e.g. + \code{"GSE32894_eset"}). If missing, the names of all available + datasets are returned and nothing is downloaded.} + \item{test}{If \code{TRUE}, load small offline test subsets bundled + with the package instead of downloading the full datasets. Intended + for examples, tests, and vignettes; the subsets are not suitable + for analysis.} +} +\value{ + A single \code{\linkS4class{ExpressionSet}} if one dataset name is + given; a named list of \code{ExpressionSet}s if several; a character + vector of available dataset names if \code{datasets} is missing. +} +\details{ + Downloaded files are stored in a package-specific cache directory + (\code{tools::R_user_dir("curatedBladderData", "cache")}). To remove + cached files, delete that directory or manage it with + \pkg{BiocFileCache}. +} +\examples{ +curatedBladderData() # list available datasets +eset <- curatedBladderData("GSE89_eset", test = TRUE) +dim(eset) +esets <- curatedBladderData(c("GSE37317_eset", "GSE89_eset"), test = TRUE) +names(esets) +} +\keyword{utilities} diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..a259495 --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,4 @@ +library(testthat) +library(curatedBladderData) + +test_check("curatedBladderData") diff --git a/tests/testthat/test-getData.R b/tests/testthat/test-getData.R new file mode 100644 index 0000000..0427a02 --- /dev/null +++ b/tests/testthat/test-getData.R @@ -0,0 +1,85 @@ +# The 12 datasets this package has provided since its initial release; +# this list guards the Zenodo manifest against accidental row loss. +expected_datasets <- c( + "GSE13507_eset", + "GSE1827_eset", + "GSE19915.GPL3883_eset", + "GSE19915.GPL5186_eset", + "GSE31189_eset", + "GSE31684_eset", + "GSE32894_eset", + "GSE37317_eset", + "GSE5287_eset", + "GSE89_eset", + "PMID17099711.GPL8300_eset", + "PMID17099711.GPL91_eset" +) + +manifest <- read.csv(system.file("extdata", "zenodo-manifest.csv", + package = "curatedBladderData")) + +test_that("manifest is complete and well-formed", { + expect_setequal(manifest$dataset, expected_datasets) + expect_false(anyDuplicated(manifest$dataset) > 0) + expect_identical(manifest$filename, paste0(manifest$dataset, ".rda")) + expect_true(all(grepl("^[0-9a-f]{32}$", manifest$md5))) + expect_true(all(grepl( + "^https://zenodo\\.org/records/[0-9]+/files/.+\\?download=1$", + manifest$url))) + expect_true(all(manifest$size_bytes > 0)) +}) + +test_that("manifest, fixtures, and data/ stubs are in lock-step", { + fixtures <- sub("\\.rda$", "", list.files( + system.file("extdata", "testdata", package = "curatedBladderData"), + pattern = "\\.rda$")) + expect_setequal(fixtures, expected_datasets) + stubs <- data(package = "curatedBladderData")$results[, "Item"] + expect_setequal(stubs, expected_datasets) +}) + +test_that("no-argument call lists the datasets", { + expect_setequal(curatedBladderData(), expected_datasets) +}) + +test_that("getter returns ExpressionSets from offline fixtures", { + eset <- curatedBladderData("GSE89_eset", test = TRUE) + expect_s4_class(eset, "ExpressionSet") + expect_gt(ncol(eset), 0) + esets <- curatedBladderData(c("GSE37317_eset", "GSE89_eset"), test = TRUE) + expect_type(esets, "list") + expect_named(esets, c("GSE37317_eset", "GSE89_eset")) + expect_s4_class(esets[[1]], "ExpressionSet") +}) + +test_that("unknown dataset names give an informative error", { + expect_error(curatedBladderData("NOT_A_DATASET"), "Unknown dataset") + expect_error(curatedBladderData("NOT_A_DATASET", test = TRUE), + "Unknown dataset") +}) + +test_that("data() stubs create a working binding", { + e <- new.env() + data("GSE89_eset", package = "curatedBladderData", envir = e) + expect_true(exists("GSE89_eset", envir = e)) + # force the delayed binding under the check guard so .stubLoad() runs + # and resolves to the offline fixture (no network) + old <- Sys.getenv("_R_CHECK_PACKAGE_NAME_", unset = NA) + Sys.setenv("_R_CHECK_PACKAGE_NAME_" = "curatedBladderData") + on.exit(if (is.na(old)) Sys.unsetenv("_R_CHECK_PACKAGE_NAME_") else + Sys.setenv("_R_CHECK_PACKAGE_NAME_" = old), add = TRUE) + expect_s4_class(e$GSE89_eset, "ExpressionSet") +}) + +test_that("full download works (opt-in; set RUN_FULL_DOWNLOAD_TESTS=1)", { + skip_on_bioc() + skip_if(!nzchar(Sys.getenv("RUN_FULL_DOWNLOAD_TESTS"))) + skip_if_offline("zenodo.org") + # smallest dataset in the collection + eset <- curatedBladderData("GSE1827_eset") + expect_s4_class(eset, "ExpressionSet") + # second call must hit the cache (no download message) + expect_silent( + eset2 <- curatedBladderData("GSE1827_eset")) + expect_identical(dim(eset), dim(eset2)) +}) diff --git a/vignettes/curatedBladderData_vignette.rnw b/vignettes/curatedBladderData_vignette.rnw index 122af2e..351d562 100755 --- a/vignettes/curatedBladderData_vignette.rnw +++ b/vignettes/curatedBladderData_vignette.rnw @@ -38,18 +38,38 @@ and clinical data formats. In this vignette, we give a short tour of the package and will show how to use it efficiently. +\section{Data access and caching} + +As of version 1.49.1 the datasets are no longer stored inside the package. +They are hosted on Zenodo and downloaded individually the first time they +are requested, then cached locally with \Biocpkg{BiocFileCache}, so later +uses need no network access. The cache lives in a package-specific +directory, \texttt{tools::R\_user\_dir("curatedBladderData", "cache")}; to +reclaim disk space, delete that directory. + +The legacy \texttt{data(GSE89\_eset)} syntax still works, downloading +through the same cache, but is deprecated in favour of the +\texttt{curatedBladderData()} function shown below. + +Throughout this vignette we pass \texttt{test = TRUE} (and set +\texttt{test.mode <- TRUE}), which loads small offline subsets bundled with +the package so that the vignette builds without downloading data. For real +analyses, omit these settings. + \section{Load data sets} Loading a single dataset is very easy. First we load the package: <>= library(curatedBladderData) @ -To get a listing of all the datasets, use the \texttt{data} function: +To get a listing of all the datasets, call \texttt{curatedBladderData()} +with no arguments: <>= -data(package="curatedBladderData") +curatedBladderData() @ -Now to load a single dataset, we use the \texttt{data} function again: -<>= -data(GSE89_eset) +Now to load a single dataset (drop \texttt{test = TRUE} to download the +full dataset): +<>= +GSE89_eset <- curatedBladderData("GSE89_eset", test = TRUE) GSE89_eset @ The datasets are provided as Bioconductor \texttt{ExpressionSet} objects and @@ -67,6 +87,7 @@ early stage and normal samples. <>= source(system.file("extdata", "patientselection_all.config",package="curatedBladderData")) +test.mode <- TRUE # use the bundled offline subsets in this vignette ls() @ @@ -208,8 +229,7 @@ a simple way to do it. For one dataset: <>= library(curatedBladderData) -library(affy) -data(GSE89_eset) +GSE89_eset <- curatedBladderData("GSE89_eset") write.csv(exprs(GSE89_eset), file="GSE89_eset_exprs.csv") write.csv(pData(GSE89_eset), file="GSE89_eset_clindata.csv") @ @@ -220,7 +240,7 @@ Or for several datasets: data.to.fetch <- c("GSE89_eset", "GSE37317_eset") for (onedata in data.to.fetch){ print(paste("Fetching", onedata)) - data(list=onedata) + assign(onedata, curatedBladderData(onedata)) write.csv(exprs(get(onedata)), file=paste(onedata, "_exprs.csv", sep="")) write.csv(pData(get(onedata)), file=paste(onedata, "_clindata.csv", sep="")) }