NEWS
spicyR 1.99.47
- radiusPlot()'s example compares two stages of diabetesData, as spicy() with several radii needs two
conditions.
spicyR 1.99.46
- signifPlot()'s help page describes what the heatmap shows (#38).
spicyR 1.99.45
- spicy(method = "image") with a survival outcome: a pair whose Cox model (coxph, or coxme with a subject)
cannot be fitted is NA, with a warning naming such pairs, and the other pairs are returned (#84).
- spicy(method = "image") stops with an explanation when the condition or the subject varies within an image,
as when an image ID such as ROI_1 is used in several patients (#79). topPairs() of an image-method result in
which no pair could be tested says so.
- getPairwise() and spicy(method = "image") with 'sigma' or a concave window: an image whose cells span no area
(a single cell, or with a convex window cells on a line) gives NA for its pairs instead of stopping, as it
does without 'sigma' (#78).
- getPairwise() reads a data.frame by the 'imageID', 'cellType' and 'spatialCoords' given, as it reads a
SingleCellExperiment (#64).
- getPairwise() with 'cellWeights' matches the weights to the cells of a SingleCellExperiment whose cells are not
ordered by image.
spicyR 1.99.44
- topPairs() shows 'side' only with 'full = TRUE': the sign of the effects gives the direction. The vignette
describes only the columns topPairs() shows by default.
spicyR 1.99.43
- Vignette: the SpatialData object read from the store is no longer printed (it listed every image's points).
spicyR 1.99.42
- topPairs() takes ‘from' and 'to' to choose pairs, and 'full = TRUE' for every column of the test. For the
cell method it reports the test’s own columns (from, to, side, excess_ref, excess_comp, excess_difference,
p_value, p_adj, with r, the covariate effects or hazard_ratio_sd when present) instead of intercept,
coefficient, p.value and adj.pvalue, which remain for the image method. With 'cutoff' and no 'n' it lists
every pair below the cutoff, not only the first 10. The vignette no longer reads $cellResults.
- bind(results, from =, to =) gives one pair's table: each image's effect and its weight in the test. bind()
names its rows by image, so images can be looked up by ID.
- plotImage() draws several images side by side, on the same scale.
- radiusPlot(): the p-value of chosen pairs at each radius, for a result with several radii.
- calibrationCheck() results have summary() (the average percentage over shuffles at each threshold, with the
expected one) and plot().
- print() of a result gives the patients (or images) in each group.
spicyR 1.99.41
- Image method: a pair whose summary is the same in every image (e.g. a cell type absent from every image) is
not tested; its p-value is NA, so it is left out of the multiple-testing correction, and print() reports how
many pairs were not tested. Its fit had a standard error of about 1e-16, so its p-value came from rounding
error and could fall anywhere, below 0.05 included. The cell method already reports such pairs as
"none (no_variation)" with p-value NA.
spicyR 1.99.40
- Image method: the built-in weight model follows scam()'s smoothing-parameter search exactly. Its GCV gradient
is now analytic, as scam's is (with A^{-1} in place of H^{-1} when H is not positive definite, as scam does).
It was by central differences, which near the flat GCV minimum are dominated by the inner fit's tolerance, so
the BFGS search (and its finite-difference starting Hessian) took a different path from scam's and stopped
elsewhere within tolerance. The weights moved by up to 0.004 on the TB granuloma MIBI data and the p-values
by up to 3e-4; on METABRIC (Jackson) the weight model's predictions differed from scam's by 0.11. On 80 real
analyses the weights now match scam()'s to 5e-8 on 78, to 4e-6 on one (from the start of the search), and
the 80th falls back to scam() as before. The fit is also faster: no refits for the differences.
spicyR 1.99.39
- The website's reference index is built from roxygen tags: each exported topic carries an @concept (test, plot,
image-method, store or data) and each section of _pkgdown.yml takes has_concept(), so a new function joins its
section through its own documentation. The sections hold the same topics as before, in alphabetical order.
spicyR 1.99.38
- colTest() takes ‘subject': with a SingleCellExperiment or SpatialExperiment, the proportions are then those of
each patient’s cells, all images pooled, and each patient is one unit of the test (the same as passing the
patient column as ‘imageID', which lisaClust’s vignette did).
- colTest() with type = "wilcox" reports the median in each group ("median in group level"), as the t-test
reports the means, so group summaries need not be computed separately.
- colTest() matches the rows of a matrix or data.frame to a condition whose names (or row names) are row names
of 'df', and leaves out rows without a condition; a condition without such names is taken in row order, as
before.
spicyR 1.99.37
- calibrationCheck() shuffles the condition across patients (or images), runs spicy() on each shuffle and
reports the percentage of pairs below each p-value threshold, the check the vignette recommends before trusting
a discovery. Patients without a condition keep it missing. It replaces the shuffle loop the vignette had.
- The vignette is simpler: imageID and cellType are set once (noted as optional), the ER status is used as it is
(neg is the reference), the example cores are named rather
than computed, droplevels() and plot cosmetics are gone, and the store section writes the whole data set and
compares with the results in memory.
spicyR 1.99.36
- Comments only: R/writeSpatialData.R names the upstream issue behind each workaround in the SpatialData writer
(Rarr #260 and #261, spatialdataR #115 and #287), so each can be removed once its fix is released.
spicyR 1.99.35
- writeCellStore() writes the cells of each image as a points element named after the image (x, y and the
instance ID that links each point to its row of the table), so the table annotates elements of the store and
Python's spatialdata no longer warns that they are missing. A SpatialExperiment's points are in the coordinate
system of their sample, with its images; a data.frame's in the image's own. Each chunk of rows is written as a
parquet file with duckdb and one duckdb pass splits them into one file per image, so memory stays that of a
chunk however the cells are ordered (spatialdataR reads only the first parquet file of a points element).
Image IDs that are not valid SpatialData element names give a warning and no points; 'points = FALSE' skips
them. duckdb and DBI are suggested.
spicyR 1.99.34
- writeCellStore() writes a whole SpatialExperiment or SingleCellExperiment as a SpatialData store: the first
assay as X and the others as layers (a sparse assay as a csr_matrix), the rowData as var, the column names as
the obs index, the reducedDims in obsm with the coordinates, and the images of imgData() as OME-NGFF image
elements named sample_id_image_id, each in its sample's coordinate system and scaled by 1 / scaleFactor.
spatialdataR and Python's spatialdata read every part back as written (new tests). metadata(), altExps() and
column or row pairings are not written.
- The writer is in its own file (R/writeSpatialData.R), written in the idiom of spatialdataR's draft writers so
that it can move there. Numbers and logicals are now written with Rarr (create_empty_zarr_array() and
update_zarr_array(), a chunk at a time); strings are still written by spicyR, in the vlen-utf8 encoding that
Rarr does not write. Writing 20 million cells takes 16.6 s (13.2 s before) with the same memory and the same
store size, and spicy() results from the stores are unchanged.
spicyR 1.99.33
- writeCellStore() takes a SpatialExperiment or SingleCellExperiment as well as a data.frame: the colData columns
become the cell table's obs and a SpatialExperiment's spatialCoords() its obsm "spatial" (assays are not
written). The store is byte for byte the one written from the same cells as a data.frame. imageInfo may give
the image IDs in its imageID column, as spicy() accepts, or else in its row names.
- plotImage() without 'from' and 'to' shows every cell of the image, coloured by its cell type, with the same
colour for a cell type in every image of the object.
- The vignette's store section works on the SpatialExperiment instead of a data.frame, and its first figure uses
plotImage().
spicyR 1.99.32
- Cell weights: spicy(), kontextualEngine() and getPairwise() take cellWeights, either a vector with one weight
in [0, 1] per cell for its own cell type (for example a quality score) or a cells x labels matrix (base or
sparse) of soft cell type membership, for example a classifier's confidence. With a matrix, from and to name
its columns, and labels may overlap (a group and its subtypes).
- Cell method: a to cell counts with its weight; its score is the chance that it has a from neighbour within r
if the labels were drawn from the weights (allocation), or the summed from weight of its neighbours (count).
The null shuffles the to weights over the cells whose from weight is below 1; its exact mean and variance
replace those of random labelling, and with 0/1 weights the results are those of the cell types (to 1e-14).
One radius, cells in memory; pairs of a label with itself are not tested.
- Kontextual: the from weights enter the parent-density scores and the to weights the observed sum, with the
same null; the parent is still a set of cell types. 0/1 weights give the cell-type results (to 1e-14).
- Image method: the per-image L function counts each pair with the product of the two cells' weights, and a
label's density is its summed weight; 0/1 weights give getPairwise()'s values exactly.
- New core functions (shared with spicyr): Dataset::weighted_excess_sums(),
Dataset::kontextual_sums_weighted(), weighted_image_rows() and kontextual_image_rows_weighted().
spicyR 1.99.31
- kontextualEngine() (the engine of Statial::kontextualTest()) now has labelClustering = FALSE by default: the
image variances are no longer multiplied by the label-clustering factor. The option is kept. This matches
spicy(), where the factor has been off since 1.99.8.
- Why: with the factor, most of the weight goes to a few images. When the conditions were shuffled between
patients (5 cohorts, 30 shuffles each), a few triples came up far more often than chance with the factor on;
without it every cohort stayed at or below the nominal level (3.0-4.4% of p-values below 0.05) and gave
no shuffle with a BH discovery (1 of 150 with the factor). It also gives more discoveries on the real labels
(Cords 83 -> 94, Vannan 121 -> 161 triples).
spicyR 1.99.30
- A radius is now not tested when a condition's effective patients fall below min(3, m_g / 2), where m_g is the
number of that condition's patients that contribute at that radius; before, the bound was 3. For conditions of
6 or more patients nothing changes. The rule applies wherever the old one did: spicy() (method = "cell", both
effects, with or without adjustment, Hartung-Knapp, and each level with more than two conditions) and
kontextualEngine(). The label stays "few_effective_units".
- Small studies get most of their tests back. Over the small real-data analyses, the radii of the grid that are
tested rise from 78% to 95%, and a condition of 3 patients can now be tested (none could before). In
diabetesData (4 patients per stage), 167 pairs are tested for the allocation effect (110 before) and 167 for the
count effect (133 before). The kontextualEngine() example uses alpha cells again.
spicyR 1.99.29
- spicy() (method = "cell", both effects) and kontextualEngine() no longer test a radius where a condition has
fewer than 3 effective patients: Kish's (sum w)^2 / sum w^2 of the patients' weights in that condition. A
patient's weight grows with the information of its images, and when the patients do not differ (frailty
variance 0) nothing caps it, so one patient with many cells can carry nearly all of a condition's weight. Such a
radius has p-value NA (and the unadjusted one; with adjustment, adjusted_for is "none (few_effective_units)")
and is left out of the combination over radii (davies, maxT, cauchy), as a radius with no informative patient
is. With more than two conditions, a pair where any condition has fewer than 3 is not tested. A condition with
fewer than 3 patients is never tested, and one with 3 or 4 rarely is.
- The test now shares one dispersion between the conditions. Each condition's CR2 variance is divided by its
model-based variance, the ratios are averaged with the conditions' numbers of patients as weights, and the
model-based variance of the difference is multiplied by this average; the Satterthwaite df are unchanged.
Before, each condition's CR2 variance entered on its own, so with conditions of unequal size and skewed patient
effects a small condition's low spread set the standard error and the test gave too many small p-values. With
adjustment, the ratios are those of each condition's coefficient in the adjusted design, and every coefficient's
variance (also the reported effect of each adjustment) is scaled by their average. variance = "hartung_knapp" is
unchanged.
- Images with a missing condition are now also left out of the choice of an allocation pair's side and of the
between-image variance (tau2_image), so the result equals the one with those images removed, also with
adjustment and several images per patient. Their per-image effect and weight (pairwiseAssoc, imageWeights) are
NA.
- Survival is unchanged.
- Calibration, on 11 real-data analyses with 30 relabellings of the conditions between patients each (the two
worst-calibrated, sorin and cords, and 9 others), as the share of relabellings with any BH-significant pair for
the combination over 25 radii (davies). The allocation effect: from 37% to 3.3% in sorin (404 patients, 340
against 64), from 23% to 3.3% in cords (one patient carried 99% of a condition's weight), and from 5.2% to 3.0%
on average over the other 9; for one radius, from 17% to 3.3% in sorin and from 17% to 6.7% in cords. The count
effect: from 33% to 3.3% in sorin, from 27% to 0% in cords and from 4.1% to 3.0% over the other 9; for one
radius, from 20% to 3.3%, from 17% to 0% and from 5.9% to 2.6%. On the 12 worst-calibrated of the paper's
analyses, the any-BH rate falls from 10.8% to 1.7% for the allocation effect (one radius: 5.0% to 2.5%) and
from 10.0% to 0.8% for the count effect (one radius: 5.6% to 1.1%).
- The pooled dispersion costs power where the conditions' dispersions differ. With the true labels, BH-significant
pairs over the 11 analyses go from 535 to 510 for the allocation effect (vannan 132 to 72, while karimi goes from
68 to 90 and jackson from 7 to 15) and from 641 to 546 for the count effect (vannan 224 to 67, while karimi goes
from 61 to 84 and metabric from 60 to 71).
- Results change at every condition test (the standard error, so the p-value; the estimates are unchanged) and
wherever images lack a condition. On the reference configurations, a radius's p-value moves by a median of 1e-4
to 0.05 depending on the data set, and a combined p-value by at most 0.66. diabetesData, with 4 patients in each
of its three stages, no longer tests 69 of its 179 allocation pairs at r = 50, and kontextualEngine() tests
neither triple alpha -> Tc, Th within T cells there (the example now uses delta cells). Survival is identical()
to 1.99.28's.
spicyR 1.99.28
- spicy() (method = "cell") leaves images whose condition is missing out of the unadjusted test. Before, the test
of two conditions counted them in the second condition: its excess, the difference, the standard error, the df,
the p-value and the frailty variance all included them. This affected the test without adjustment and the
unadjusted_* columns of an adjusted test. The adjusted design already left these images out, so its results are
unchanged, except where its frailty variance is held at the unadjusted value (an added column that varies
between the images of a patient, such as adjustAbundance with several images per patient). There the held
variance is now that of the images with a condition, and the adjusted test changes with it (#83).
- With more than two conditions, an image with a missing condition stopped every pair from being tested ("no pair
could be tested"). These images are now left out of the test, as with two conditions.
- The test now equals the one with these images removed, except for two steps that use every image: the side of
an allocation pair (attraction or avoidance), and the between-image variance (tau2_image) when patients have
several images.
- Results with no missing condition are unchanged: every reference configuration is identical() to 1.99.27's.
spicyR 1.99.27
- spicy() (method = "cell", effect = "allocation") no longer tests a radius where one condition has no
informative patient. The allocation effect is bounded: an image of an avoiding pair with no ‘to' cell next to a
'from' cell has the effect -1 exactly, and an image of an attracting pair with every 'to' cell next to one has
+1. A patient is informative at a radius when at least one of its images is not at -1 or +1. When every patient
of a condition is at the bound, that condition’s CR2 variance is exactly 0, the standard error rests on the few
informative patients of the other condition, and the Satterthwaite df stay at 9-14: on relabelled real data such
tests reached t of -44 to -63. Such a radius now has p-value NA (and the unadjusted one; with adjustment,
adjusted_for is "none (no_information)"), and it is left out of the combination over radii (davies, maxT,
cauchy). The rule is the same for one radius, for adjusted and unadjusted tests, and with more than two
conditions, where a pair with any condition without an informative patient is not tested. The printed summary
counts the pairs not tested.
- Calibration, on the 80 real-data analyses of the paper with 30 relabellings of the conditions between
patients each: the share of relabellings with any BH-significant pair falls from 9.8% to 3.4% for the
combination over 25 radii (davies) and from 3.5% to 2.8% for one radius; the rate of combined null p-values
below 1e-6 falls from 1.5e-5 to 2.5e-7. With the true labels, BH-significant pairs fall from 781 to 711. On 162
synthetic data sets with known truth, power is unchanged and the false-positive rate is lower.
- Results change only at such radii and in the combined p-values that included them. Of the reference
configurations, the 25-radius synthetic data with 40 types change at 156 of 40000 radius tests (all at the
smallest radius, avoiding pairs) and in the combined p-value of 131 of 1600 pairs, and diabetesData with its
three stages (r = 50) no longer tests 14 of 193 pairs; every other configuration, every count-effect result
(the count effect has no bound) and Kontextual are identical() to 1.99.26's. Survival is unchanged: its score
test has no condition groups, and a response with no variation at all is already not tested.
spicyR 1.99.26
- spicy() (method = "cell") with adjustAbundance or covariates no longer fits the adjusted design at a radius
where the pair's response has no variation, as where no ‘to' cell has a 'from' neighbour (small r) or every
one has (saturated, large r). There the unadjusted test is exactly 0 / 0 and its radius is left out; the
adjusted design turned those exact zeros into rounding noise (estimates and standard errors of about 1e-16),
so the radius was kept with an arbitrary t and influence vector. That noise moved the combined p-values
(davies, maxT, cauchy) arbitrarily: with age perturbed by a relative 1e-10, 21-28% of the adjusted combined
p-values on the Jackson, Keren and Schurch data moved by more than 0.01, and single-radius adjusted tests at
such radii gave noise p-values. Such a radius is now not tested: adjusted_for is "none (no_variation)", its
p-value is NA, and nothing of it enters the combination, which equals that with the radius left out. The same
perturbation now moves no combined p-value by more than 4e-9. Adjusted p-values at no-variation radii, and the
combined p-values that included them, change: 162 of 576 pairs on Jackson (25 radii), 55 of 256 on Keren and 85
of 324 on Schurch, none crossing 0.05; for t04 -> t05 of the 10-type synthetic data (r = 5, 10, ..., 125, age
and abundance, davies) the combined p is 0.772 (0.967 before). Unadjusted results, and adjusted ones at radii
with variation, are identical() to 1.99.25’s.
- The core's design test (design_tests()) reports reason "no_variation" when the design fits the response
exactly: a weighted residual sum of squares at most (64 eps n)^2 times the weighted sum of squares of the
response. This covers the paths that do not go through the unadjusted test first (more than two conditions,
survival, Hartung-Knapp). A survival test is adjusted for abundance only where the unadjusted one was done.
- With several radii and something to adjust for, a radius where no pair's design was fitted has the same effect
columns as the others (NA); before, combining the radii stopped with an error.
spicyR 1.99.25
- spicy(combine = "maxT") computes the max-T p-value, the probability that a multivariate normal vector with the
radii's sandwich correlation leaves the cube [-c, c]^K, by the quasi-Monte Carlo method of Genz (1992) and
Genz & Bretz (2009), with an absolute error target of 1e-4, in place of recursive Gauss-Legendre quadrature.
The variables are separated on the Cholesky factor of the correlation, reordered so the narrowest expected
interval comes first, and integrated by Korobov lattice rules of growing size (the baker's transformation,
antithetic points, 12 random shifts) until 3.5 standard errors are below 1e-4, or 100,000 points per shift.
The shifts come from a fixed seed, so the same inputs give the same p-value on every call, at any number of
cores. The method and its lattice parameters are written from the publications (inst/scripts/
korobov_search.cpp finds the parameters); no code is taken from mvtnorm. Two radii keep the quadrature,
which is accurate to about 1e-10 there; the quadrature itself remains in the core
(mvn_outside_quadrature()) for comparison.
The quadrature took 3.2 million evaluations per pair with six radii; the new method takes about 26,000 (18 ms
against 233 ms per pair). On one core, for 50 images of 5,000 cells with r = c(5, 10, 20, 30, 40, 50), 80 cell
types take 141 s (1,438 s before), 40 types 32 s (381 s), 20 types 8.4 s (90 s), 10 types 2.1 s (23 s) and
5 types 0.6 s (5.6 s).
Against mvtnorm::pmvnorm() at an absolute error of 1e-6, on the 100 tests of 10 cell types the new p-values
differ by at most 6.1e-5 (0.44% relative at p < 0.05), and on 308 random and singular correlation matrices
with three to six radii by at most 1.0e-4 (4.8% relative for 0.001 <= p < 0.05); pmvnorm() at 1e-4 differs
from the same reference by up to 8.3e-4. On the timing data (4,200 pairs at 10, 20 and 40 types, allocation and
count), 6 p-values change by more than 1e-4 (at most 1.4e-4) and none crosses 0.05; the other columns are
unchanged, and results without maxT are identical() to 1.99.24's.
spicyR 1.99.24
- spicy() (method = "cell", two conditions) tests every pair at every radius in one call to the C++ core
(src/core/src/pairs.cpp, pair_tests()): the image rows, the image random effect, the frailty test, the
adjusted design and, with several radii, the max-T, Davies or Cauchy combination. Before, each (pair, radius)
test went back to R, which turned the core's results into data.frames one row at a time; that bookkeeping,
not the arithmetic, took over 95% of the time. The tests, degrees of freedom, variances and combinations are
unchanged and the results are identical() to 1.99.23's. On one core, for 50 images of 5,000 cells: with
r = seq_len(25) * 2 and combine = "davies", 80 cell types (6,320 pairs) take 8.6 s (263 s before), 40 types
3.1 s (62 s), 20 types 1.4 s (15 s) and 10 types 0.9 s (4.8 s); with r = 25, 80 types take 0.6 s (10.9 s)
and 40 types 0.3 s (2.5 s). Ten million cells with r = 25 and 10 types take 9.3 s, as before. With
‘cores' > 1 the pairs are also tested on that many threads, with identical results. More than two conditions,
survival outcomes and Kontextual keep their per-pair path, with the core’s image rows now returned as
data.frames without a call to as.data.frame().
- writeCellStore() writes the image column as a categorical always (a character column with the images in
order of first appearance, a factor with its own levels), so Python's spatialdata.read_zarr() reads stores
made from a data.frame with character image IDs (it requires a categorical region key). The instance IDs are
written in 16 bits at least, which spatialdata also requires. In a column with missing values, the mask and
the numeric or logical values now carry the "array" encoding attributes, as anndata writes them, so anndata
no longer gives an OldFormatWarning on R stores. spicy() results from these stores are identical to those before.
spicyR 1.99.23
- writeCellStore() writes the store itself, a chunk of 2^20 rows at a time, in place of one anndataR call that
wrote every array as a single chunk. It failed at 100 million cells (on the row index), and reading any slice
of a store it wrote decompressed the whole column. Every column, the row index and obsm "spatial" (float64,
as the cells give it) are now in chunks of 2^20 rows compressed with zstd, so spicy() reads a store a block at
a time. The layout is the AnnData encoding as before, with the same arrays, data types and attributes, and
spicy() results are identical to those from the old stores and from the cells in memory. Writing 100 million
cells takes 81 s and about 0.4 GB beyond the data.frame, and spicy(r = 25) from that store peaks at 1.8 GB on
one core and 5.2 GB on 8 (8.8 GB and 15.6 GB from the store written as single chunks). The region and instance keys in uns are now string
scalars and the per-image table carries the SpatialData table attributes, so Python's spatialdata reads the
store. anndataR is no longer suggested.
spicyR 1.99.22
- spicy() (method = "cell"): images whose condition is missing are now left out of the covariate design, as
they already were of the unadjusted test. Before, any such image made the adjusted design rank deficient, so
the covariate adjustment was dropped ('adjusted_for' "none (design_not_full_rank)") and the unadjusted test
reported, for some pairs or, from a store, for all of them (GitHub issue #82).
- Tests: the check that workers of a started BiocParallel param run one BLAS thread is skipped where BLAS can
only run one thread (the Bioconductor builders).
spicyR 1.99.21
- spicy() (method = "cell") takes ‘imageInfo': a table with one row per image (a data.frame whose imageID column,
or else its row names, gives the image IDs, or the name of a table of the SpatialData store or object) for the
condition, subject, covariates or survival outcome when they are not columns of the cell table. It works for
cells in memory and in a store, so a survival outcome (a Surv column of imageInfo) can now be analysed from a
store. Images missing from imageInfo, and rows for images not among the cells, are errors. Results are
identical to those with the same columns in the cell table. The argument choosing a store’s cell table is now
called ‘tableKey' (it was 'table'), matching spicyr’s 'table_key'.
- writeCellStore() writes a data.frame of cells (and optionally a per-image table) as a SpatialData Zarr store,
which spicy() and spatialdataR's readSpatialData() read. The vignette ends with a small example: a store of 40
METABRIC cores, analysed from its path and as a SpatialData object, with results identical to the data.frame.
spatialdataR and anndataR are suggested.
- The image-by-image loop of the cell method is in the C++ core shared with spicyr (src/core/src/stream.cpp):
the passes over the image and cell-type codes, the gathering of each batch of whole images and the neighbour
sums of each image in a Dataset of its own, the images shared among ‘cores' threads. The R bindings only
describe R’s vectors to it. Results are identical (to 1.99.20 on 100 million and 1 billion cells, and on the
tests). From a store of 100 million cells, spicy(r = 25) takes 196 s and 1.5 GB on one core (250 s and 1.7 GB
before, run the same night) and 78 s and 5.2 GB on 8 cores (109 s and 5.1 GB); 1 billion cells take 15
minutes and 5.3 GB on 8 cores and 44 minutes and 1.65 GB on one, on a busy server.
spicyR 1.99.20
- spicy() (method = "cell") reads a SpatialData or AnnData Zarr store directly: give its path as ‘cells', with
'table' choosing the table and the column arguments naming obs columns ('imageID' defaults to the table’s
region key and 'spatialCoords' to obsm "spatial"). One pass over the image, cell type, condition and subject
columns, a block of rows at a time, gives the cells per image and type and the image-level values; the
coordinates and cell types are then read a batch of whole images at a time, so the table is never held in
memory. Results are identical to those of the same cells as a data.frame. Each image of a batch runs on its
own, the images shared among 'cores' threads. A SpatialData object (spatialdataR) is read from the store it
came from, or else from its table. Reading a store needs the Rarr package (Suggests). spicy(r = 25) from a
store of 1 billion cells (500 images, 10 types) takes 38 minutes and at most 1.6 GB on one core, and
14 minutes and 6.3 GB on 8; 100 million cells take 224 s (1.7 GB) and 93 s (5.1 GB).
spicyR 1.99.19
- spicy() (method = "cell") reads the cells image by image: the neighbour search and its sums run on one batch
of whole images at a time, gathered from the cell table's own columns, so the cells are neither sorted nor
copied as a whole. Image IDs and cell types that are plain character columns become factors in one pass.
Results are identical; at 100 million cells (50 images) the peak memory above the data falls from about
17 GB to about 1 GB (0.8 GB of it the integer codes of the two factors), and the run takes 241 s
instead of 363 s.
spicyR 1.99.18
- spicy() (method = "cell") needs much less memory and time on large data: it no longer makes a cellID and an
imageCellID string for every cell (it uses neither), keeps one row per image instead of a reordered copy of the
whole cell table, and takes the cells per image and type from the counts it already has. Results are identical.
spicyR 1.99.17
- spicy() with several radii finds every cell's neighbours once, at the largest radius, and counts them for each
radius from that one search. Results are identical; an omnibus over 25 radii takes about 4 times less time.
spicyR 1.99.16
- R-level parallel loops (method = "image" with sigma or a concave window, weightsByPair, survival models) run
on BiocParallel instead of parallel::mclapply: forked workers start once per call and share the data,
tasks are handed out in about eight chunks per worker, and socket workers are used on Windows. BLAS and
OpenMP run one thread in each worker during the call; OpenBLAS otherwise starts one thread per core in
every worker, and forked workers can hang after the session has used several OpenMP threads.
BiocParallel and RhpcBLASctl are imported; parallel is no longer imported.
spicyR 1.99.15
- spicy(combine = "davies") combines many radii by Davies' (1987) bound for the maximum of the standardised
effect over the radii, from the same per-radius tests and sandwich correlations as the max-T. It allows a fine
grid of radii; combine = "maxT" now stops with a message for more than six radii.
spicyR 1.99.14
- spicy(weightsByPair = TRUE) no longer fails for a cell type paired with itself: both terms of the weight
model have the same covariate, so it is fitted as one monotone smooth, which gives the predictions scam()
gives for the two-term model.
spicyR 1.99.13
- spicy() fits one model for the cell method: the patient frailty and the image random effect, without the
label-clustering inflation. The arguments frailty, labelClustering and imageHeterogeneity are removed, and
variance = "auto" is removed: the variance is CR2 on Satterthwaite df for every study size unless
variance = "hartung_knapp" is given.
spicyR 1.99.12
- Cell method: images within a patient now have their own random effect (imageHeterogeneity = TRUE, the
default). Each image is weighted by 1 / (its sampling variance + the between-image variance), with the
between-image variance estimated by DerSimonian-Laird from the spread within patients and reported as
tau2_image, so one large image can no longer outweigh a patient's other images. With one image per patient
the results are unchanged.
spicyR 1.99.11
- Concave windows (method = "image", window = "concave") are computed by a C++ port of concaveman, which
gives the same polygon as concaveman::concaveman(); concaveman is no longer suggested.
spicyR 1.99.10
- New buildKnnGraph(): the k-nearest-neighbour graph between the cells of each image, from the C++ core,
stored as a colPair for convPairs(). It gives the same graph as imcRtools::buildSpatialGraph(type =
"knn"), so imcRtools is no longer suggested; pkgdown is no longer suggested either.
spicyR 1.99.9
- Vignette: the numbers quoted in the text are computed from the results, and the statements about them
are checked when the vignette is built. The shuffled-label check uses 10 shuffles.
spicyR 1.99.8
- spicy(labelClustering = ) defaults to FALSE: the within-image variance is no longer inflated for
'to' cells that cluster among themselves unless asked for.
spicyR 1.99.7
- spicy(variance = ) defaults to "cr2" again (CR2 on Satterthwaite df). "auto" (the Hartung-Knapp
variance when a condition has at most 5 patients, CR2 otherwise) remains available as an option.
spicyR 1.99.6
- The allocation effect is now scaled on two sides. A pair whose 'to' cells avoid the 'from' cells (fewer
'to' cells with a 'from' cell within r than expected, summed over all images, without the conditions)
uses (observed share - q) / q, the fraction of the 'to' cells that would have a 'from' cell nearby that
were moved away; an attracting pair keeps (observed share - q) / (1 - q). Before, avoidance used the
attraction scale, on which the effect of a fixed avoidance grows with q, i.e. with how many 'from'
cells there are and how densely they are packed, and is unbounded below as q approaches 1. The
results gain a 'side' column ("attract" or "avoid").
- spicy(variance = ) gains "auto", the new default: the Hartung-Knapp variance on m - 2 df when a
condition has at most 5 patients (CR2's Satterthwaite df are then very small), CR2 otherwise. The
variance used is returned in $variance.
spicyR 1.99.5
- The cell method's default effect is the allocation effect (effect = "allocation"): the extra fraction
of 'to' cells with at least one 'from' cell within r, (observed share - q) / (1 - q), q its exact
expectation under random labelling. If a fraction f of the 'to' cells is moved next to 'from' cells,
the effect is f, however many 'from' cells there are and however densely they are packed. The count
excess of earlier versions (extra 'from' cells per 'to' cell) is effect = "count"; it grows with how
densely the 'from' cells are packed, so a change in packing alone can appear as a change in
co-localisation. Self-pairs use the exact random-labelling expectation of the allocation count.
- spicy(adjustAbundance = ) now defaults to FALSE (kontextualEngine() is unchanged). The log-share
adjustment does not separate more 'from' cells from more densely packed ones, and it removes real
effects when the share tracks the condition.
- Survival results: the column log_hr_per_cell is renamed log_hr_per_unit, the log hazard ratio per unit
of the effect (per 100% of 'to' cells for the allocation effect, per extra 'from' cell per 'to' cell
for the count).
- The label-clustering factor of the allocation effect is computed on its binary scores.
- Survival results no longer repeat the main test in unadjusted_* columns when abundance is not
adjusted.
- k nearest neighbours: images with at most k cells were read out of bounds in the neighbour sums; they
are now skipped.
spicyR 1.99.4
- The label-clustering factor of a ‘to' type is the median over every counted ('from') type, whichever pairs
are requested, in spicy() and kontextualEngine(). A pair’s result no longer depends on the other pairs asked
for: spicy(from = "A", to = "B") gives exactly the A__B row of spicy(). Before, the factor rested on the
requested pairs only (one ratio per image for a single pair), which in Kontextual simulations cost power.
spicyR 1.99.3
NEW FEATURES
- kontextualEngine(), the engine of Statial::kontextualTest() (documented there): Statial's
Kontextual statistic compared in each image with its exact expectation under random labelling
within the parent, combined by spicy()'s frailty GEE. Internal; topPairs(), signifPlot() and
spicyBoxPlot(parent = ) work on its results.
- signifPlot() draws the survival results of the cell method (it failed before).
- spicy(alternateResult = ) warns when given Kontextual values, which it compares between conditions
as they are, and points to Statial::kontextualTest().
spicyR 1.99.2
- The direction of a pair is reversed relative to 1.99.0 and 1.99.1 (and restored to that of
earlier development versions): from -> to now asks whether 'to' cells are placed near
'from' cells, with the excess counted as extra 'from' cells around each 'to' cell. A result
reported as A -> B by 1.99.0 or 1.99.1 is reported as B -> A now, with the same numbers.
spicyR 1.99.0
MAJOR CHANGES
- spicy() now runs spicyR Cell by default (method = "cell"): for each pair from -> to,
the excess number of 'from' cells within r of each 'to' cell beyond random labelling of
the observed cells (are 'to' cells placed near 'from' cells more than other cells are?), combined over images and patients by a frailty GEE and tested with a
CR2 variance on Satterthwaite degrees of freedom, with patients ('subject') as the units.
The original test is available with method = "image" (and is chosen automatically when
'alternateResult' or 'sigma' is given).
- The direction of a pair: 'to' is the type whose placement is tested (its cells are
relabelled at random under the null, and the excess is per 'to' cell), 'from' the type
counted around it.
NEW FEATURES
- By default the test is adjusted for the abundance of the counted (‘from') type (the log of its share
of all cells in each image; adjustAbundance = TRUE) and for any covariates; the unadjusted
test is reported in the unadjusted_* columns, and each adjustment’s effect and p-value in
abundance_effect and covariate_effect columns.
- spicyBoxPlot() shows a point per image sized by its weight in the test, and is interactive
with interactive = TRUE (plotly); plotImage(r = ) draws the radius around each to cell.
- Two or more conditions (each level against the reference), image- or patient-level
covariates, k nearest neighbours (k), several radii combined by max-T with the sandwich
correlation or by the Cauchy combination (r = c(...), combine), Hartung-Knapp variance for
very few patients, and survival outcomes (a Surv condition: score test and hazard ratio).
- The statistics run in a C++17 core shared verbatim with the Python twin, spicyr
(https://github.com/SydneyBioX/spicyr-py), which reproduces spicyR's results.
- Much faster: every ordered pair of 22 cell types from 400,000 cells (METABRIC) in seconds.
- spicyR 2.0 was written with AI assistance (Claude, Anthropic, through Claude Code), directed by the
authors: the C++ core, its R bindings, R/cell.R and R/spicy_main.R, the C++ ports
of the image method's model fits, the tests (including a plain-R reference written from the
method's equations) and the vignettes. The commits concerned carry a Co-Authored-By line.
- Far fewer dependencies.
spicyR 1.18.0
NEW FEATURES
- Added functionality for survival and mixed effect survival analysis.
- Added new plotting functions for survival analysis.
- Added support for using weights for alternative results.
BUG FIXES
- Fixed issue with bubble plot where unicode characters don't show up on
certain operating systems.
- Fixed issue with bubble plot where legend was not accurately showing
conditions or phenotypes on the plot.
SIGNIFICANT USER-VISIBLE CHANGES
- Vignette has been updated with new dataset showcasing more functionality
of spicyR.
DEPRECATED ARGUMENTS
- 'nCores' argument deprecated for both getPairwise() and spicy(), replaced
with 'cores'.
- 'BPPARAM' argument deprecated for both getPairwise() and spicy(), replaced
with 'cores'.
- 'imageIDCol' argument deprecated for both getPairwise() and spicy(), replaced
with 'imageID'.
- 'cellTypeCol' argument deprecated for both getPairwise() and spicy(), replaced
with 'cellType'.
- 'spatialCoordCols' argument deprecated for both getPairwise() and spicy(), replaced
with 'spatialCoords'.
- 'Rs' argument deprecated for both getPairwise() and spicy(), replaced
with 'r'.
* Deprecation warnings have been added to guide users to the new argument names.