--- title: "Using projectData" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Using projectData} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") ``` ## Why projectData? R packages are an effective way to distribute research data. A package carries a version, documentation for each dataset, and a defined installation location, and R already provides mature ways to reach the contents: `data()`, lazy loading of package data, and namespace-qualified access such as `pkg::object`. For a single data package these tools are usually all that is needed, and projectData is designed to work alongside them, not to replace them. Difficulties tend to appear when an analysis depends on several independently distributed collections. Data may be divided between a primary project package and supplementary or reference packages, and different collections may define objects with the same name. Attaching packages or calling `data()` for each one leaves the choice of source to attachment order or to repeated manual qualification, which is hard to keep consistent across a project. Some resources are also large or stored in formats that ordinary deserialization cannot restore, such as disk-backed matrices, so they need a format-specific restoration step and should stay unopened until they are used. Finally, it is often useful to ask what a project has available, and which installed package version and library each object came from, without reading any data. This matters most when several versions or libraries are present. These are workflow and coordination problems. They do not reflect a limitation of base R, and many projects never meet them. projectData is a small coordination layer over installed data packages. Calling `data_attach()` on a package registers it as a named *collection* and records an inventory of its resources, along with the package name, version, and the installation root that was resolved. Registering reads package metadata only; no data values are read. Objects are then read on first access, and `data_get()` can return an object from a named collection or package without attaching anything. When collections share a name, projectData does not pick a winner by attachment order. You can designate a primary collection, state a preference for individual objects, or add suffix aliases. An unresolved conflict produces an error that lists the candidates. Attachment is optional and happens only when you request it, so objects can also be reached without adding names to the search path. `data_catalog()` and `data_info()` report provenance from the recorded metadata alone. Ordinary data packages need no changes to work this way. An optional, validated manifest can describe special files or object mappings, and a versioned loader interface lets an *adapter* supply the discovery and opening steps for a specialized storage format. This division keeps the core package light. The core handles registration, resolution, caching, and provenance, and it reads ordinary datasets and explicitly described RDS files. Format-specific code lives in separate adapter packages that you choose to install. For example, the separate `projectData.monocle3` package restores Monocle 3 `cell_data_set` objects backed by BPCells matrices, so that projectData does not depend on monocle3 or BPCells. See the [adapter vignette](adapters.html) for how adapters work. Missing or unavailable adapters affect only the resources that need them. projectData is not a package manager. It never installs or updates data packages, and it does not replace distribution through CRAN, Bioconductor, Zenodo, or similar channels. It cannot make compatible software versions or external backing files available, and it does not by itself make an analysis reproducible. It is also not a data-processing framework. If `data()` and namespace-qualified access already meet your needs, you can keep using them. projectData is intended for projects that need consistent access to several collections, deferred opening of large or specialized resources, and a record of where each object came from. The normal workflow has three steps: 1. **Install** the data package. 2. **Attach** it. 3. **Use** its objects, interactively or in a script. Need another data package later? Attach it too. If the packages define the same name, projectData asks you to decide which one you mean (see [Conflicts](#conflicts)). ## 1. Install Installing is ordinary R package installation and is not done by projectData. A data package distributed as a tarball (for example from Zenodo) is installed like any source package: ```r install.packages("path/to/rnaseqData_1.0.0.tar.gz", repos = NULL, type = "source") ``` To keep this vignette offline, the next chunk builds two tiny throwaway data packages in a temporary library. In real use, skip this chunk and omit the `lib.loc` argument below. ```{r fixtures} library(projectData) source(system.file("examples", "fixtures.R", package = "projectData")) lib <- fixture_library() fixture_package(lib, "rnaseqData", "1.0.0", lazy = TRUE, datasets = list( counts_rna = list(counts_rna = matrix(1:6, 2)), meta_rna = list(meta_rna = data.frame(sample = c("a", "b"))) )) fixture_package(lib, "proteinData", "2.1.0", lazy = FALSE, datasets = list( counts_prot = list(counts_prot = matrix(7:12, 2)), meta_prot = list(meta_prot = data.frame(sample = c("a", "b"))) )) ``` ## 2. Attach `data_attach()` takes the name of an installed data package. It records what the package contains and puts its objects on the search path under their own names. This reads package metadata only: no data values are read, and nothing is installed. Nothing is attached unless you call this function. ```{r attach} data_attach("rnaseqData", lib.loc = lib) ``` ## 3. Use Use the objects by their ordinary names. The first use of an object reads it. ```{r use} data_config(messages = TRUE) counts_rna ``` The message names the resource and says it is opened from the installed package; projectData never installs packages. The same code works in a script after the `data_attach()` call. Attach another package whenever you need it. Objects already in use are not affected. ```{r attach-second} data_attach("proteinData", lib.loc = lib) counts_prot ``` ## Looking at what is attached These read only recorded metadata. They never open any data. ```{r catalog} data_catalog()[, c("collection", "exposed_name", "resource", "state")] ``` `state` is `unopened` until the object has been used, then `ready`. The `resource` column is the loadable unit, which can differ from the object name. A resource that produces several objects is opened once for all of them. `data_info("counts_rna")` returns the full provenance row for a name: package, version, installed location, source file, where it was discovered (`origin`), and whether its object names have been checked (`mapping`). `data_help("counts_rna")` opens the help page of the original topic in the source package. It is separate from inspection and never runs implicitly. ## Getting objects explicitly Bare names are convenient, but `data_get()` returns an object without relying on what is on the search path, so it is the more robust choice inside functions and scripts. (Attached bindings can also be evaluated by IDE completion or previews; see `system.file("ide-repro", package = "projectData")`.) ```{r explicit} data_get("meta_prot") data_get("meta_rna", collection = "rnaseqData") ``` Give several names to open several objects in one call. This is a good way to open large objects at the start of a batch job: the first-access messages appear together, before the rest of the analysis runs. ```{r several} str(data_get(c("meta_rna", "meta_prot"))) ``` ## Getting one object without attaching Sometimes you want a single object and no new names on the search path, much as you would write `package::object` for an exported object. Give `data_get()` the `package`: ```{r get-unattached} fixture_package(lib, "imagingData", "0.3.0", lazy = TRUE, datasets = list( counts_img = list(counts_img = matrix(13:18, 2)) )) counts_img <- data_get("counts_img", package = "imagingData", lib.loc = lib) counts_img "imagingData" %in% data_collections()$package[data_collections()$attached] ``` There is nothing to register first. `data_get()` inventories the package if necessary (metadata only), then returns the object. It does not attach anything and does not change collections that are already attached. The name is looked up in that package itself, so preferences, primary collections and suffixes you set for attached access do not affect it. `data_get()` still **opens** the object you ask for; it is not a metadata query. Use `data_catalog()` or `data_query()` to look without opening. If you attach the package later, the object is not read again. ## First-access messages Before each resource is read for the first time, projectData prints a message naming the collection, the objects, the size of the source file when it is a single file, and the loader. For large data this is the moment to notice what is about to load. Turn the messages off with `data_config(messages = FALSE)`; the setting lasts for the session. ## Looking at a package without attaching it `data_query()` shows what a package contains, and which names would collide with the packages you list or with what is already attached, **without attaching anything or remembering anything**. Use it to look before you attach. ```{r query} data_detach() fixture_package(lib, "rnaseqBatch2", "1.0.0", lazy = TRUE, datasets = list( counts_rna = list(counts_rna = matrix(101:106, 2)), meta_rna = list(meta_rna = data.frame(sample = c("c", "d"))) )) data_query(c("rnaseqData", "rnaseqBatch2"), lib.loc = lib, id = c("batch1", "batch2"))[, c("collection", "exposed_name", "binding")] nrow(data_collections()) ``` The query already shows the problem: `counts_rna` and `meta_rna` exist in both packages, so their `binding` is `ambiguous`. The query changed nothing: no collections are attached. ## Conflicts {#conflicts} When two attached collections define the same name, projectData never resolves it by attachment order. Attaching still succeeds, and the catalog flags the conflict. Using the bare name fails with an error that lists the candidates. Use `id` to give each collection a name of your own: ```{r conflict, error = TRUE} data_attach(c("rnaseqData", "rnaseqBatch2"), lib.loc = lib, id = c("batch1", "batch2")) counts_rna ``` There are three ways to resolve conflicts, and they can be combined. **Per-object preference.** Choose the collection for a particular name. This is the right tool when an object from one package is always preferred over an object with the same name in another package. ```{r prefer} data_prefer("counts_rna", "batch1") counts_rna ``` Other contested names stay unresolved until you decide them: ```{r prefer-others, error = TRUE} meta_rna ``` **A primary collection.** If one collection should win *all* its contested names, designate it. A per-object preference for a non-primary collection overrides the primary designation for that name. A primary collection can also be set when attaching: `data_attach("rnaseqData", id = "batch1", primary = "batch1")`. ```{r primary} data_primary("batch1") meta_rna ``` **Suffix aliases and qualified access.** Qualified access, `data_get(name, collection = )`, is the most robust way to access objects within scripts or functions. It returns the named object and also reaches the copy that lost a conflict. ```{r get} data_get("counts_rna", collection = "batch2") ``` A suffix exposes every object in a collection as an alias of the form ``. Set it when you attach. ```{r suffix} data_attach("rnaseqBatch2", lib.loc = lib, id = "batch2", suffix = ".batch2", replace = TRUE) counts_rna.batch2 ``` Suffix aliases must be unique. A clash is reported when you attach, before anything is changed. To clear decisions, use `data_prefer("counts_rna", NULL)` and `data_primary(NULL)`. ## Script datasets A package that ships non-lazy data scripts (`data/*.R`) is opened by running the script, only on first access. The script sees the functions and data of the standard startup packages (stats, graphics, grDevices, utils, datasets, methods and base, in the order of an ordinary R session), so `head()` or `model.matrix()` work unqualified. That set is fixed; it does not follow your `defaultPackages` option or your search path. Your workspace and any other attached package are not visible, so qualify calls to other packages (`otherpkg::fn()`). This isolates names; it is not a security sandbox. See the "Script datasets" section of `?data_attach`. ## Cleaning up ```{r cleanup} data_detach() ``` `data_detach()` removes the attached collections and the environment projectData created, and forgets them. It does not delete files or modify objects you already hold, so an object kept in a variable can keep reading its backing files after detachment. That is not a promise about replacing or removing the installed package: a disk-backed object that references installed files may stop working then. See `?data_detach`. `data_detach("batch2")` removes just one collection. Packages you only reached through `data_get(package = )` are forgotten the same way (they were never on the search path); a later `data_get()` simply opens them again. ## What this does not do * Class compatibility, software versions, backing files, and platform-specific readers still matter; projectData does not guarantee reproducibility. * Returned objects are shared references to one cached resource, not independent copies. * Related tools: `conflicted` resolves conflicts between package exports and `library(include.only = )` selects exports. They work with exported data but do not provide collection inventory, deferred access beyond lazy data, format-specific restoration, or provenance.