28  ppplist from solist,anylist

Function spatstat.geom::solist() returns an R object of S3 class 'ppplist', if all of the input objects are two-dimensional point-patterns (ppp.object, Chapter 27). The S3 class 'ppplist' inherits from the classes 'solist' (Chapter 30), 'anylist' (Chapter 13), 'listof' and 'list'.

Listing 28.1, Listing 28.2 summarize the S3 methods for the class 'ppplist' in the spatstat.* family of packages,

Listing 28.1: Existing S3 methods spatstat.geom::*.ppplist
Code
suppressPackageStartupMessages(library(spatstat.geom))
.S3methods(class = 'ppplist', all.names = TRUE) |> 
  attr(which = 'info', exact = TRUE) |>
  subset.data.frame(subset = from == 'spatstat.geom')
#                       visible          from       generic  isS4
# as.data.frame.ppplist    TRUE spatstat.geom as.data.frame FALSE
# superimpose.ppplist      TRUE spatstat.geom   superimpose FALSE
Listing 28.2: Existing S3 methods spatstat.explore::*.ppplist
Code
suppressPackageStartupMessages(library(spatstat.explore))
.S3methods(class = 'ppplist', all.names = TRUE) |> 
  attr(which = 'info', exact = TRUE) |>
  subset.data.frame(subset = from == 'spatstat.explore')
#                               visible             from               generic  isS4
# density.ppplist                  TRUE spatstat.explore               density FALSE
# densityAdaptiveKernel.ppplist    TRUE spatstat.explore densityAdaptiveKernel FALSE

The examples in Chapter 28 require that the search path contains the following namespaces,

library(groupedHyperframe)

Package groupedHyperframe (v0.3.2) implements more S3 methods to the class 'ppplist' (Table 28.1),

Table 28.1: S3 methods groupedHyperframe::*.ppplist (v0.3.2)
visible generic isS4
.rmax.ppplist TRUE groupedHyperframe::.rmax FALSE
aggregate_marks.ppplist TRUE groupedHyperframe::aggregate_marks FALSE
density_marks.ppplist TRUE groupedHyperframe::density_marks FALSE
Emark_.ppplist TRUE groupedHyperframe::Emark_ FALSE
Gcross_.ppplist TRUE groupedHyperframe::Gcross_ FALSE
Jcross_.ppplist TRUE groupedHyperframe::Jcross_ FALSE
Kcross_.ppplist TRUE groupedHyperframe::Kcross_ FALSE
kerndens.ppplist TRUE groupedHyperframe::kerndens FALSE
Kmark_.ppplist TRUE groupedHyperframe::Kmark_ FALSE
Lcross_.ppplist TRUE groupedHyperframe::Lcross_ FALSE
markconnect_.ppplist TRUE groupedHyperframe::markconnect_ FALSE
markcorr_.ppplist TRUE groupedHyperframe::markcorr_ FALSE
markvario_.ppplist TRUE groupedHyperframe::markvario_ FALSE
nncross_.ppplist TRUE groupedHyperframe::nncross_ FALSE
pairwise_cor_spatial.ppplist TRUE groupedHyperframe::pairwise_cor_spatial FALSE
quantile.ppplist TRUE stats::quantile FALSE
Vmark_.ppplist TRUE groupedHyperframe::Vmark_ FALSE
Table 28.2: S3 methods currently not planned for class 'ppplist'
Table 28.2: S3 methods currently not planned for class 'ppplist'
Not Planned Explained in
unmark.ppplist() Section 34.1

28.1 Kernel Density of numeric-marks

The S3 generic function density_marks() has been introduced in Section 27.4 (Table 27.3). The S3 method density_marks.ppplist() is a batch process of the S3 method density_marks.ppp() (Section 27.4), which finds the kernel densitys of allnumeric-marks in each point-pattern in the input ppplist.

Listing 28.3 finds the kernel densitys of all numeric-marks of the split-ted betacells (Section 10.4).

