multipointRmultipointR
is a package to fit point process models from spatstat.model
on SpatialExperiment
and SpatialFeatureExperiment
objects. Cells are approximated as points and their distribution in
space is modelled as the log-linear combination of spatial (or
non-spatial) covariates. multipointR
has a mode to do inferences on the level of a single image with
fitModel and across multiple images with
fitModelAcrossImages.
multipointR can be installed and loaded from Bioconductor as follows
For this document, we will use the well established MIBI TOF dataset from Keren et al. (2018).
spe <- SpatialDatasets::spe_Keren_2018()
spe
#> class: SpatialExperiment
#> dim: 48 197678
#> metadata(0):
#> assays(1): intensities
#> rownames(48): Na Si ... Ta Au
#> rowData names(0):
#> colnames(197678): 1 2 ... 197677 197678
#> colData names(40): CellID imageID ... Censored sample_id
#> reducedDimNames(0):
#> mainExpName: NULL
#> altExpNames(0):
#> spatialCoords names(2) : x y
#> imgData names(0):The preprocessing and visualisation of this dataset is taken from the Statial vignette.
# Code source: https://www.bioconductor.org/packages/release/bioc/vignettes/
# Statial/inst/doc/Statial.html
# #kontextual-identifying-discrete-changes-in-cell-state
# Examine all cell types in image
unique(spe$cellType)
#> [1] "Keratin_Tumour" "dn_T_CD3" "B_cell" "CD4_T_cell"
#> [5] "DC_or_Mono" "Unidentified" "Macrophages" "CD8_T_cell"
#> [9] "Other_Immune" "Endothelial" "Mono_or_Neu" "Mesenchymal"
#> [13] "Neutrophils" "NK" "Tumour" "DC"
#> [17] "Tregs"
# Set up cell populations
tumour <- c("Keratin_Tumour", "Tumour")
bcells <- c("B_cell")
tcells <- c("dn_T_CD3", "CD4_T_cell", "CD8_T_cell", "Tregs")
myeloid <- c("DC_or_Mono", "DC", "Mono_or_Neu", "Macrophages", "Neutrophils")
endothelial <- c("Endothelial")
mesenchymal <- c("Mesenchymal")
tissue <- c(endothelial, mesenchymal)
immune <- c(bcells, tcells, myeloid, "NK", "other immune")
all <- c(tumour, tissue, immune, "Unidentified")
# Lets define a new cell type vector
spe$cellTypeNew <- spe$cellType
# Select for all cells that express higher than baseline level of p53
p53Pos <- assay(spe)["p53", ] > -0.300460
# Find p53+ tumour cells
spe$cellTypeNew[spe$cellType %in% tumour] <- "Tumour"
spe$cellTypeNew[p53Pos & spe$cellType %in% tumour] <- "p53_Tumour"
# Group all immune cells under the name "Immune"
spe$cellTypeNew[spe$cellType %in% immune] <- "Immune"
spe$cellTypeNew <- as.factor(spe$cellTypeNew)
speSub <- subset(spe, , cellTypeNew %in% c("Immune", "Tumour", "p53_Tumour"))
speSub$cellTypeNew <- factor(speSub$cellTypeNew,
levels = c("Immune", "Tumour", "p53_Tumour")
)Parametric point process models (PPMs), as implemented in the
spatstat package, model the distribution of points in space
as an (in)homogeneous Poisson distribution (Baddeley et al. 2014). Thus, the expected
number of points falling in a region \(B\) follows an inhomogeneous Poisson
process with rate \(\lambda(u)\), the
local intensity (Baddeley et
al. 2016).
\[ \mathbb{E}[n(\mathbf{X} \cap B)] = \int_B \lambda(u)du \]
The PPM then models this inhomogeneous intensity \(\lambda(u)\) as a function of spatial and non-spatial covariates
\[ \lambda_{\Theta}(u) = \exp(\beta_0 + \Theta^TZ(u)) \]
where \(\beta_0\) is the baseline intensity, \(Z(u)\) are spatially varying covariate functions and \(\Theta\) is the parameter vector to be estimated. In particular, parameters within \(\Theta\) would be relevant for making inferences in order to make claims about spatial associations.
In the inhomogeneous Poisson process we have to model the baseline inhomogeneity as fixed effects. This means we have to either “know” what drives the inhomogeneity or parametrise a flexible \(\beta_0(u)\) for example with a bivariate spline surface. In such a setting we run into the possibility of overparametrising our model. An alternative are Cox point processes where the underlying inhomogeneity is modelled as a random effect. Cox processes are Poisson processes with a random intensity function \(\Lambda(u)\). Given this underlying random intensity function point process is constructed with intensity equal to \(\Lambda(u)\) (Baddeley et al. 2016).
The expected number of points of the Cox process is then
\[ \mathbb{E}[n(\mathbf{X} \cap B)] = \mathbb{E}(\mathbb{E}[n(\mathbf{X} \cap B)\mid\Lambda]) = \int_B \mathbb{E}[\Lambda(u)]\,\mathrm{d}u, \]
meaning that the expected number of points in a region \(B\) is conditional on the random intensity function. The intensity of the Cox process is then an inhomogeneous Poisson process conditional on the realisation of the stochastic function \(\Lambda(u)\) (Diggle et al. 2013).
\[ \lambda(u) = \mathbb{E}[\Lambda(u)] \]
A convenient formulation is that of a log Gaussian Cox process (LGCP).
\[ \Lambda(u) = \exp(G(u)), \] where \(G(u)\) is a Gaussian random field in \(\mathbb{R}^2\), with mean \(\mu(u)\) and covariance \(C(u,v)\). The exponentiation keeps the intensity positive whereas the assumption of the GRF leads to a well defined model, meaning that the covariance matrix is positive definite. Furthermore, we only consider models that are isotropic and stationary. These are obvious simplifactions in comparison to highly heterogeneous tissues.
The objective function of the LGCP can then be formulated as
\[ \begin{aligned} \Lambda(u) &= \underbrace{\exp(\beta_0(u)+ \mathbf{\Theta}^{\mathsf{T}}\mathbf{Z}(u))}_{\lambda_{\mathbf{\Theta}}(u)} \cdot \underbrace{\exp(G_0(u) - \sigma^2/2)}_{M(u)}, \end{aligned} \]
This is a nicely separable formulation with the first order terms of the Cox proccess (\(\lambda_{\mathbf{\Theta}}(u)\)) and the random part \(M(u)\) of the Cox model described by the second order term \(\sigma^2\) and a zero mean Gaussian random field \(G_0(u)=G(u) - \mu(u)\) with \(\mathbb{E}[M(u)] = 1\) (Diggle et al. 2013).
In order to estimate the mean of the GRF spatstat uses a
two step fitting procedure of Baddeley et al. (2014). First, the Poisson
process component is fit \(\log\lambda(u)\). After that, the
Cox/cluster component \(\sigma^2/2\) is
substracted by estimating via e.g. minimum contrast of the two summary
statistics or the Palm likelihood. Since the Palm likelihood approach
was shown to be more stable, we will use it here (Baddeley et al. 2022).
(Baddeley et al. 2016; Baddeley and Turner 2000).
For the single image case, we will analyse the relationship between the distribution of p53+ tumour cells and immune cells in image \(6\).
# Code source: https://www.bioconductor.org/packages/release/bioc/vignettes/
# Statial/inst/doc/Statial.html
# #kontextual-identifying-discrete-changes-in-cell-state
# Plot image 6
df <- spe |>
colData() |>
cbind(spatialCoords(spe)) |>
as.data.frame() |>
dplyr::filter(imageID == "6") |>
dplyr::filter(cellTypeNew %in%
c("Immune", "Tumour", "p53_Tumour"))
df$cellTypeNew <- factor(df$cellTypeNew,
levels = c("Immune", "Tumour", "p53_Tumour")
)
p1 <- df |>
arrange(cellTypeNew) |>
ggplot(aes(x = x, y = y, color = cellTypeNew)) +
geom_point(size = 1) +
scale_colour_manual(
values = c("#505050", "#D6D6D6", "#64BC46"),
labels = c("Immune", "Tumour", "p53+ Tumour")
) +
guides(colour = guide_legend(
title = "Cell types",
override.aes = list(size = 5)
)) +
coord_equal() +
theme_light()
p1We notice qualitatively a clearly separated tumour with p53+ cells and immune cells at the tumour border.
First, we will estimate the distribution of p53+ tumour cells as a homogeneous intensity in space, i.e., we fit a model of the form
\[ \mu(u) = \beta_0 - \sigma^2/2 \]
with a constant intercept \(\beta_0\)
speSub <- subset(spe, , imageID == "6")
m0 <- fitModel(
spe = speSub,
marks = "cellTypeNew",
interaction = "Hardcore",
method = "palm",
formula = as.formula("p53_Tumour ~ 1")
)The model is very basic and has not many parameters, let’s look at the spatial trend of the first step Poisson model:
The resulting spatial trend is a flat surface.
This is not a very sensible model since we see clear a inhomogeneous distribution of points in space.
Next, we will formulate a null model of the distribution of p53 positive cells. For this model, we will specify an inhomogeneous intensity varying with a bivariate spline model of \(x\) and \(y\) (\(s(x,y)\)). The model we fit is of the form
\[ \mu(u) = \beta_0(u) - \sigma^2/2 \]
with a spatially-varying intercept \(\beta_0(u)\) an inhomogeneous intercept parameterised as a tensor product spline.
m1 <- fitModel(
spe = speSub,
marks = "cellTypeNew",
method = "palm",
formula = as.formula("p53_Tumour ~
splines::ns(x, df = 2)*splines::ns(y, df = 2)"),
interaction = "Hardcore"
)We can look at the influence of the parameters with a \(z\)-tests for the individual parameters
coef(summary(m1))
#> Estimate S.E. CI95.lo
#> (Intercept) 6.0328406 1.083545 3.909132
#> splines::ns(x, df = 2)1 3.0102602 2.442727 -1.777397
#> splines::ns(x, df = 2)2 0.9028717 1.170090 -1.390462
#> splines::ns(y, df = 2)1 -1.1599082 2.446484 -5.954929
#> splines::ns(y, df = 2)2 1.2114326 1.258495 -1.255173
#> splines::ns(x, df = 2)1:splines::ns(y, df = 2)1 -2.2486119 5.513571 -13.055012
#> splines::ns(x, df = 2)2:splines::ns(y, df = 2)1 -2.5088183 2.690909 -7.782902
#> splines::ns(x, df = 2)1:splines::ns(y, df = 2)2 -5.1862606 2.894589 -10.859551
#> splines::ns(x, df = 2)2:splines::ns(y, df = 2)2 -4.8744137 2.138995 -9.066766
#> CI95.hi Ztest Zval
#> (Intercept) 8.1565497 *** 5.5676884
#> splines::ns(x, df = 2)1 7.7979170 1.2323359
#> splines::ns(x, df = 2)2 3.1962055 0.7716260
#> splines::ns(y, df = 2)1 3.6351126 -0.4741123
#> splines::ns(y, df = 2)2 3.6780381 0.9626040
#> splines::ns(x, df = 2)1:splines::ns(y, df = 2)1 8.5577886 -0.4078322
#> splines::ns(x, df = 2)2:splines::ns(y, df = 2)1 2.7652658 -0.9323313
#> splines::ns(x, df = 2)1:splines::ns(y, df = 2)2 0.4870301 -1.7917086
#> splines::ns(x, df = 2)2:splines::ns(y, df = 2)2 -0.6820615 * -2.2788341We can see that because we already assume an underlying random baseline most of the spline terms are not significantly different from zero. Therefore, we will not consider an additional spatially varying \(\beta_0(u)\) anymore.
From the literature, we know that p53+ tumour cells have immune-modulatory function. Our hypothesis is therefore that p53+ tumour cells will interact spatially with immune cells. In order to test this, we formulate a Cox model of distance to immune cells.
The model is then:
\[ \mu(u) = \beta_0 + \beta_{dist}\mathbf{Z}_{dist}(u) - \sigma^2/2 \]
m2 <- fitModel(
spe = speSub,
marks = "cellTypeNew",
formula = as.formula("p53_Tumour ~
spatstat.geom::distfun(Immune)"),
method = "palm",
interaction = "Hardcore"
)We can again look at the contribution of the model parameters with a LRT
coef(summary(m2))
#> Estimate S.E. CI95.lo CI95.hi Ztest
#> (Intercept) 6.281838 0.3162038 5.662090 6.901586 ***
#> spatstat.geom.distfun.Immune. 12.881319 7.7161343 -2.242026 28.004664
#> Zval
#> (Intercept) 19.866422
#> spatstat.geom.distfun.Immune. 1.669401We see that the model including the distance to immune cells does not lead to an improvement in model fit for this specific image.
An important part when fitting statistical models for inference is to check the model diagnostics to query potential deviations from the modeling assumptions
For kppm objects the theory and software to assess
goodness-of-fit is not as developed as for ppm objects. We
will consider two options:
ppm fit and compare this to a
simulated reference distribution of the kppm objectThe assessment of the summary statistics with confidence bands is a good second order check.
summaryQc <- envelope(m2, pcfinhom, global = TRUE)
#> Warning: Default settings for pcfinhom have changed in spatstat.explore 3.8-1
#> Warning: Default value of normpower has changed in pcfinhom
#> Generating 198 simulated realisations of fitted Cox process (99 to estimate the
#> mean and 99 to calculate envelopes) ...
#> 1, 2, 3, 4.6.8.10.12.14.16.18.20.22.24.26.28.30.32.34
#> .36.38.40.42.44.46.48.50.52.54.56.58.60.62.64.66.68.70.72.74
#> .76.78.80.82.84.86.88.90.92.94.96.98.100.102.104.106.108.110.112.114
#> .116.118.120.122.124.126.128.130.132.134.136.138.140.142.144.146.148.150.152.154
#> .156.158.160.162.164.166.168.170.172.174.176.178.180.182.184.186.188.190.192.194
#> .196.
#> 198.
#>
#> Done.
plot(summaryQc)We notice some devitations from the at low radii \(r\) of the model envelopes and the observed pair-correlation function indicating some unexplained variance
set.seed(1234)
#extract the first Poisson fit
poM2 <- as.ppm(m2)
#simulate confidence bands under `kppm` model
nsim <- 99
sims <- simulate(m2, nsim = nsim)
#plot the QQ
qq <- qqplot.ppm(poM2,
nsim = nsim,
expr = sims,
type = "pearson")
#> Simulating 99 realisations... 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
#> 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
#> 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
#> 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
#> 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98,
#> 99.
#>
#> Diagnostic info:
#> simulated patterns contained an average of 772.080808080808 points.
#> Calculating quantiles...averaging.....Done.Under the quantiles of the simulated kppm model the
difference is less clear
Another option that users have is to define a segmented polygon as
spatial covariate in their ppm model.
To do this, we first perform a segmentation on our image. For
simplicity, we will use the Bioconductor package sosta. Of
course, one can use any other segmentation method, the only requirement
being that the polygon is stored as an sf object.
segmentedTumour <- sosta::reconstructShapeDensityImage(
speSub,
marks = "cellTypeNew",
markSelect = c("Tumour", "p53_Tumour"),
thres = 5e-4
)
p1 + geom_sf(
data = segmentedTumour, inherit.aes = FALSE,
fill = NA, color = "red", linewidth = 1
)
#> Coordinate system already present.
#> ℹ Adding new coordinate system, which will replace the existing one.In order to save this polygon we will convert our
SpatialExperiment object into a
SpatialFeatureExperiment object and store it in the
annotGeometries.
sfeSub <- toSpatialFeatureExperiment(speSub)
annotGeometry(sfeSub, "tumour_mask") <- segmentedTumourAfter this, we can specify our ppm with the segmented
tumour as spatial covariate. The result will be a logical covariate,
indicating the intensity of the response within the tumour
(tumour_mask == TRUE) as compared to outside.
m3 <- fitModel(
spe = sfeSub,
marks = "cellTypeNew",
formula = as.formula("p53_Tumour ~
tumour_mask"),
method = "palm",
standardize = FALSE,
interaction = "Hardcore"
)We can again assess the importance of the variables with a \(z\)-test
coef(summary(m3))
#> Estimate S.E. CI95.lo CI95.hi Ztest Zval
#> (Intercept) -11.997792 0.4626349 -12.904539 -11.091044 *** -25.93361
#> tumour_maskTRUE 3.818998 0.4537724 2.929621 4.708376 *** 8.41611We can interpret from this test that the covariate
tumour_mask is important for describing the distribution of
p53+ cells in space. This makes sense, since p53+ tumour cells are a
subset of tumour cells and can only be found within the tumour.
plot(m3) + geom_sf(
data = segmentedTumour, inherit.aes = FALSE,
fill = NA, color = "darkred", linewidth = 0.6
)
#> Coordinate system already present.
#> ℹ Adding new coordinate system, which will replace the existing one.The model is just a binary surface within and outside of the tumour
summaryQc <- envelope(m3, pcfinhom, global = TRUE)
#> Generating 198 simulated realisations of fitted Cox process (99 to estimate the
#> mean and 99 to calculate envelopes) ...
#> 1, 2, 3, 4.6.8.10.12.14.16.18.20.22.24.26.28.30.32.34
#> .36.38.40.42.44.46.48.50.52.54.56.58.60.62.64.66.68.70.72.74
#> .76.78.80.82.84.86.88.90.92.94.96.98.100.102.104.106.108.110.112.114
#> .116.118.120.122.124.126.128.130.132.134.136.138.140.142.144.146.148.150.152.154
#> .156.158.160.162.164.166.168.170.172.174.176.178.180.182.184.186.188.190.192.194
#> .196.
#> 198.
#>
#> Done.
plot(summaryQc)This model fits the data much better than model m2. We
can combine models m2 and m3 and create a
model that incorporates both the tumour mask plus the distance to the
immune cells.
m4 <- fitModel(
spe = sfeSub,
marks = "cellTypeNew",
formula = as.formula("p53_Tumour ~
tumour_mask +
spatstat.geom::distfun(Immune)"),
standardize = FALSE,
method = "palm",
penalised=TRUE,
interaction = "Hardcore"
)coef(summary(m4))
#> Estimate S.E. CI95.lo
#> (Intercept) -11.987258302 0.472768286 -12.913867114
#> tumour_maskTRUE 3.849249897 0.438014904 2.990756462
#> spatstat.geom.distfun.Immune. -0.000675049 0.003060273 -0.006673074
#> CI95.hi Ztest Zval
#> (Intercept) -11.060649489 *** -25.3554620
#> tumour_maskTRUE 4.707743333 *** 8.7879428
#> spatstat.geom.distfun.Immune. 0.005322976 -0.2205846In this model we notice again no significant effect of distance to immune cells after accounting for underlying randomness and the binary tumour mask.
plot(m4) + geom_sf(
data = segmentedTumour, inherit.aes = FALSE,
fill = NA, color = "darkred", linewidth = 0.6
)
#> Coordinate system already present.
#> ℹ Adding new coordinate system, which will replace the existing one.Now we have a surface of zero intensity outside of the tumour and within the tumour a varrying surface.
summaryQc <- envelope(m4, pcfinhom, global = TRUE)
#> Generating 198 simulated realisations of fitted Cox process (99 to estimate the
#> mean and 99 to calculate envelopes) ...
#> 1, 2, 3, 4.6.8.10.12.14.16.18.20.22.24.26.28.30.32.34
#> .36.38.40.42.44.46.48.50.52.54.56.58.60.62.64.66.68.70.72.74
#> .76.78.80.82.84.86.88.90.92.94.96.98.100.102.104.106.108.110.112.114
#> .116.118.120.122.124.126.128.130.132.134.136.138.140.142.144.146.148.150.152.154
#> .156.158.160.162.164.166.168.170.172.174.176.178.180.182.184.186.188.190.192.194
#> .196.
#> 198.
#>
#> Done.
plot(summaryQc)The goodness of fit is improved in comparison to model
m3 but still we notice some deviations at low radii.
As mentioned above, this dataset contains many images. We may want to fit separate models like those above to each image and look at the resulting distribution of the coefficients relating the distance to immune cells \(\beta_{\text{dist}}\)
mdlLs <- fitModelAcrossImages(
spe = spe,
imageId = "imageID",
imageLs = seq(1,29),
marks = "cellTypeNew",
formula = as.formula("p53_Tumour ~
spatstat.geom::distfun(Immune)"),
improve.type = "wclik1",
method = "palm",
interaction = "Hardcore",
threshold = 10
)
#> Fitting kppm to image 1
#> There were less than 10 points to compute an intensity on
#> Fitting kppm to image 2
#> Fitting kppm to image 3
#> There were less than 10 points to compute an intensity on
#> Fitting kppm to image 4
#> There were less than 10 points to compute an intensity on
#> Fitting kppm to image 5
#> There were less than 10 points to compute an intensity on
#> Fitting kppm to image 6
#> Fitting kppm to image 7
#> There were less than 10 points to compute an intensity on
#> Fitting kppm to image 8
#> There were less than 10 points to compute an intensity on
#> Fitting kppm to image 9
#> Warning in sqrt(s/A): NaNs produced
#> Warning in sqrt(B/s): NaNs produced
#> Fitting kppm to image 10
#> Fitting kppm to image 11
#> Fitting kppm to image 12
#> Fitting kppm to image 13
#> Fitting kppm to image 14
#> Fitting kppm to image 15
#> Fitting kppm to image 16
#> Fitting kppm to image 17
#> Fitting kppm to image 18
#> Fitting kppm to image 19
#> Fitting kppm to image 20
#> Fitting kppm to image 21
#> Fitting kppm to image 22
#> Fitting kppm to image 23
#> Fitting kppm to image 24
#> Fitting kppm to image 25
#> Fitting kppm to image 26
#> Fitting kppm to image 27
#> Fitting kppm to image 28
#> Fitting kppm to image 29The dataset describes three tumour subtypes, cold, compartmentalised
and mixed. We want to investigate the differences in the distance to
immune cells coefficient \(\beta_{\text{dist}}\) across these
subtypes. First, we need to extract the coefficients from the model and
convert this to a data.frame.
mdlDfSub <- mdlDf %>% filter(covariate %in% c("spatstat.geom.distfun.Immune."))
ggplot(mdlDfSub, aes(x = covariate, y = Estimate, label = imageID)) +
geom_boxplot(outlier.shape = NA, alpha = 0.3) +
geom_jitter(aes(color = log10(S.E.)),
position = position_jitter(seed = 123)
) +
geom_text(aes(color = log10(S.E.)),
hjust = 0,
vjust = 0, position = position_jitter(seed = 123)
) +
theme_light() +
facet_wrap(~tumour_type)We see a difference between the tumour types: cold tumours show no effect and a generally positive effect of immune cell distance on p53+ cells in compartmentalised and mixed tumours despite not being significant.
There is a model that is a clear outliers, let’s look at it in more detail
The second model is very sparse in terms of the response point distribution. This could explain some of the more extreme values.
We can also test the effect of tumour type (cold, compartmentalised, mixed) on the distance to immune cells coefficient \(\beta_{\text{dist}}\) with a linear model. We weight each observation by the inverse of the standard error, as done by Gerber et al. (2026).
The model is then parametrised as follows:
\[ {\hat{\beta}}_{\text{dist}} = \gamma_0 + \gamma Z_{\text{stage}} + \epsilon \quad \epsilon \sim \mathcal{N}\!\left(0,\, \sigma^2 \text{SE}_{\text{dist}}\right) \]
We can fit this model
mdl <- lm(Estimate ~ tumour_type,
data = mdlDf,
weights = 1 / (mdlDf$S.E.),
subset = covariate == "spatstat.geom.distfun.Immune."
)
print(summary(mdl))
#>
#> Call:
#> lm(formula = Estimate ~ tumour_type, data = mdlDf, subset = covariate ==
#> "spatstat.geom.distfun.Immune.", weights = 1/(mdlDf$S.E.))
#>
#> Weighted Residuals:
#> Min 1Q Median 3Q Max
#> -14.805 -6.758 -1.359 2.673 27.978
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 1.384 6.548 0.211 0.8347
#> tumour_typecompartmentalised 34.782 12.814 2.714 0.0133 *
#> tumour_typemixed 27.028 9.790 2.761 0.0121 *
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 10.18 on 20 degrees of freedom
#> Multiple R-squared: 0.3584, Adjusted R-squared: 0.2942
#> F-statistic: 5.586 on 2 and 20 DF, p-value: 0.01182In agreement with the findings above, there are positive but only weakly significant effects for both compartmentalised and mixed tumours in comparison to cold tumours.
AI was used to revise the content in this vignette including the formulae and derivations
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats4 stats graphics grDevices utils datasets methods
#> [8] base
#>
#> other attached packages:
#> [1] SpatialDatasets_1.11.0 ExperimentHub_3.3.2
#> [3] AnnotationHub_4.3.2 BiocFileCache_3.3.0
#> [5] dbplyr_2.6.0 patchwork_1.3.2
#> [7] spatstat.model_3.7-2 rpart_4.1.27
#> [9] spatstat.explore_3.8-3 nlme_3.1-171
#> [11] spatstat.random_3.5-2 spatstat.geom_3.8-3
#> [13] spatstat.univar_3.2-0 spatstat.data_3.1-9
#> [15] ggplot2_4.0.3 dplyr_1.2.1
#> [17] SpatialFeatureExperiment_1.15.1 SpatialExperiment_1.23.0
#> [19] SingleCellExperiment_1.35.2 SummarizedExperiment_1.43.0
#> [21] Biobase_2.73.2 GenomicRanges_1.65.4
#> [23] Seqinfo_1.3.2 IRanges_2.47.5
#> [25] S4Vectors_0.51.10 BiocGenerics_0.59.12
#> [27] generics_0.1.4 MatrixGenerics_1.25.0
#> [29] matrixStats_1.5.0 multipointR_0.99.7
#> [31] BiocStyle_2.41.0
#>
#> loaded via a namespace (and not attached):
#> [1] splines_4.6.1 bitops_1.1-0
#> [3] filelock_1.0.3 tibble_3.3.1
#> [5] R.oo_1.27.1 polyclip_1.10-7
#> [7] lifecycle_1.0.5 httr2_1.3.0
#> [9] Rdpack_2.6.6 formula.tools_1.7.1
#> [11] sf_1.1-3 edgeR_4.99.10
#> [13] lattice_0.23-1 MASS_7.3-66
#> [15] backports_1.5.1 magrittr_2.0.5
#> [17] limma_3.99.2 sass_0.4.10
#> [19] rmarkdown_2.32 jquerylib_0.1.4
#> [21] yaml_2.3.12 otel_0.2.0
#> [23] sp_2.2-4 spatstat.sparse_3.2-0
#> [25] DBI_1.3.0 buildtools_1.0.0
#> [27] RColorBrewer_1.1-3 multcomp_1.4-32
#> [29] abind_1.4-8 spatialreg_1.4-3
#> [31] purrr_1.2.2 R.utils_2.13.0
#> [33] RCurl_1.98-1.20 TH.data_1.1-5
#> [35] rappdirs_0.3.4 sandwich_3.1-3
#> [37] spatstat.utils_3.2-5 maketools_1.3.2
#> [39] terra_1.9-50 units_1.0-1
#> [41] goftest_1.2-3 dqrng_0.4.1
#> [43] DelayedMatrixStats_1.35.0 codetools_0.2-20
#> [45] DropletUtils_1.33.2 DelayedArray_0.39.8
#> [47] scuttle_1.23.2 tidyselect_1.2.1
#> [49] farver_2.1.2 jsonlite_2.0.0
#> [51] BiocNeighbors_2.7.3 e1071_1.7-17
#> [53] survival_3.8-12 sosta_1.5.2
#> [55] smoothr_1.3.0 tools_4.6.1
#> [57] Rcpp_1.1.2 glue_1.8.1
#> [59] BiocBaseUtils_1.15.1 SparseArray_1.13.4
#> [61] xfun_0.62 mgcv_1.9-4
#> [63] EBImage_4.55.2 HDF5Array_1.41.3
#> [65] withr_3.0.3 BiocManager_1.30.27
#> [67] fastmap_1.2.0 boot_1.3-32
#> [69] rhdf5filters_1.25.4 spData_2.3.5
#> [71] digest_0.6.39 R6_2.6.1
#> [73] wk_0.9.5 LearnBayes_2.15.2
#> [75] tensor_1.5.1 jpeg_0.1-11
#> [77] RSQLite_3.53.3 R.methodsS3_1.8.2
#> [79] h5mread_1.5.3 data.table_1.18.6.1
#> [81] class_7.3-24 httr_1.4.9
#> [83] htmlwidgets_1.6.4 S4Arrays_1.13.2
#> [85] spdep_1.4-2 pkgconfig_2.0.3
#> [87] gtable_0.3.6 blob_1.3.0
#> [89] S7_0.2.2 XVector_0.53.0
#> [91] sys_3.4.3 htmltools_0.5.9
#> [93] fftwtools_0.9-11 scales_1.4.0
#> [95] png_0.1-9 reformulas_0.4.4
#> [97] knitr_1.52 rjson_0.2.23
#> [99] coda_0.19-4.1 curl_8.1.0
#> [101] proxy_0.4-29 cachem_1.1.0
#> [103] zoo_1.9-1 rhdf5_2.57.20
#> [105] operator.tools_1.6.3.1 BiocVersion_3.24.0
#> [107] KernSmooth_2.23-27 parallel_4.6.1
#> [109] AnnotationDbi_1.75.2 s2_1.1.13
#> [111] pillar_1.11.1 grid_4.6.1
#> [113] vctrs_0.7.3 beachmat_2.29.3
#> [115] sfheaders_0.4.5 evaluate_1.0.5
#> [117] zeallot_0.2.0 magick_2.9.1
#> [119] mvtnorm_1.4-2 cli_3.6.6
#> [121] locfit_1.5-9.12 compiler_4.6.1
#> [123] crayon_1.5.3 rlang_1.3.0
#> [125] labeling_0.4.3 classInt_0.4-11
#> [127] viridisLite_0.4.3 deldir_2.0-4
#> [129] BiocParallel_1.47.0 Biostrings_2.81.9
#> [131] tiff_0.1-12 marginaleffects_1.0.0
#> [133] Matrix_1.7-6 sparseMatrixStats_1.25.0
#> [135] bit64_4.8.6 Rhdf5lib_2.1.0
#> [137] KEGGREST_1.53.6 statmod_1.5.2
#> [139] rbibutils_2.4.1 memoise_2.0.1
#> [141] bslib_0.12.0 bit_4.6.0