Contents
PGSLibrary
PGSLibrary R6 class representing multiple PRS models
PGSLibrary is an R6 container holding N polygenic score models. The models are not
stored as owned PGS objects: each is a pointer into a shared PolyGeniusBackbone variant
dictionary, plus a per-position overlay recording whatever a grammar verb has changed. The
grammar filters, selects and mutates variants or model metadata, fetches per-model values,
and prints a tidy overview.
Prints a table returned by PGSLibrary$fetch(), then lists the unique error messages
of any column whose expression failed for at least one model.
s[[i]] materializes: it builds and returns a real, independent PGS that owns its variant
table, which costs one materialization off the shared dictionary. Reach for
library$models[[i]] instead when a cheap, non-materializing pointer is enough -- reading
$name, $build or nrow() in a loop over library$models, for instance. Rows come back in
genomic order, not the model's original ingest order.
Applies up to three operations, in the order i -> j -> k: pick models, select variant
columns, then filter models by a model-level condition. i is overloaded -- it subsets by
position when it evaluates to a number or a logical without a data mask, and is treated as a
variant-level filter expression otherwise. Every form returns a fully independent library
with its own freshly narrowed backbones, x[] with no index at all included.
Usage
S3 method for class 'PGSLibraryFetchTbl'
print(x, ...)
PGSLibrary(..., name = NULL)S3 method for class 'PGS'
c(..., recursive = FALSE, name = NULL)S3 method for class 'PGSLibrary'
c(..., recursive = FALSE, name = NULL)S3 method for class 'PGSLibrary'
length(x)S3 method for class 'PGSLibrary'
names(x)S3 method for class 'PGSLibrary'
names(x) <- valueS3 method for class 'PGSLibrary'
x[[i, ...]]S3 method for class 'PGSLibrary'
x[i, j, k, ...]Arguments
| Argument | Description |
|---|---|
x | A PGSLibrary. |
... | For PGSLibrary() and c(), unnamed [PGS](/reference/pgs/), PGSLibraryView or PGSLibrary objects, or lists of those -- a nested library contributes its models, in order -- while a named argument becomes a metadata field on the new library. For [[ and [, unused; present for S3 method compatibility. |
name | Character scalar, or NULL (default). Name for the resulting library. |
recursive | Logical scalar, default FALSE. Ignored; present for compatibility with the base c() generic. |
value | Character vector of new model names, exactly one per model, each non-NA and non-empty after trimming whitespace. Any other value aborts. |
i | Model index (numeric or logical) or an unquoted variant-level logical expression, or missing. For [[, a single in-range position only. As an index it is positional: a negative index excludes rather than selects, so x[-1] drops the first model, and positive and negative positions cannot be mixed. Two departures from base R: an out-of-range position is dropped silently rather than returning NA, and a logical index is taken as given rather than recycled. As an expression it is passed to filter.variants(). |
j | Character vector of variant column names, or an unquoted tidyselect expression, or missing. Passed to select.variants(). |
k | Unquoted model-level logical expression, or missing. Passed to filter.models(). |
Value
x, invisibly. Called for its console output.
A new PGSLibrary, holding copies of the models' content: the inputs are
absorbed, not retained, so later changes to the library never reach them.
For c(), a new PGSLibrary whose record has name = "constructed": the inputs'
$provenance records are not merged, and the inputs are not modified.
For length(), an integer scalar: the number of models.
For names(), a character vector of model names, one per model in position order.
Duplicates are possible and are kept: a name labels a model, position identifies it.
For names<-, x with its models renamed and $fingerprint restamped. x is an R6
object, so it is renamed in place: the assignment rebinds the same object, and any other
binding to it sees the new names too. The shared backbone is not renamed, so a library pointing
at the same models keeps its own names.
For [[, a new PGS owning its own variant table; mutating it does not affect the
library. Aborts unless i is a single index within 1:length(x).
For [, a new PGSLibrary, possibly empty. x is not modified.
Grammar
A verb that reshapes a library returns a new one and leaves the receiver untouched. Which
route it takes decides what that child shares. Every verb that builds its child through
$.narrow() gives it a fresh backbone per genome build, narrowed to the models it kept:
filter.variants(), select.variants(), mutate.variants(), filter.models(),
split.models() and [. mutate.models() rewrites the registry overlay instead, so its child
shares the parent's backbones. names<-() writes through that same overlay and renames in
place, returning the receiver rather than a child. Why the two routes differ is in
objects-map.md, section "The two column planes, and why they are keyed differently".
filter.variants(expr),select.variants(cols),mutate.variants(...)-- variant rows and columns, per model.filter.models(expr),mutate.models(...),split.models(expr)-- which models are in the library, and their model-level fields.fetch(...),modelsOverview(...),length()-- summaries across models.
Contract
A non-empty PGSLibrary is always backed by one PolyGeniusBackbone per genome
build, with every member's rows sorted into genomic order -- which is what lets a supplied
model.filter apply as a logical mask rather than a materialize-then-filter step. Every
construction path and every grammar verb preserves this, and compute$scores() normalizes
any models= shape into a library before reading from it. Full account in objects-map.md,
section "Why a bare PGS is deliberately plain, and PGSLibrary is not".
Library-level provenance ($provenance)
Every library carries one PolyGeniusProvenance() record of how the library itself was made,
read with provenance(library). It is not PGS$generation, which is per model and records the
algorithm that produced that one model.
name is the creation path -- "generate$models", "generate$from.pgs.catalog",
"generate$from.pgs.file", or "constructed" for a library assembled directly through
PGSLibrary() or c(), whose original sources are not recoverable. call is the deparsed
creating call, params what that call supplied, and created/version are stamped by the
constructor.
misc holds what the creation path resolved. For generate$models: sources, one entry per
input holding that input's resource-request parameters and spec.id, never its .meta;
algorithms, one entry per algorithm request including the whole hyperparameter sweep, which
is otherwise unrecoverable once the rule has expanded it into individual models; and
target.build/naming. A library produced by an execution-engine run also carries
failed.models -- a table of requested outputs that failed or were blocked, which can be
non-empty on a successfully returned library -- plus execution.status, execution.id,
execution.jsonl.path, execution.log.path, execution.summary.path and
execution.wall.seconds. Those key names are load-bearing:
visualize$execution$*() and generate-resolve.R resolve the run from them, each reading a
different subset.
One record, carried verbatim. A grammar verb -- filter.variants(), select.variants(),
filter.models(), mutate.variants(), mutate.models(), split.models(),
attach.variants(), an indexed [ -- narrows a library; it does not make one. The child keeps
its parent's record unchanged, so sources/algorithms still name the request its models came
from and visualize$execution$performance(subset) still resolves the run that built them. A
saveRDS() round trip keeps the record; c() starts a new "constructed" one and does not
merge its inputs'. See provenance() and objects-invariants.md invariant 5, which keeps the
library-level and per-model records distinct.
Operators and subsetting
c()combines models and libraries into a new,"constructed"library.length(),names(),names<-()read and rename the models.[[extracts one model, materialized as a real PGS.[subsets models, filters variants, or selects variant columns. It takes base R's index semantics, sox[-1]excludes the first model.
Public fields
name — Character scalar or NULL. Name of this library. Read/write.
provenance — A PolyGeniusProvenance(). Library-level provenance -- how this
library was made; read it with provenance(library), and see
the Library-level provenance section. Read/write, but package-managed: a grammar
verb carries it through unchanged, and print() renders it on its own line rather
than under Metadata fields.
fingerprint — Character scalar. Hash of the ordered position -> (name, build) map,
order-sensitive and duplicate-agnostic (repeated names are taken positionally, never
deduplicated). Read/write, but package-managed: restamped at the end of every
construction or mapping-changing mutation, so an assigned value does not survive one.
Compare two with comparePGSLibraries().
Active bindings
models — Plain list of PGSLibraryView objects, one per model in this library's
own position order, rebuilt from the registry on every read -- each view is a small
pointer record, so library$models[[i]] materializes no variant table, while
library[[i]] does and returns a real PGS. Reading it is O(n.models), so read it
once outside a loop, as every verb that loops over them does. Effectively read-only:
assigning anything other than this library's own views, in the same number, aborts.
The one accepted write is library$models[[i]]$name <- x, whose rename has already
landed in the registry by reference, so it is a no-op. Use the grammar verbs to
change what the library holds.
backbone — The one PolyGeniusBackbone this library points into. Read-only, and free
to read: it is the stored object, not a derived copy. Aborts on an empty library, and on a
library spanning more than one genome build -- naming the models on each build and pointing
at $backbones and liftover() -- because a backbone is single-build by construction
while a library may legitimately hold models from several builds.
backbones — Named list of every PolyGeniusBackbone this library points into, keyed by
resolved build key -- one entry per distinct build among its models, almost always of
length 1. Read-only; see $backbone for the single-build accessor most callers want.
Methods
Public methods
PGSLibrary.class$new()PGSLibrary.class$.set.registry()PGSLibrary.class$.get.registry()PGSLibrary.class$.narrow()PGSLibrary.class$print()PGSLibrary.class$modelsOverview()PGSLibrary.class$fetch()PGSLibrary.class$filter.variants()PGSLibrary.class$select.variants()PGSLibrary.class$filter.models()PGSLibrary.class$mutate.variants()PGSLibrary.class$attach.variants()PGSLibrary.class$mutate.models()PGSLibrary.class$split.models()PGSLibrary.class$length()PGSLibrary.class$write.pgs.file()
Method new()
Initialize a new PGSLibrary, absorbing every model handed to it
into one fresh PolyGeniusBackbone per distinct genome build. The inputs themselves are
not modified and not retained: absorption copies their content, so later changes to a
library never reach the model objects it was built from.
Without an explicit provenance = argument the library gets a minimal record with
name = "constructed" -- see the Library-level provenance section. Aborts on an
unnamed argument that is not model-like, and on a named argument that is. Emits an
informational message, not an error, when the models span more than one genome build.
Usage
PGSLibrary.class$new(..., name = NULL)
Arguments
... — Unnamed arguments are PGS, PGSLibraryView or PGSLibrary
objects, or lists of those; a nested library contributes its models, in order. Named
arguments become metadata fields on the library object, so a named argument that is
itself a model, view or library aborts rather than quietly becoming a field.
name — Character scalar, or NULL (default). Name for the library.
Returns
A new PGSLibrary.
Method .set.registry()
Replace this library's registry and backbone map in place, and restamp
$fingerprint from the new map. Not part of the user-facing grammar: called from
library.assemble() and from generate.library.from.execution()
(generate-resolve.R), each of which is building a brand new library.
Usage
PGSLibrary.class$.set.registry(registry, backbones)
Arguments
registry — A registry data.table, one row per model in position order, with
columns position, backbone.key, model.slot, name and the extra list-column.
backbones — Named list of PolyGeniusBackbone objects, keyed by resolved build key.
Returns
self, invisibly, with $fingerprint restamped.
Method .get.registry()
This library's own registry, copied. The read twin of $.set.registry(), for a
caller that has to write the registry out rather than derive a child from it -- the
registry is the only part of a library's state no active binding exposes. Copied because
the live table is shared by reference with every PGSLibraryView this library hands out,
whose $name<- writes straight into it.
Usage
PGSLibrary.class$.get.registry()
Returns
A data.table, one row per model in position order, with columns position,
backbone.key, model.slot, name and the extra list-column.
Method .narrow()
Build a child library holding only the retained models, and only the variants those models reference. Every verb producing a smaller or moved library routes through here, so none of them can disagree about what a dropped model leaves behind: the child owns a freshly narrowed backbone per build and shares nothing with this library.
Not part of the user-facing grammar. filter.models(), split.models(),
filter.variants() and [ are its callers.
Usage
PGSLibrary.class$.narrow(
keep = NULL,
keep.slots = NULL,
keep.rows = NULL,
name = self$name
)
Arguments
keep — Integer or logical over this library's positions, in the order they should
appear, or NULL for every model -- which is the identity narrowing, i.e. an
independent copy.
keep.slots — Passed to backbone.narrow(): narrows the retained models' variants
further, and cannot widen them.
keep.rows — Passed to backbone.narrow(): a per-model logical mask over that model's
own variant rows, indexed by this library's positions. Subset alongside keep, so a
library spanning two builds cannot hand one backbone another's masks.
name — Character scalar, default this library's own $name. Name for the child library.
Returns
A new PGSLibrary. This library is not modified. It keeps its parent's
provenance record verbatim: narrowing a library is not making one.
Method print()
Print a header, the per-model overview table, a row per free-form metadata
field, and a closing line summarising $provenance. name, models, provenance,
fingerprint, backbone and backbones are not listed as metadata fields.
Usage
PGSLibrary.class$print(...)
Arguments
... — Further unquoted expressions passed to self$modelsOverview(), each adding a
column to the printed table.
Returns
self, invisibly. Called for its console output.
Method modelsOverview()
Build a one-row-per-model overview: name, build and nvars, plus any
further expression given. Reads the registry only -- no variant table is materialized
unless an added expression mentions variants.
Usage
PGSLibrary.class$modelsOverview(...)
Arguments
... — Further unquoted expressions passed to self$fetch(), optionally named; each
adds a column.
Returns
A tibble of class "PGSLibraryFetchTbl", one row per model, with columns
name, build, nvars and one per extra expression.
Method fetch()
Evaluate one or more expressions once per model. Each is evaluated in a data
mask exposing that model's fields (name, build, n.variants, gwas, generation,
and anything mutate.models() attached) plus variants. An expression that fails is
recorded rather than raised: its value is NULL for that model, and the unique messages
land on the column's "errors" attribute and in the table's own "errors" list
attribute (column -> messages), which print() shows beneath the table.
variants is bound lazily, so an expression that never mentions it materializes
nothing; one that does forces a single materialization per model, reused across every
expression in the same call. Prefer n.variants over nrow(variants) for a row count
-- same number, no materialization.
Aborts when called with no expression.
Usage
PGSLibrary.class$fetch(...)
Arguments
... — One or more unquoted expressions, optionally named. A named argument uses its
name as the column name; an unnamed one uses its own expression text.
Returns
With one expression, its per-model values: an atomic vector when every model
returned a scalar or NULL, otherwise a list. With two or more, a tibble of class
"PGSLibraryFetchTbl", one row per model and one column per expression.
Method filter.variants()
Keep the variant rows matching expr, in every model. Materializes each
model's current variants, evaluates expr against it to a per-model row mask, and
narrows the child's own backbones to exactly those rows. A variant no surviving row
references leaves the dictionary, the weight columns and both column planes with it, so
the result is as small as its content and carries no trace of what was dropped.
Materializing the models decodes each backbone's dictionary once, however many models
share it.
NA drops the row, as dplyr::filter() does.
Usage
PGSLibrary.class$filter.variants(expr)
Arguments
expr — Unquoted variant-level expression, evaluated per model against its variants
table, as for dplyr::filter().
Returns
A new PGSLibrary owning fresh, narrowed backbones. This library is not
modified.
Method select.variants()
Drop every variant column cols does not name, in every model. The columns
are deleted from the child's own source and model planes, so a dropped one stops costing
memory rather than merely being hidden at materialization time.
The five base columns are undroppable and always survive: chr/position/nea/ea
are the shared dictionary itself, and beta is the model. Naming a subset of them is
therefore not an error, it simply selects nothing to drop.
cols is resolved once, against the first model's current columns. Two models can now
differ in which columns they carry -- mutate.variants() writes per model -- so naming
a column the first model does not have aborts even when a later one does. Aborts on an
empty library, which has no first model.
Usage
PGSLibrary.class$select.variants(cols)
Arguments
cols — Character vector of column names, or an unquoted tidyselect expression
yielding them, as for dplyr::select().
Returns
A new PGSLibrary owning fresh backbones, carrying only the base columns
and the selected ones. This library is not modified.
Method filter.models()
Keep only the models for which expr is TRUE. Positions are renumbered
from 1. The child owns a freshly narrowed backbone per build; no model is re-absorbed.
A model whose expr raises an error is dropped, not reported.
Usage
PGSLibrary.class$filter.models(expr)
Arguments
expr — Unquoted model-level logical expression, evaluated per model in a mask of its
fields plus variants (e.g. n.variants > 1000, build == "hg38/GRCh38"). A string
compared with build by ==, != or %in% is resolved through genomeBuilds
first, so any alias of the assembly matches (build == "hg38"; matching is the
catalog's own, see workspace$catalogs$genomeBuilds). Only a bare build is resolved,
not .data$build. Anything
other than TRUE drops the model. variants is bound lazily, so an expression that
never mentions it materializes nothing. Prefer n.variants over nrow(variants) for a
row count -- the same number, without the decode.
Returns
A new PGSLibrary with the surviving models, possibly empty. This library is
not modified.
Method mutate.variants()
Add or overwrite variant columns in every model. Each expression is
evaluated per model against its materialized variants, and the result is written back
to where that column actually lives: beta is a weight column, so it replaces one;
every other name is a per-model column, so it lands on the backbone's model plane,
row-aligned with the model's weights. Materializing the models decodes each backbone's
dictionary once, however many models share it.
A model's identity is not writable. chr, position, nea and ea are refused
outright: they are the dictionary's own content, shared by every model that carries the
variant, and rewriting one would silently move a different model's variant too. Build a
new library, or lift it, instead.
A name the model's GWAS source already provides is refused as well, rather than silently shadowed or given an invented suffix -- the two values would be the same column name meaning two different things, one shared across the source and one private to this model. Pick another name.
The child owns its own backbones, so nothing here is visible from this library or from any other library pointing at the same models.
Aborts, committing nothing at all, when the bytes of the columns this call introduces
or overwrites -- summed over every model, base columns not counted -- exceed
workspace$config$max.model.column.bytes. One new double column at genome scale is
tens of GB. Narrow the library with filter.models() first, or compute the expression in
the consuming code instead.
Usage
PGSLibrary.class$mutate.variants(...)
Arguments
... — One or more named, unquoted expressions, as for dplyr::mutate(); the names
are the columns added or overwritten, and are what the byte budget is charged against.
Returns
A new PGSLibrary owning fresh backbones, with the mutated variants.
This library is not modified.
Method attach.variants()
Attach study-invariant per-variant metadata -- pathogenicity, a nearest
gene, a severity score, a reference-panel frequency -- keyed on the variant itself
rather than on a model. One row is stored per variant and read by every model carrying
it, which is what distinguishes this from mutate.variants(): that writes a column per
model, so the same gene name would be stored once per model instead of once.
Only rows matching a variant already in the dictionary are kept, and the match/miss counts are reported rather than a partial join being kept silently. Alleles are oriented to canonical form at attach time, so a source spelling the pair the other way round still matches.
A library spanning two builds has one backbone per build and the source is attached to each. Coordinates belong to one build, so the other backbone will report few or no matches -- which the reported counts make visible.
Usage
PGSLibrary.class$attach.variants(name, tbl)
Arguments
name — Character scalar: the name the annotation is attached under.
tbl — A data.frame/data.table carrying chr, position, ea, nea and at
least one annotation column. A table missing one of the four, or carrying nothing
beyond them, aborts.
Returns
A new PGSLibrary owning fresh backbones with the annotation attached. This
library is not modified.
Method mutate.models()
Add or overwrite model-level fields in every model. Each expression is
evaluated per model in a mask of its fields plus variants. Writing name renames the
model in the child library's own registry; every other name lands in that position's extra
overlay. Neither touches the shared backbone, so a library pointing at the same models is
unaffected.
Usage
PGSLibrary.class$mutate.models(...)
Arguments
... — One or more named, unquoted expressions (e.g. name = toupper(name),
tag = "v1"). Every expression must be named -- the name is the field it writes --
and an unnamed one aborts, as does a call with no expressions at all. variants is
bound lazily, so an expression that never mentions it materializes nothing.
Returns
A new PGSLibrary sharing this library's backbones. This library is not
modified.
Method split.models()
Split the models into one library per distinct key. Every child gets its
own freshly narrowed backbone per build, and takes its key as its $name. A model
whose expr yields NA or raises an error is silently omitted from every child.
Usage
PGSLibrary.class$split.models(expr)
Arguments
expr — Unquoted expression evaluated per model in a mask of its fields plus
variants, then coerced to a character key. variants is bound lazily, so an
expression that never mentions it materializes nothing.
Returns
A named list of PGSLibrary objects, one per distinct key, named by it.
A model keyed NA is in no child library. This library is not modified.
Method length()
Count the models in this library. Reads the registry; materializes nothing.
Usage
PGSLibrary.class$length()
Returns
Integer scalar.
Method write.pgs.file()
Write every model to a directory as PGS Catalog scoring files: one .txt
per model, named from the model name (sanitized, and suffixed -2, -3, ... when two
models sanitize to the same stem, so no file is silently overwritten), plus a
manifest.json carrying this library's $fingerprint and, per model, position (this
library's own 1-based order, duplicates preserved -- there is no separate model id),
file, name and variants. Materializes each model in turn. Read back with
generate$from.pgs.file(), which errors unless the positions
form a gap-free permutation and the re-read library's recomputed fingerprint matches.
Usage
PGSLibrary.class$write.pgs.file(dir, mode = c("extended", "strict"))
Arguments
dir — Character scalar. Destination directory, created recursively when absent.
Existing files of the same name are overwritten.
mode — One of "extended" (default), "strict"; see PGS$write.pgs.file().
Returns
dir, invisibly. Called for the files it writes.
Examples
variants <- tibble::tibble(
chr = c("1", "1", "2"),
position = c(101, 202, 303),
ea = c("A", "G", "T"),
nea = c("G", "A", "C"),
beta = c(0.1, -0.2, 0.05)
)
m1 <- PGS(variants, name = "m1", build = "GRCh38")
m2 <- PGS(variants, name = "m2", build = "GRCh38")
s <- PGSLibrary(m1, m2, name = "my models")
s
s$modelsOverview()
s2 <- s$filter.variants(beta > 0)
s3 <- s$filter.models(n.variants > 2)See Also
Other pgs-objects:
PGS(),
PGSLibraryView,
PolyGeniusBackbone(),
PolyGeniusWeightStore