Listing 28.3: Example: function density_marks.ppplist()
Code
spatstat.data::betacells |>
  spatstat.geom::split.ppp(f = 'type') |>
  density_marks()
# $off
# $off$area
# 
# Call:
#   density.default(x = `$area`)
# 
# Data: $area (70 obs.);    Bandwidth 'bw' = 11.44
# 
#        x               y            
#  Min.   :134.0   Min.   :6.323e-06  
#  1st Qu.:199.1   1st Qu.:9.940e-04  
#  Median :264.2   Median :3.102e-03  
#  Mean   :264.2   Mean   :3.832e-03  
#  3rd Qu.:329.3   3rd Qu.:5.410e-03  
#  Max.   :394.4   Max.   :1.184e-02  
# 
# 
# $on
# $on$area
# 
# Call:
#   density.default(x = `$area`)
# 
# Data: $area (65 obs.);    Bandwidth 'bw' = 23.46
# 
#        x               y            
#  Min.   :137.5   Min.   :2.919e-06  
#  1st Qu.:249.3   1st Qu.:2.427e-04  
#  Median :361.1   Median :1.414e-03  
#  Mean   :361.1   Mean   :2.231e-03  
#  3rd Qu.:473.0   3rd Qu.:4.368e-03  
#  Max.   :584.8   Max.   :6.075e-03

Listing 28.4 showcases the exception handling, as the input ppplist btb.extra (Section 10.6) does not contain numeric-mark.

Listing 28.4: Exception: function density_marks.ppplist(), no numeric-marks
Code
spatstat.data::btb.extra |> 
  density_marks()
# $full
# named list()
# 
# $standard
# named list()

As explained in Section 27.4, the S3 method density_marks.ppplist() is different from the S3 method spatstat.explore::density.ppplist(), which uses the \(x\)- and \(y\)-coords only thus provides identical return with the marks removed from the input ppplist.

Review: function spatstat.explore::density.ppplist() (Baddeley, Rubak, and Turner 2015)
a1 = spatstat.data::btb.extra |>
  spatstat.explore::density.ppplist()
a0 = spatstat.data::btb.extra |>
  spatstat.geom::solapply(FUN = spatstat.geom::unmark.ppp) |>
  spatstat.explore::density.ppplist()
stopifnot(identical(a1, a0))

28.1.1 Kernel Density Estimates of numeric-marks

The S3 generic function kerndens() has been introduced in Section 27.4.1 (Table 27.5). The S3 method kerndens.ppplist() finds the kernel density estimates of all numeric-marks in all point-patterns in the input ppplist.

Example: function kerndens.ppplist()
spatstat.data::betacells |>
  spatstat.geom::split.ppp(f = 'type') |>
  kerndens(n = 8L)
# $area
# $area$off
# [1] 6.860440e-06 1.241176e-03 4.217601e-03 1.094990e-02 6.417054e-03 3.925757e-03 1.168156e-03 6.322900e-06
# 
# $area$on
# [1] 3.516447e-06 9.294044e-04 4.454117e-03 6.067291e-03 3.010829e-03 7.952900e-04 2.538680e-04 2.918730e-06
Exception: function kerndens.ppplist(), no numeric-marks
spatstat.data::btb.extra |> 
  kerndens() |>
  is.null()
# [1] TRUE

28.2 Quantile of numeric-marks

The S3 method quantile.ppplist() finds the quantiles of all numeric-marks in all point-patterns (Section 27.5).

Example: function quantile.ppplist()
spatstat.data::betacells |>
  spatstat.geom::split.ppp(f = 'type') |>
  quantile()
# $area
# $area$off
#     0%    25%    50%    75%   100% 
# 168.30 239.40 257.35 279.25 360.10 
# 
# $area$on
#    0%   25%   50%   75%  100% 
# 207.9 281.7 321.3 362.2 514.4
Example: function quantile.ppplist(), no numeric-marks
spatstat.data::btb.extra |> 
  quantile()
# named list()

28.3 Aggregate Marks-Statistics

