Changes in version 0.99.0 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. Renamed functions - 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. Changes that can alter results - Functions with a 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. - PLS fits (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. - With a "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. New features - Two package vignettes run on small Bioconductor datasets: 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. - New 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. - New 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. - A 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. - A model trained with 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). - A 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. - New vignette "Pretrain PhiSpace models: train once, annotate later". It trains models on the four whole-transcriptome lung references of the CosMx vignette with 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. Memory and speed - 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 " 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. Bug fixes - 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. Progress messages - 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. Other changes - The package moved to its own repository, 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. - The package now cites the published papers: Mao et al. (2025), Genome Biology 26, 323 (doi:10.1186/s13059-025-03755-8), and Mao et al. (2026), Cell Reports Methods 6(8), 101483 (doi:10.1016/j.crmeth.2026.101483). 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. - The License field changed from AGPL (>= 3) to AGPL-3. - Raw scores from 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. - The PhiSpaceR_1ref() help page now documents every value that the scoreCells fallback changes. scoreCells() - 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. - Runs now record whether signatures were generated or supplied and which effective genes were used for correlation scoring.