The version number drops from 1.1.0.9000 to 0.99.0 because Bioconductor requires new submissions to use 0.99.z. The changes below are relative to PhiSpace 1.1.0, the release archived on Zenodo.
getClass() is now getLabels(). The old name masked
methods::getClass() when PhiSpace was attached. Replace getClass( with
getLabels( in existing scripts; the arguments and results are unchanged.seed argument (findNiches(), clusterPhiSpace(),
rankFeatures(), spatialSampler(), subsample(), pseudoBulk() and
tunePhiSpace()) now set the seed with withr::local_seed() and restore
the session's random-number state on exit. Their own results are
unchanged, but random numbers drawn after the call differ from before,
when the call left the generator in the state given by its seed.getPC() now computes totVar, props and accuProps from the centred
(and, if requested, scaled) data that it decomposes. Previous versions divided
by the total variance of the raw data, so the proportions were too small
whenever column means were not zero. Scores and loadings are unchanged. The
corrected proportions also change the variance_explained value that
findNiches() stores in its metadata.clusterPhiSpace() now computes its principal components with a full
singular value decomposition instead of irlba. irlba could return inaccurate
trailing components, and the default ncomp (up to 30 components) could
include them in the clustering. Clusters can therefore differ from previous
versions, mainly for score matrices with few columns.mvr(method = "PLS"), and therefore PhiSpace(),
PhiSpaceR_1ref() and tunePhiSpace()) now find each weight vector with an
exact eigendecomposition (eigen()) instead of irlba::partial_eigen().
irlba stopped at a tolerance of about 1e-5 and used a random start, so
coefficients differed from pls::kernelpls.fit by up to about 2e-5
(relative) and varied with the random seed. They now agree with
pls::kernelpls.fit to about 1e-14 and do not depend on the seed. Scores
change by a similarly small amount."rank" assay, PhiSpace(), PhiSpaceR_1ref() and
tunePhiSpace() rank the selected features again after feature selection.
This second ranking now ranks the genes within each cell, as
RankTransf() does. Previous versions ranked each gene across cells, so a
cell's ranked values depended on the other cells in the same reference or
query. Scores computed from a "rank" assay change.mvr(method = "PCA") now uses a full singular value decomposition when
ncomp is at least half of the smaller dimension of X, where irlba can be
inaccurate.getPC() (and therefore findNiches() and computeUMAP()) also uses a
full singular value decomposition when ncomp is at least half of the
smaller dimension of X. irlba warned "You're computing too large a
percentage of total singular values" in these cases, for example in the
CosMx and CITE-seq vignettes. In tests, the results agreed with irlba to
about 1e-14, apart from the signs of components. The CosMx niches and the
CITE-seq result checks are unchanged.getPC() also uses the full singular value decomposition when the smaller
dimension of X is at most 100, as for PhiSpace score matrices with one
column per cell type. On a 2,702 x 7 score matrix with 3 components, irlba
did not converge, and its principal component scores changed with the
session's random state by up to 0.17 (scores of about 1 in size). The
niches of findNiches() therefore changed between runs, although it takes
a seed: it sets the seed after the PCA. Where irlba converged, results
are unchanged apart from rounding and the signs of components.vignette("PhiSpace") annotates single-cell RNA-seq of mouse blood stem
and progenitor cells (scRNAseq) with bulk ImmGen samples (celldex), and
vignette("PhiSpaceST") annotates a 10x Visium section of the mouse brain
(STexampleData) with single-cell RNA-seq of the cortex and hippocampus,
and finds niches. The eight larger case studies are website articles in
vignettes/articles/ and are not part of the package.tunePhiSpace() runs in non-interactive sessions. New arguments ncomp
(when tune_ncomp = TRUE) and nfeat give the values to use after the
cross-validation. If they are not given, an interactive session plots the
loss and asks, as before. A non-interactive session chooses the ncomp
with the lowest loss and needs nfeat when tune_nfeat = TRUE. Previous
versions always asked with readline(), which fails in a non-interactive
session, and ignored ncomp when tune_ncomp = TRUE. The loss plots are
now drawn only in interactive sessions.logNormTransf() divides counts by size factors (by default the
library sizes, centred to mean 1) and takes log2(x + 1). Its values are
identical to those of scuttle::logNormCounts() with default arguments,
which Bioconductor 3.24 deprecates. scranTransf() now uses it; its values
are unchanged, but a sparse or DelayedArray count matrix now gives a
dgCMatrix assay.trainPhiSpace() fits a PhiSpace model on a reference and returns it as
a PhiSpaceModel object, which can be saved and applied later to queries
with predict(), without the reference cells. The model records the
training assay, the PhiSpace version and, optionally, the reference name,
species and gene ID type. PhiSpaceR_1ref() now uses the same code and
also returns its model (model); its scores are unchanged.PhiSpaceModel also stores the cross-products X'X and X'Y of the centred
(and scaled) reference over its genes (keepStats = TRUE, not for the
"rank" assay). When a query lacks some model genes, for example a targeted
spatial panel, predict() refits the model exactly on the genes that the
query has, without the reference cells; the result equals a model trained
with selectedFeat set to those genes (to about 1e-14 in the tests). Models
without these statistics keep the coefficients of the shared genes and warn
that the scores are approximate. X'X takes 8 bytes per pair of genes:
72 MB for 3,000 genes, 3.2 GB for 20,000 genes.nfeat selects its genes from a fit on all reference
genes, whereas PhiSpace() selects them among the genes shared with the
query. Such a model therefore stores the statistics of the top nfeatPool
genes per phenotype (default 2,000). If a query lacks some model genes,
predict() selects nfeat genes per phenotype again among these
candidate genes, from a refit on the candidates that the query has, and
refits the model on them. predict(nfeat = ) selects another number of
genes. To select genes among those of a known query platform, train with
genes.predict() and PhiSpace() check that a query looks preprocessed like
the model's reference. The model records a summary of the reference
values over its genes (valueProfile), and a warning is given if the
query's non-zero values are integers and the reference's are not (or the
reverse), if only one of them has negative values, if their 99th
percentiles differ by more than a factor of 10, or if only one of the
model and the query assay is "rank". On the PerturbSeq and CosMx data,
raw counts and centred and scaled values were detected; the five
vignette queries gave no warning. trainPhiSpace() gains a
preprocessing description, printed with the model. When a query shares
no gene with a model, the error says whether one side uses Ensembl IDs
or whether the genes match when case is ignored (another species).PhiSpaceModel trained with phenotypes records the number of
reference cells in each label (classSizes). print() shows, for each
phenotype, the number of labels and the smallest and largest label with
their cell counts. Labels with few cells give less reliable scores.PhiSpace() accepts a PhiSpaceModel as the reference, or a list that
mixes models and reference objects. A model is restricted once to the genes
shared by all queries, as above; updateRef = TRUE is not available for a
model.nfeat = 500, applies them to the 960-gene CosMx panel, and
compares them with the per-query fit: median correlation per cell type 0.998
to 0.9995, the same top cell type for 96% to 97% of cells per model, and
niches with an adjusted Rand index of 0.97. Models trained on the panel
genes give the same scores as the per-query fit.PhiSpace() scores a query with all PhiSpaceModel references in one
pass: the query matrix is multiplied once by the coefficients of all
models, without selecting the model genes from the query or transposing
it. predict() uses the same computation for one model. Models on the
"rank" assay, and queries that are neither a sparse Matrix nor a base
matrix, are scored one model at a time, as before. Scores agree with the
previous computation to about 1e-15. With the four CosMx lung models
(98,002 cells, 960 genes), PhiSpace() took 1.1 s instead of 3.6 s. With
eight synthetic models (300,000 cells, 20,000 genes, 40 cell types per
model), it took 48 s instead of 69 s, with the same peak R memory
(9.4 GB). With one model and a whole-transcriptome query (PerturbSeq,
23,384 genes), the time was unchanged and peak R memory fell from 940 MB
to 366 MB.
mvr() and phenotype() no longer convert a sparse predictor matrix to a
dense one. Centring and scaling are applied implicitly, inside the matrix
products. On a simulated 20,000 x 8,000 sparse matrix (10% non-zero, 20
responses), peak R memory (including the 185 MB input) fell from 3,574 MB
to 906 MB for the PLS fit and from 4,646 MB to 988 MB for prediction; run
times fell from 9.3 s to 0.9 s and from 4.0 s to 0.4 s. In fresh vignette
builds, peak memory fell from 17-24 GB to 7.2 GB for BridgeAnnotation and
from 64.9 GB to 16.1 GB for CITE-seq.
mvr() gains a keepComps argument that selects the numbers of components
whose coefficients are returned. The default, 1:ncomp, returns all of them
as before. PhiSpace(), PhiSpaceR_1ref(), tunePhiSpace() and
rankFeatures() keep only the final coefficients. As a result, the
atlas_re$reg_re$coefficients array returned by PhiSpaceR_1ref() now has
one slice, named "<ncomp> comps"; select it by that name, not by
position. phenotype() accepts both forms, so objects saved by earlier
versions still work.
getPC(scale = TRUE) computes the column standard deviations of a sparse
matrix without converting it to a dense one. On a simulated 50,000 x 2,000
sparse matrix (5% non-zero, 57 MB), the memory used above the R session
baseline fell from 2,302 MB to 261 MB, and the run time from 8.8 s to
6.2 s. Results agree to about 1e-14.
RankTransf() no longer converts a sparse count matrix with no negative
values to a dense one. The ranks are unchanged.
PhiSpaceR_1ref() (and therefore PhiSpace()) no longer subsets the
reference and query objects to their shared genes, which copied every
assay. It extracts only the assay it uses and keeps the selected genes
before it transposes the matrix. Results are unchanged. On simulated data
(3.3 GB of input: a 10,000-cell reference and a 40,000-cell query, each
with three assays), peak R memory fell from 7.4 GB to 3.7 GB.
spatialSmoother() smooths an assay with one sparse cell-by-cell weight
matrix instead of a loop over cells, and pseudoBulk() aggregates with a
sparse indicator matrix instead of a loop over pseudo-bulks.
scoreCells() computes correlation scores for blocks of cells at once and
no longer holds the class's whole query matrix as a dense matrix. Results
agree with the previous versions to about 2e-15. On simulated data (5,000
genes, 30,000 cells), smoothing an assay took 8 s instead of 332 s,
pseudoBulk() 0.7 s instead of 22 s, and Spearman correlation scoring
(25,000 query cells, 20 classes) 5 s instead of 26 s.
clusterPhiSpace() no longer stores a cells x cells distance matrix to
compute silhouette widths (select_k_method = "silhouette"). The
distances from blocks of cells to all cells are computed and summed by
cluster, so memory grows linearly with the number of cells; previously the
matrix was rebuilt for each k and cluster::silhouette() copied it. The
widths agree with cluster::silhouette() to about 1e-16. With 30,000
cells and five values of k, peak R memory fell from 10.9 GB to 0.8 GB; the
run time rose from 45 s to 81 s. cluster moved from Imports to Suggests.
Two optional limits for large inputs. Both are off by default:
clusterPhiSpace() gains silhouette_max_cells. If set, silhouette
widths are computed on a random subsample of that many cells; k-means
still clusters every cell.rankFeatures(method = "DWD") accepts dwd_params$max_cells. If set,
a fit with a non-linear kernel stops before it builds a cells x cells
kernel matrix for more cells than this.rankFeatures() with method = "PLSDA" or method = "PLS" returned
scores = NULL, because it did not ask the PLS fit for the component
scores. It now returns them as an observations x ncomp matrix. Importance
scores and feature rankings are unchanged.
rankFeatures(method = "DWD") with the default linear kernel failed with
"non-conformable arguments" whenever there were at least as many cells as
genes. kerndwd then returns one coefficient per gene, which
rankFeatures() treated as one weight per cell. These coefficients are
now the importance scores, and the DWD scores are computed from them.
Fits with fewer cells than genes, or with a non-linear kernel, are
unchanged.
pseudoBulk() drew the pseudo-bulks of a cluster with only one cell from
the wrong cells: for the cell at position i, sample() drew from cells 1
to i. It now draws from that cell only. Clusters with two or more cells
give the same pseudo-bulks as before, for the same seed.
print(), summary() and plot() for clusterPhiSpace() results now read
the variance proportions that getPC() returns. Previously print()
reported 0% variance explained, summary() and plot(type = "variance")
failed, and the PCA plot axis labels showed no percentages.
scranTransf() replaced non-positive size factors with an arbitrary
positive size factor instead of the smallest one: it took a position in
the sorted size factors and read that position in the unsorted ones. It
now uses the smallest positive size factor. Data with only positive size
factors give the same result as before.
findNiches(), zeroFeatQC(), spatialSampler() and spatialSmoother()
now report progress with message() instead of cat(), so
suppressMessages() and the knitr chunk option message = FALSE silence
them. findNiches(), zeroFeatQC() and spatialSampler() gain a
verbose argument (default TRUE); spatialSmoother() already had one.findNiches() reports niche sizes on one line instead of printing a table.github.com/mao-lab-org/PhiSpace,
with the package at the root. Install it with
BiocManager::install("mao-lab-org/PhiSpace"). The website moved to
https://mao-lab-org.github.io/PhiSpace/; the old address redirects there.
jiadongm/PhiSpace keeps the papers' analysis code, and its pkg/ folder
is no longer updated.citation("PhiSpace") returns both. The README files, DESCRIPTION, the
PhiSpace() and PhiSpaceR_1ref() help pages and the vignettes no longer
link to the bioRxiv preprints.mvr(method = "PCA") no longer hides irlba's warnings. With the full
singular value decomposition used for large ncomp, the only warning left
is that irlba did not converge.License field changed from AGPL (>= 3) to AGPL-3.phenotype() (for example PhiSpaceScore and YrefHat)
no longer carry a "scaled:center" attribute.scuttle moved from Imports to Suggests: the package code no longer
calls it, and the examples and tests use scuttle::mockSCE().pls is now a suggested package; the unit tests compare mvr() with
pls::kernelpls.fit.PhiSpaceR_1ref() help page now documents every value that the
scoreCells fallback changes.scoreCells() now selects signature genes by mean expression by default
(signature_method = "mean"). The previous default, "findMarkers",
calls scran::findMarkers(), which Bioconductor 3.24 deprecates. Results
change for multi-class references unless signature_method = "findMarkers"
is given; this includes the PhiSpace() fallback when
fallback_min_classes is above 2.scoreCells() now calculates each class's correlation score using that
class's effective signature genes. Previous versions used every gene shared
by the reference and query.scoreCells() gains a signature_genes argument for supplying a character
vector to a one-class reference or an exactly named list to a multi-class
reference.