The S3 generic function aggregate_marks() has been introduced in Section 27.7 (Table 27.6). The S3 method aggregate_marks.ppplist()

  • aggregates and vectorizes the marks of each ppp member of the input (Section 27.7);
  • returns a numeric-vectorlist (Chapter 32).
Example: function aggregate_marks.ppplist(), for sample mean and sd
spatstat.geom::solist(
  spatstat.data::spruces,
  spatstat.data::spruces
) |>
  aggregate_marks(FUN = mean)
# Component 1:
#      mean 
# 0.2503731 
# 
# Component 2:
#      mean 
# 0.2503731
spatstat.geom::solist(
  spatstat.data::spruces,
  spatstat.data::spruces
) |>
  aggregate_marks(FUN = \(z) c(mean = mean(z), sd = sd(z)))
# Component 1:
#       mean         sd 
# 0.25037313 0.04697474 
# 
# Component 2:
#       mean         sd 
# 0.25037313 0.04697474
Example: function aggregate_marks.ppplist(), for relative frequencies
spatstat.geom::solist(
  spatstat.data::ants,
  spatstat.data::ants
) |>
  aggregate_marks(FUN = \(z) table(z)/length(z))
# Component 1:
# Cataglyphis      Messor 
#   0.2989691   0.7010309 
# 
# Component 2:
# Cataglyphis      Messor 
#   0.2989691   0.7010309
Example: function aggregate_marks.ppplist(), for sample mean and sd of area-by-type
spatstat.geom::solist(
  spatstat.data::betacells,
  spatstat.data::betacells
) |>
  aggregate_marks(by = area ~ type, FUN = \(z) c(mean = mean(z), sd = sd(z)))
# Component 1:
# off.area.mean   off.area.sd  on.area.mean    on.area.sd 
#     259.72143      40.86083     325.11692      60.71534 
# 
# Component 2:
# off.area.mean   off.area.sd  on.area.mean    on.area.sd 
#     259.72143      40.86083     325.11692      60.71534
Example: function aggregate_marks.ppplist(), for relative frequencies of season-by-group
spatstat.geom::solist(
  spatstat.data::gorillas,
  spatstat.data::gorillas
) |>
  aggregate_marks(by = season ~ group, FUN = \(z) table(z)/length(z))
# Component 1:
#   major.season.dry major.season.rainy   minor.season.dry minor.season.rainy 
#          0.4285714          0.5714286          0.4208754          0.5791246 
# 
# Component 2:
#   major.season.dry major.season.rainy   minor.season.dry minor.season.rainy 
#          0.4285714          0.5714286          0.4208754          0.5791246

The S3 method t.vectorlist() (Section 32.3) is the fastest way to extract a “slice” from the returned numeric-vectorlist.

Advanced: function t.vectorlist()
spatstat.geom::solist(
  spatstat.data::gorillas,
  spatstat.data::gorillas
) |>
  aggregate_marks(by = season ~ group, FUN = \(z) table(z)/length(z)) |>
  t.vectorlist()
# major.season.dry:
# [1] 0.4285714 0.4285714
# 
# major.season.rainy:
# [1] 0.5714286 0.5714286
# 
# minor.season.dry:
# [1] 0.4208754 0.4208754
# 
# minor.season.rainy:
# [1] 0.5791246 0.5791246

28.4 Default \(r_\text{max}\)

The S3 generic function .rmax() has been introduced in Section 27.10 (Table 27.9). The S3 method .rmax.ppplist() obtains the default \(r_\text{max}\) before the (potentially) very slow batch processes.

Advanced: function .rmax.ppplist()
spatstat.data::btb.extra$full |> 
  spatstat.explore::markcorr() |> 
  vapply(FUN = .rmax.fv, FUN.VALUE = NA_real_)
#        year spoligotype 
#    26.96117    26.96117
spatstat.data::btb.extra$standard |> 
  spatstat.explore::markcorr() |> 
  vapply(FUN = .rmax.fv, FUN.VALUE = NA_real_)
#        year spoligotype 
#    26.96117    26.96117
spatstat.data::btb.extra |> 
  .rmax(fun = 'K')
