PolyGenius
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) <- value

S3 method for class 'PGSLibrary'

x[[i, ...]]

S3 method for class 'PGSLibrary'

x[i, j, k, ...]

Arguments

ArgumentDescription
xA 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.
nameCharacter scalar, or NULL (default). Name for the resulting library.
recursiveLogical scalar, default FALSE. Ignored; present for compatibility with the base c() generic.
valueCharacter vector of new model names, exactly one per model, each non-NA and non-empty after trimming whitespace. Any other value aborts.
iModel 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().
jCharacter vector of variant column names, or an unquoted tidyselect expression, or missing. Passed to select.variants().
kUnquoted 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, so x[-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

Aliases: PGSLibrary, print.PGSLibraryFetchTbl, PGSLibrary.class, c.PGS, c.PGSLibrary, length.PGSLibrary, names.PGSLibrary, names<-.PGSLibrary, [[.PGSLibrary, [.PGSLibrary