#     full standard 
# 26.96117 26.96117

28.5 \(k\)-Means Clustering

Function kmeans.ppplist()

  • is a “pseudo” S3 method, as the workhorse function stats::kmeans() shipped with R version 4.5.2 (2025-10-31) is not an S3 generic function.
  • is a simple iteration of the function kmeans.ppp() (Section 27.11).
  • returns an object of class 'pppkmlist', which inherits from 'ppplist'.

Package groupedHyperframe (v0.3.2) implements the following S3 methods to the class 'pppkmlist' (Table 28.3),

Table 28.3: S3 methods groupedHyperframe::*.pppkmlist (v0.3.2)
visible from generic isS4
split.pppkmlist TRUE groupedHyperframe base::split FALSE

28.5.1 Examples

The author uses a subset of the hyper data frame flu (Section 10.10) to illustrate this concept.

Example: function kmeans.ppplist()
set.seed(14); spatstat.data::flu |>
  spatstat.geom::subset.hyperframe(subset = (stain == 'M2-M1') & (virustype == 'wt')) |>
  spatstat.geom::`$.hyperframe`(name = 'pattern') |>
  kmeans.ppplist(formula = ~ x + y, centers = 3L)
# List of point patterns
# 
# wt M2-M1 13:
# Marked planar point pattern: 471 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# with k-means clustering of 153, 147, 171 points
# 
# wt M2-M1 22:
# Marked planar point pattern: 217 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# with k-means clustering of 64, 85, 68 points
# 
# wt M2-M1 27:
# Marked planar point pattern: 214 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# with k-means clustering of 71, 59, 84 points
# 
# wt M2-M1 43:
# Marked planar point pattern: 406 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# with k-means clustering of 169, 108, 129 points
# 
# wt M2-M1 49:
# Marked planar point pattern: 417 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# with k-means clustering of 125, 195, 97 points
# 
# wt M2-M1 65:
# Marked planar point pattern: 318 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# with k-means clustering of 117, 109, 92 points
# 
# wt M2-M1 71:
# Marked planar point pattern: 265 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# with k-means clustering of 118, 83, 64 points
# 
# wt M2-M1 84:
# Marked planar point pattern: 509 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# with k-means clustering of 155, 156, 198 points

28.5.2 Split by \(k\)-Means Clustering

The S3 method split.pppkmlist() splits a pppkmlist by the \(k\)-means clustering indices of each 'pppkm' member. The returned object has attributes

  • attr(,'id'), indices of the point-patterns before splitting.
  • attr(,'cluster'), indices of \(k\)-means clusters, nested in id.

The author uses a subset of the hyper data frame flu (Section 10.10) to illustrate this concept.

Example: function split.pppkmlist()
set.seed(14); spatstat.data::flu |>
  spatstat.geom::subset.hyperframe(subset = (stain == 'M2-M1') & (virustype == 'wt')) |>
  spatstat.geom::`$.hyperframe`(name = 'pattern') |> 
  kmeans.ppplist(formula = ~ x + y, centers = 3L) |>
  split()
# $`wt M2-M1 13.1`
# Marked planar point pattern: 153 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 13.2`
# Marked planar point pattern: 147 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 13.3`
# Marked planar point pattern: 171 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 22.1`
# Marked planar point pattern: 64 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 22.2`
# Marked planar point pattern: 85 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 22.3`
# Marked planar point pattern: 68 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 27.1`
# Marked planar point pattern: 71 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 27.2`
# Marked planar point pattern: 59 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 27.3`
# Marked planar point pattern: 84 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 43.1`
# Marked planar point pattern: 169 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 43.2`
# Marked planar point pattern: 108 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 43.3`
# Marked planar point pattern: 129 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 49.1`
# Marked planar point pattern: 125 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 49.2`
# Marked planar point pattern: 195 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 49.3`
# Marked planar point pattern: 97 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 65.1`
# Marked planar point pattern: 117 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 65.2`
# Marked planar point pattern: 109 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 65.3`
# Marked planar point pattern: 92 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 71.1`
# Marked planar point pattern: 118 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 71.2`
# Marked planar point pattern: 83 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 71.3`
# Marked planar point pattern: 64 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 84.1`
# Marked planar point pattern: 155 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 84.2`
# Marked planar point pattern: 156 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# $`wt M2-M1 84.3`
# Marked planar point pattern: 198 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# attr(,"id")
#  [1] 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8
# attr(,"cluster")
#  [1] 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3

28.6 Batch Process on Eligible Marks

The S3 methods Emark_.ppplist(), Vmark_.ppplist(), etc., in Table 28.1,

  • collects the return of the corresponding S3 method in Table 27.18, for each point-pattern (ppp.object, Chapter 27) in the input ppplist (Chapter 28);
  • organizes these returns into an fvlist (Chapter 16) for each numeric-mark, named in the fashion of <numeric-mark>.<suffix>.

The S3 methods Gcross_.ppplist(), Kcross_.ppplist(), etc., in Table 28.1,

  • collects the return of the corresponding S3 method in Table 27.19, for each point-pattern (ppp.object, Chapter 27) in the input ppplist (Chapter 28);
  • organizes these returns into an fvlist (Chapter 16) for each multi-type-mark, named in the fashion of <multitype-mark>.<suffix>.

The S3 method nncross_.ppplist() in Table 28.1,

  • collects the return of the corresponding S3 method in Table 27.20, for each point-pattern (ppp.object, Chapter 27) in the input ppplist (Chapter 28);
  • organizes these returns into an anylist (Chapter 13) for each multi-type-mark, named in the fashion of <multitype-mark>.<suffix>.

The low-level utility function op_ppplist(), for batch operation on ppplist, is the underlying mechanism of the batch processes (Section 3.2). Function op_ppplist()

  • applies the operation to each point-pattern (ppp.object, Chapter 27) in the input, and returns a two-level hierarchical list, the first level corresponds to the individual point-patterns, and the second level corresponds to the eligible numeric-marks (for operations in Table 27.18) or eligible multi-type-marks (foroperations in Table 27.19 and Table 27.20);
  • ‘flips’ the hierarchy such that the first level represents the eligible marks, and the second level corresponds to the individual point-patterns.
Data: a ppplist object finp_s
finp = spatstat.data::finpines
append_marks(finp) = finp |> 
  spatstat.geom::cut.ppp(z = 'height', breaks = 2L, labels = c('L', 'H')) |>
  spatstat.geom::marks.ppp()
finp_s = finp |> 
  spatstat.geom::split.ppp(f = 'm3') |>
  spatstat.geom::solapply(FUN = spatstat.geom::subset.ppp, select = c('diameter', 'm3'))
finp_s
# List of point patterns
# 
# L:
# Marked planar point pattern: 77 points
# Mark variables: diameter, m3 
# window: rectangle = [-5, 5] x [-8, 2] metres
# 
# H:
# Marked planar point pattern: 49 points
# Mark variables: diameter, m3 
# window: rectangle = [-5, 5] x [-8, 2] metres
Example: function markcorr_.ppplist()
finp_s |>
  markcorr_()
# 
# $diameter.k
# An 'fvlist' of 2 fv.objects k[mm](r)
# Available rmax: 2.5
# Minimum Legal rmax: 2.5
Example: function nncross_.ppplist()
gorillas_group_dist = spatstat.data::gorillas |> 
  spatstat.geom::split.ppp(f = 'group') |>
  nncross_(i = 'dry', j = 'rainy')
# 
gorillas_group_dist |>
  lapply(FUN = \(i) {
    i |> lapply(FUN = summary)
  })
# $season.nncross
# $season.nncross$major
#    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#    0.00   43.56   67.63   80.18  107.80  263.51 
# 
# $season.nncross$minor
#    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#   9.117  43.825  74.924  92.316 113.309 361.513
gorillas_group_dist |>
  lapply(FUN = lengths)
# $season.nncross
# major minor 
#   150   125