23  ppp

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

library(groupedHyperframe)

Function spatstat.geom::ppp() creates a two-dimensional point-pattern object (ppp.object), i.e., an R object of S3 class 'ppp'. In addition to the existing S3 method dispatches to the class 'ppp' in the spatstat.* family of packages (Listing 23.1 - Listing 23.4),

Listing 23.1: Existing S3 method dispatches spatstat.geom::*.ppp
Code
suppressPackageStartupMessages(library(spatstat.geom))
methods(class = 'ppp', all.names = TRUE) |> 
  attr(which = 'info', exact = TRUE) |>
  subset.data.frame(subset = from == 'spatstat.geom')
#                       visible          from           generic  isS4
# [.ppp                    TRUE spatstat.geom                 [ FALSE
# [<-.ppp                  TRUE spatstat.geom               [<- FALSE
# affine.ppp               TRUE spatstat.geom            affine FALSE
# anyDuplicated.ppp        TRUE spatstat.geom     anyDuplicated FALSE
# as.data.frame.ppp        TRUE spatstat.geom     as.data.frame FALSE
# as.im.ppp                TRUE spatstat.geom             as.im FALSE
# as.layered.ppp           TRUE spatstat.geom        as.layered FALSE
# as.owin.ppp              TRUE spatstat.geom           as.owin FALSE
# as.ppp.ppp               TRUE spatstat.geom            as.ppp FALSE
# boundingbox.ppp          TRUE spatstat.geom       boundingbox FALSE
# boundingcentre.ppp       TRUE spatstat.geom    boundingcentre FALSE
# boundingcircle.ppp       TRUE spatstat.geom    boundingcircle FALSE
# boundingradius.ppp       TRUE spatstat.geom    boundingradius FALSE
# by.ppp                   TRUE spatstat.geom                by FALSE
# circumradius.ppp         TRUE spatstat.geom      circumradius FALSE
# closepairs.ppp           TRUE spatstat.geom        closepairs FALSE
# closing.ppp              TRUE spatstat.geom           closing FALSE
# connected.ppp            TRUE spatstat.geom         connected FALSE
# coords.ppp               TRUE spatstat.geom            coords FALSE
# coords<-.ppp             TRUE spatstat.geom          coords<- FALSE
# crossdist.ppp            TRUE spatstat.geom         crossdist FALSE
# crosspairs.ppp           TRUE spatstat.geom        crosspairs FALSE
# cut.ppp                  TRUE spatstat.geom               cut FALSE
# default.symbolmap.ppp    TRUE spatstat.geom default.symbolmap FALSE
# dilation.ppp             TRUE spatstat.geom          dilation FALSE
# distfun.ppp              TRUE spatstat.geom           distfun FALSE
# distmap.ppp              TRUE spatstat.geom           distmap FALSE
# domain.ppp               TRUE spatstat.geom            domain FALSE
# duplicated.ppp           TRUE spatstat.geom        duplicated FALSE
# edit.ppp                 TRUE spatstat.geom              edit FALSE
# erosion.ppp              TRUE spatstat.geom           erosion FALSE
# fardist.ppp              TRUE spatstat.geom           fardist FALSE
# flipxy.ppp               TRUE spatstat.geom            flipxy FALSE
# Frame<-.ppp              TRUE spatstat.geom           Frame<- FALSE
# has.close.ppp            TRUE spatstat.geom         has.close FALSE
# head.ppp                 TRUE spatstat.geom              head FALSE
# identify.ppp             TRUE spatstat.geom          identify FALSE
# intensity.ppp            TRUE spatstat.geom         intensity FALSE
# is.connected.ppp         TRUE spatstat.geom      is.connected FALSE
# is.empty.ppp             TRUE spatstat.geom          is.empty FALSE
# is.marked.ppp            TRUE spatstat.geom         is.marked FALSE
# is.multitype.ppp         TRUE spatstat.geom      is.multitype FALSE
# markformat.ppp           TRUE spatstat.geom        markformat FALSE
# marks.ppp                TRUE spatstat.geom             marks FALSE
# marks<-.ppp              TRUE spatstat.geom           marks<- FALSE
# multiplicity.ppp         TRUE spatstat.geom      multiplicity FALSE
# nncross.ppp              TRUE spatstat.geom           nncross FALSE
# nndist.ppp               TRUE spatstat.geom            nndist FALSE
# nnfun.ppp                TRUE spatstat.geom             nnfun FALSE
# nnwhich.ppp              TRUE spatstat.geom           nnwhich FALSE
# nobjects.ppp             TRUE spatstat.geom          nobjects FALSE
# npoints.ppp              TRUE spatstat.geom           npoints FALSE
# opening.ppp              TRUE spatstat.geom           opening FALSE
# pairdist.ppp             TRUE spatstat.geom          pairdist FALSE
# periodify.ppp            TRUE spatstat.geom         periodify FALSE
# persp.ppp                TRUE spatstat.geom             persp FALSE
# pixellate.ppp            TRUE spatstat.geom         pixellate FALSE
# plot.ppp                 TRUE spatstat.geom              plot FALSE
# print.ppp                TRUE spatstat.geom             print FALSE
# quadratcount.ppp         TRUE spatstat.geom      quadratcount FALSE
# quantess.ppp             TRUE spatstat.geom          quantess FALSE
# rebound.ppp              TRUE spatstat.geom           rebound FALSE
# relevel.ppp              TRUE spatstat.geom           relevel FALSE
# rescale.ppp              TRUE spatstat.geom           rescale FALSE
# rexplode.ppp             TRUE spatstat.geom          rexplode FALSE
# rjitter.ppp              TRUE spatstat.geom           rjitter FALSE
# rotate.ppp               TRUE spatstat.geom            rotate FALSE
# round.ppp                TRUE spatstat.geom             round FALSE
# rounding.ppp             TRUE spatstat.geom          rounding FALSE
# scalardilate.ppp         TRUE spatstat.geom      scalardilate FALSE
# shift.ppp                TRUE spatstat.geom             shift FALSE
# split.ppp                TRUE spatstat.geom             split FALSE
# split<-.ppp              TRUE spatstat.geom           split<- FALSE
# subset.ppp               TRUE spatstat.geom            subset FALSE
# summary.ppp              TRUE spatstat.geom           summary FALSE
# superimpose.ppp          TRUE spatstat.geom       superimpose FALSE
# tail.ppp                 TRUE spatstat.geom              tail FALSE
# text.ppp                 TRUE spatstat.geom              text FALSE
# unique.ppp               TRUE spatstat.geom            unique FALSE
# uniquemap.ppp            TRUE spatstat.geom         uniquemap FALSE
# unitname.ppp             TRUE spatstat.geom          unitname FALSE
# unitname<-.ppp           TRUE spatstat.geom        unitname<- FALSE
# unmark.ppp               TRUE spatstat.geom            unmark FALSE
# unstack.ppp              TRUE spatstat.geom           unstack FALSE
# Window.ppp               TRUE spatstat.geom            Window FALSE
# Window<-.ppp             TRUE spatstat.geom          Window<- FALSE
Listing 23.2: Existing S3 method dispatches spatstat.explore::*.ppp
Code
suppressPackageStartupMessages(library(spatstat.explore))
methods(class = 'ppp', all.names = TRUE) |> 
  attr(which = 'info', exact = TRUE) |>
  subset.data.frame(subset = from == 'spatstat.explore')
#                              visible             from                  generic  isS4
# auc.ppp                         TRUE spatstat.explore                      auc FALSE
# berman.test.ppp                 TRUE spatstat.explore              berman.test FALSE
# bw.abram.ppp                    TRUE spatstat.explore                 bw.abram FALSE
# bw.relrisk.ppp                  TRUE spatstat.explore               bw.relrisk FALSE
# cdf.test.ppp                    TRUE spatstat.explore                 cdf.test FALSE
# density.ppp                     TRUE spatstat.explore                  density FALSE
# densityAdaptiveKernel.ppp       TRUE spatstat.explore    densityAdaptiveKernel FALSE
# densityfun.ppp                  TRUE spatstat.explore               densityfun FALSE
# densityHeat.ppp                 TRUE spatstat.explore              densityHeat FALSE
# densityVoronoi.ppp              TRUE spatstat.explore           densityVoronoi FALSE
# envelope.ppp                    TRUE spatstat.explore                 envelope FALSE
# nnclean.ppp                     TRUE spatstat.explore                  nnclean FALSE
# nndensity.ppp                   TRUE spatstat.explore                nndensity FALSE
# pcf.ppp                         TRUE spatstat.explore                      pcf FALSE
# quadrat.test.ppp                TRUE spatstat.explore             quadrat.test FALSE
# relrisk.ppp                     TRUE spatstat.explore                  relrisk FALSE
# relriskHeat.ppp                 TRUE spatstat.explore              relriskHeat FALSE
# resolve.lambda.ppp              TRUE spatstat.explore           resolve.lambda FALSE
# resolve.lambdacross.ppp         TRUE spatstat.explore      resolve.lambdacross FALSE
# resolve.reciplambda.ppp         TRUE spatstat.explore      resolve.reciplambda FALSE
# rhohat.ppp                      TRUE spatstat.explore                   rhohat FALSE
# roc.ppp                         TRUE spatstat.explore                      roc FALSE
# scanmeasure.ppp                 TRUE spatstat.explore              scanmeasure FALSE
# sdr.ppp                         TRUE spatstat.explore                      sdr FALSE
# segregation.test.ppp            TRUE spatstat.explore         segregation.test FALSE
# sharpen.ppp                     TRUE spatstat.explore                  sharpen FALSE
# Smooth.ppp                      TRUE spatstat.explore                   Smooth FALSE
# Smoothfun.ppp                   TRUE spatstat.explore                Smoothfun FALSE
# SmoothHeat.ppp                  TRUE spatstat.explore               SmoothHeat FALSE
# spatialCovariateEvidence.ppp    TRUE spatstat.explore spatialCovariateEvidence FALSE
# SpatialMedian.ppp               TRUE spatstat.explore            SpatialMedian FALSE
# SpatialQuantile.ppp             TRUE spatstat.explore          SpatialQuantile FALSE
Listing 23.3: Existing S3 method dispatches spatstat.model::*.ppp
Code
suppressPackageStartupMessages(library(spatstat.model))
methods(class = 'ppp', all.names = TRUE) |> 
  attr(which = 'info', exact = TRUE) |>
  subset.data.frame(subset = from == 'spatstat.model')
#             visible           from generic  isS4
# kppm.ppp       TRUE spatstat.model    kppm FALSE
# lurking.ppp    TRUE spatstat.model lurking FALSE
# ppm.ppp        TRUE spatstat.model     ppm FALSE
Listing 23.4: Existing S3 method dispatches spatstat.random::*.ppp
Code
suppressPackageStartupMessages(library(spatstat.random))
methods(class = 'ppp', all.names = TRUE) |> 
  attr(which = 'info', exact = TRUE) |>
  subset.data.frame(subset = from == 'spatstat.random')
#            visible            from generic  isS4
# rshift.ppp    TRUE spatstat.random  rshift FALSE

Package groupedHyperframe (v0.3.0.20251020) implements more S3 method dispatches to the class 'ppp' (Listing 23.5, Table 23.1),

Listing 23.5: Table: S3 method dispatches groupedHyperframe::*.ppp
Code
methods2kable(class = 'ppp', package = 'groupedHyperframe', all.names = TRUE)
Table 23.1: S3 method dispatches groupedHyperframe::*.ppp (v0.3.0.20251020)
visible from generic isS4
.kmeans.ppp TRUE groupedHyperframe groupedHyperframe::.kmeans FALSE
.rmax.ppp TRUE groupedHyperframe groupedHyperframe::.rmax FALSE
aggregate_marks.ppp TRUE groupedHyperframe groupedHyperframe::aggregate_marks FALSE
append_marks<-.ppp TRUE groupedHyperframe groupedHyperframe::`append_marks<-` FALSE
density_marks.ppp TRUE groupedHyperframe groupedHyperframe::density_marks FALSE
Emark_.ppp TRUE groupedHyperframe groupedHyperframe::Emark_ FALSE
Gcross_.ppp TRUE groupedHyperframe groupedHyperframe::Gcross_ FALSE
is.numeric.ppp TRUE groupedHyperframe base::is.numeric FALSE
Jcross_.ppp TRUE groupedHyperframe groupedHyperframe::Jcross_ FALSE
Kcross_.ppp TRUE groupedHyperframe groupedHyperframe::Kcross_ FALSE
kerndens.ppp TRUE groupedHyperframe groupedHyperframe::kerndens FALSE
Kmark_.ppp TRUE groupedHyperframe groupedHyperframe::Kmark_ FALSE
Lcross_.ppp TRUE groupedHyperframe groupedHyperframe::Lcross_ FALSE
markconnect_.ppp TRUE groupedHyperframe groupedHyperframe::markconnect_ FALSE
markcorr_.ppp TRUE groupedHyperframe groupedHyperframe::markcorr_ FALSE
markvario_.ppp TRUE groupedHyperframe groupedHyperframe::markvario_ FALSE
Math.ppp TRUE groupedHyperframe methods::Math FALSE
na.exclude.ppp TRUE groupedHyperframe stats::na.exclude FALSE
na.omit.ppp TRUE groupedHyperframe stats::na.omit FALSE
nncross_.ppp TRUE groupedHyperframe groupedHyperframe::nncross_ FALSE
pairwise_cor_spatial.ppp TRUE groupedHyperframe groupedHyperframe::pairwise_cor_spatial FALSE
quantile.ppp TRUE groupedHyperframe stats::quantile FALSE
Vmark_.ppp TRUE groupedHyperframe groupedHyperframe::Vmark_ FALSE
Table 23.2: S3 method dispatches currently not planned for class 'ppp'
Table 23.2: S3 method dispatches currently not planned for class 'ppp'
Not Planned Explained in
na.fail.ppp(), na.pass.ppp() Section 28.1

23.1 Missing marks Handling

The S3 method dispatches na.omit.ppp() and na.exclude.ppp() omits and excludes, respectively, the missing marks from a ppp.object. Both functions return a ppp.object.

If missingness exists in the marks, the 'na.action'-attribute of the marks is saved as an attribute of the returned ppp.object.

Example: functions na.omit.ppp() and na.exclude.ppp() on 'none'-markformat
vesicles_omit = spatstat.data::vesicles |>
  na.omit()
vesicles_exclude = spatstat.data::vesicles |>
  na.exclude()
stopifnot(
  identical(vesicles_omit, spatstat.data::vesicles),
  identical(vesicles_exclude, spatstat.data::vesicles)
)
Example: functions na.omit.ppp() and na.exclude.ppp() on 'vector'-markformat, no missingness
ants_omit = spatstat.data::ants |>
  na.omit()
ants_exclude = spatstat.data::ants |>
  na.exclude()
stopifnot(
  identical(ants_omit, spatstat.data::ants),
  identical(ants_exclude, spatstat.data::ants)
)
Example: functions na.omit.ppp() and na.exclude.ppp() on 'dataframe'-markformat
spatstat.data::nbfires |> 
  spatstat.geom::npoints.ppp()
# [1] 7108
nbfires_omit = spatstat.data::nbfires |> 
  na.omit() 
nbfires_exclude = spatstat.data::nbfires |> 
  na.exclude() 
nbfires_omit |> 
  spatstat.geom::npoints.ppp()
# [1] 6989
nbfires_omit |> 
  attr(which = 'na.action', exact = TRUE) |> 
  attr(which = 'class', exact = TRUE)
# [1] "omit"
nbfires_exclude |> 
  attr(which = 'na.action', exact = TRUE) |> 
  attr(which = 'class', exact = TRUE)
# [1] "exclude"

Note that the function spatstat.geom::ppp() (v3.6.0.3) already removes missing \(x\)- and \(y\)-coords in the creation of a ppp.object.

Note that the S3 method dispatch spatstat.geom::plot.ppp() (v3.6.0.3) also removes missing marks (email with Dr. Baddeley, 2025-01-07).

23.2 Are marks numeric?

The S3 method dispatch is.numeric.ppp() determines whether the marks, if any, in a ppp.object are numeric.

Exception: function is.numeric.ppp() on 'none'-markformat
spatstat.data::vesicles |>
  is.numeric()
# logical(0)
Example: function is.numeric.ppp() on 'vector'-markformat
spatstat.data::longleaf |>
  is.numeric()
# [1] TRUE
spatstat.data::ants |>
  is.numeric()
# [1] FALSE

Note that the S3 method dispatches is.numeric.ppp() and spatstat.geom::is.multitype.ppp() behave differently for a ppp.object with 'dataframe'-markformat, e.g., betacells (Section 8.4).

Review: functions spatstat.geom::is.multitype.ppp() (Baddeley, Rubak, and Turner 2015) on 'dataframe'-markformat
spatstat.data::betacells |>
  spatstat.geom::is.multitype.ppp()
# [1] FALSE
Example: functions is.numeric.ppp() on 'dataframe'-markformat
spatstat.data::betacells |>
  is.numeric()
#  type  area 
# FALSE  TRUE

23.3 Math-groupGeneric of numeric-marks

The S3 method dispatch Math.ppp() performs the transformations, in the Math-groupGeneric, on one or more numeric-marks of a ppp.object, and returns a ppp.object with the transformed marks. The \(x\)- and \(y\)-coords and the multi-type marks of the input ppp.object remain unchanged. This function serves a similar purpose to the S3 method dispatch spatstat.geom::Math.im().

Listing 23.6 applies log- and log1p-transformations on the point-pattern object bronzefilter (Section 8.5) with 'vector'-markformat.

Listing 23.6: Example: log-transformations on 'vector'-markformat
Code
list(
  original = spatstat.data::bronzefilter,
  log = spatstat.data::bronzefilter |> log(),
  log1p = spatstat.data::bronzefilter |> log1p()
) |>
  lapply(FUN = \(i) {
    i |> 
      spatstat.geom::marks.ppp() |>
      summary.default()
  })
# $original
#    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#   0.013   0.120   0.160   0.167   0.200   0.467 
# 
# $log
#    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
# -4.3428 -2.1203 -1.8326 -1.8989 -1.6094 -0.7614 
# 
# $log1p
#    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
# 0.01292 0.11333 0.14842 0.15244 0.18232 0.38322

Listing 23.7 applies log-transformations on the numeric-marks in the point-pattern object betacells (Section 8.4) with 'dataframe'-markformat.

Listing 23.7: Example: log-transformations on numeric-marks in 'dataframe'-markformat
Code
list(
  original = spatstat.data::betacells,
  log = spatstat.data::betacells |> log()
) |>
  lapply(FUN = \(i) {
    i |> 
      spatstat.geom::marks.ppp() |>
      with.default(expr = area) |>
      summary.default()
  })
# $original
#    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#   168.3   248.8   279.4   291.2   324.2   514.4 
# 
# $log
#    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#   5.126   5.517   5.633   5.653   5.782   6.243

Listing 23.8 applies log-transformation on the \(x\)- and \(y\)-coordinates-only point-pattern object vesicles (Section 8.19).

Listing 23.8: Exception: log-transformations on 'none'-markformat
Code
list(
  spatstat.data::vesicles |> log(),
  spatstat.data::vesicles |> log1p(),
  spatstat.data::vesicles |> log2(),
  spatstat.data::vesicles |> log10()
) |> 
  vapply(FUN = identical, y = spatstat.data::vesicles, FUN.VALUE = NA) |>
  stopifnot()

23.4 Kernel Density of numeric-marks

The S3 generic function density_marks() finds the kernel densitys (Becker, Chambers, and Wilks 1988) of the numeric-marks.

The S3 method dispatch density_marks.ppp() finds the kernel densitys of one or more numeric-marks of a ppp.object.

Exception: function density_marks.ppp() on 'none'-markformat
spatstat.data::vesicles |>
  density_marks() |>
  is.null()
# [1] TRUE
Example: function density_marks.ppp() on numeric-marks in 'vector'-markformat
spatstat.data::longleaf |>
  density_marks()
# 
# Call:
#   density.default(x = m)
# 
# Data: m (584 obs.);   Bandwidth 'bw' = 4.615
# 
#        x                y            
#  Min.   :-11.84   Min.   :1.759e-06  
#  1st Qu.: 13.55   1st Qu.:1.665e-03  
#  Median : 38.95   Median :1.227e-02  
#  Mean   : 38.95   Mean   :9.823e-03  
#  3rd Qu.: 64.35   3rd Qu.:1.624e-02  
#  Max.   : 89.74   Max.   :2.320e-02
Exception: function density_marks.ppp() on multitype-marks in 'vector'-markformat
spatstat.data::ants |>
  density_marks() |>
  is.null()
# [1] TRUE
Example: function density_marks.ppp() on numeric-marks in 'dataframe'-markformat
spatstat.data::betacells |>
  density_marks()
# $area
# 
# Call:
#   density.default(x = `$area`)
# 
# Data: $area (135 obs.);   Bandwidth 'bw' = 18.99
# 
#        x               y            
#  Min.   :111.3   Min.   :1.736e-06  
#  1st Qu.:226.3   1st Qu.:1.385e-04  
#  Median :341.4   Median :9.455e-04  
#  Mean   :341.4   Mean   :2.170e-03  
#  3rd Qu.:456.4   3rd Qu.:4.087e-03  
#  Max.   :571.4   Max.   :7.065e-03

Table 23.3 shows the difference between the function density_marks.ppp() versus the S3 method dispatch spatstat.explore::density.ppp(),

Table 23.3: Kernel density estimates density_marks.ppp() vs. spatstat.explore::density.ppp()
spatstat.explore::density.ppp() density_marks.ppp()
Computes fixed-bandwidth kernel estimate (Diggle 1985) of the intensity function from a point pattern, i.e., the \(x\)- and \(y\)-coords only kernel density (Becker, Chambers, and Wilks 1988) of the numeric-mark(s)
Returns an im.object (Chapter 19) a (list of) stats::density object(s)
Review: function spatstat.explore::density.ppp() (Baddeley, Rubak, and Turner 2015)
a1 = spatstat.data::betacells |> 
  spatstat.explore::density.ppp()
a0 = spatstat.data::betacells |>
  spatstat.geom::unmark.ppp() |>
  spatstat.explore::density.ppp()
stopifnot(identical(a0, a1))

23.5 Kernel Density Estimates of numeric-marks

The S3 generic function kerndens() extracts the $y element of a density object.

The default dispatch kerndens.default() is simply a wrapper of stats::density.default()$y.

Example: function kerndens.default()
d = faithful$eruptions |> 
  density(bw = 'sj')
kd = faithful$eruptions |> 
  kerndens(bw = 'sj')
stopifnot(identical(d$y, kd))

The S3 method dispatch kerndens.ppp() finds the kernel density estimates of one or more numeric-marks of a ppp.object. The S3 method dispatch kerndens.ppp() is simply a wrapper of the S3 method dispatch density_marks.ppp() (Section 23.4).

Exception: function kerndens.ppp() on 'none'-markformat
spatstat.data::vesicles |>
  kerndens() |>
  is.null()
# [1] TRUE
Example: function kerndens.ppp() on numeric-marks in 'vector'-markformat
spatstat.data::longleaf |>
  kerndens(n = 8L)
# [1] 8.403103e-05 2.019181e-02 1.471192e-02 1.438080e-02 1.612033e-02 4.244355e-03 6.127754e-04 1.759222e-06
Exception: function kerndens.ppp() on multitype-marks in 'vector'-markformat
spatstat.data::ants |>
  kerndens() |>
  is.null()
# [1] TRUE
Example: function kerndens.ppp() on numeric-marks in 'dataframe'-markformat
spatstat.data::betacells |>
  kerndens(n = 8L)
# $area
# [1] 2.530826e-06 9.483771e-04 6.254928e-03 5.117580e-03 2.486671e-03 6.781429e-04 1.411284e-04 1.736171e-06

23.6 Quantile of numeric-marks

The S3 method dispatch quantile.ppp() finds the quantiles of one or more numeric marks of a ppp.object.

Example: function quantile.ppp() on 'none'-markformat
spatstat.data::vesicles |>
  quantile() |>
  is.null()
# [1] TRUE
Example: function quantile.ppp() on numeric-marks in 'vector'-markformat
spatstat.data::longleaf |>
  quantile()
#     0%    25%    50%    75%   100% 
#  2.000  9.100 26.150 42.125 75.900
Example: function quantile.ppp() on multitype-marks in 'vector'-markformat
spatstat.data::ants |>
  quantile() |>
  is.null()
# [1] TRUE
Example: function quantile.ppp() on numeric-marks in 'dataframe'-markformat
spatstat.data::betacells |>
  quantile()
# $area
#     0%    25%    50%    75%   100% 
# 168.30 248.85 279.40 324.25 514.40
spatstat.data::finpines |>
  quantile()
# $diameter
#   0%  25%  50%  75% 100% 
#    0    1    2    3    7 
# 
# $height
#    0%   25%   50%   75%  100% 
# 0.800 1.825 2.850 3.600 5.400

Note that the S3 method dispatch quantile.ppp() is completely different from the S3 method dispatch spatstat.explore::SpatialQuantile.ppp().

23.7 Nearest Neighbours of multitype-marks, Alternative Interface

Function .nncross() is a wrapper of function spatstat.geom::nncross(., what = 'dist'), designed to provide an interface consistent with function spatstat.explore::Gcross(), etc. This design allows the internal batch-processing mechanism (Section 23.15) to be shared between Table 23.10 and Table 23.11.

Example: interface of functions .nncross() and spatstat.explore::Gcross()
.nncross |> 
  args()
# function (X, i, j, ...) 
# NULL
spatstat.explore::Gcross |>
  args()
# function (X, i, j, r = NULL, breaks = NULL, ..., correction = c("rs", 
#     "km", "han")) 
# NULL

To compute the distance to the nearest neighbour of points with 'Messor'-mark for each point with 'Cataglyphis'-mark in the point-pattern object ants (Section 8.2), one option (Listing 23.9) is to use function spatstat.geom::split.ppp(), then function spatstat.geom::nncross.ppp().

Listing 23.9: Review: function spatstat.geom::nncross.ppp() (Baddeley, Rubak, and Turner 2015)
Code
nn = spatstat.data::ants |>
  spatstat.geom::split.ppp() |>
  with.default(expr = {
    spatstat.geom::nncross.ppp(X = Cataglyphis, Y = Messor, what = 'dist')
  })
nn |>
  summary()
#    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#   12.21   21.38   31.38   36.27   42.52   90.26

Alternative approaches are to use function .nncross(), specifying the parameters i and j

  • as integer indices corresponding to the levels of the multitype-marks in ants (Section 8.2), i.e, 1L for 'Cataglyphis' and 2L for 'Messor', as in Listing 23.10.
  • as character levels of the multitype-marks in ants (Section 8.2), as in Listing 23.11.
Listing 23.10: Example: function .nncross() with integer levels indices
nn1 = spatstat.data::ants |> 
  .nncross(i = 1L, j = 2L)
stopifnot(identical(nn, nn1))
Listing 23.11: Example: function .nncross() with character levels
nn2 = spatstat.data::ants |> 
  .nncross(i = 'Cataglyphis', j = 'Messor')
stopifnot(identical(nn, nn2))

Function .nncross() returns an invisible NULL-value (Listing 23.12) if the character values supplied to the i and j parameters do not match any levels of the multitype-marks in ants (Section 8.2).

Listing 23.12: Exception: function .nncross(), non-existing levels, exception handling
Code
spatstat.data::ants |>
  .nncross(i = 'a', j = 'b') |>
  is.null()
# [1] TRUE

23.8 Aggregation of marks

The S3 generic function aggregate_marks() aggregates various statistics (other than the quantiles (Section 23.6)) of the marks of a point-pattern object.

The S3 method dispatch aggregate_marks.ppp()

  • for each type of markformat of the input point-pattern object,
    • 'dataframe': aggregates the numeric- and/or multitype-marks, according to a grouping structure determined by one-or-more multitype-marks specified in the parameter by, for the summary statistics specified in the parameter FUN, using the workhorse function stats::aggregate.data.frame();
    • 'vector': computes the summary statistics specified in the parameter FUN of the numeric- or multitype-marks;
    • 'none': returns an invisible NULL-value.
  • (optional) vectorizes the aggregated summary statistics for downstream use (Section 24.4).

Listing 23.13 demonstrates the exception handling with the point-pattern object vesicles (Section 8.19) without any mark.

Listing 23.13: Exception: function aggregate_marks.ppp(), exception handling
Code
spatstat.data::vesicles |> 
  aggregate_marks() |>
  is.null()
# [1] TRUE

Listing 23.14 and Listing 23.15 aggregate the sample mean, or both the sample mean and the sample standard deviation sd, of the numeric-mark in the point-pattern object spruces (Section 8.17).

Listing 23.14: Example: function aggregate_marks.ppp(), for sample mean
spatstat.data::spruces |> 
  aggregate_marks(FUN = mean)
#      mean 
# 0.2503731
Listing 23.15: Example: function aggregate_marks.ppp(), for sample mean and sd
spatstat.data::spruces |> 
  aggregate_marks(FUN = \(z) c(mean = mean(z), sd = sd(z)))
#       mean         sd 
# 0.25037313 0.04697474

Listing 23.16 and Listing 23.17 aggregate the (relative) frequency of the multitype-mark in the point-pattern object ants (Section 8.2).

Listing 23.16: Example: function aggregate_marks.ppp(), for frequency
spatstat.data::ants |> 
  aggregate_marks(FUN = table)
# Cataglyphis      Messor 
#          29          68
Listing 23.17: Example: function aggregate_marks.ppp(), for relative frequency
spatstat.data::ants |> 
  aggregate_marks(FUN = \(z) table(z)/length(z))
# Cataglyphis      Messor 
#   0.2989691   0.7010309

Listing 23.18 and Listing 23.19 aggregate the numeric-mark area by the multitype-mark type of the point-pattern object betacells (Section 8.4), using the sample mean, or both the sample mean and the sample standard deviation sd. Listing 23.20 and Listing 23.21 vectorize the returns.

Listing 23.18: Example: function aggregate_marks.ppp(), for sample mean of area-by-type
spatstat.data::betacells |>
  aggregate_marks(by = area ~ type, FUN = mean)
#   type     area
# 1  off 259.7214
# 2   on 325.1169
Listing 23.19: Example: function aggregate_marks.ppp(), for sample mean and sd of area-by-type
spatstat.data::betacells |>
  aggregate_marks(by = area ~ type, FUN = \(z) {
    c(mean = mean(z), sd = sd(z))
  })
#   type area.mean   area.sd
# 1  off 259.72143  40.86083
# 2   on 325.11692  60.71534
Listing 23.20: Example: function aggregate_marks.ppp(), for sample mean of area-by-type, vectorized
spatstat.data::betacells |>
  aggregate_marks(by = area ~ type, FUN = mean, vectorize = TRUE)
# off.area  on.area 
# 259.7214 325.1169
Listing 23.21: Example: function aggregate_marks.ppp(), for sample mean and sd of area-by-type, vectorized
spatstat.data::betacells |>
  aggregate_marks(by = area ~ type, FUN = \(z) {
    c(mean = mean(z), sd = sd(z))
  }, vectorize = TRUE)
# off.area.mean   off.area.sd  on.area.mean    on.area.sd 
#     259.72143      40.86083     325.11692      60.71534

Listing 23.23 aggregates the non-missing values (Section 23.1) of the log1p-transformed numeric-mark (Section 23.3, Listing 23.22) fnl.size by the interaction of two multitype-marks fire.type:cause of the point-pattern object nbfires (Section 8.15), using the sample mean and the sample standard deviation sd. Listing 23.24 vectorizes the return.

Listing 23.22: Data: nbfL
Code
nbfL = spatstat.data::nbfires |>
  na.omit() |>
  spatstat.geom::subset.ppp(
    subset = (fire.type %in% c('forest', 'grass')) & (cause %in% c('rrds', 'rec')), 
    select = c('fire.type', 'cause', 'fnl.size')
  ) |>
  log1p()
Listing 23.23: Example: function aggregate_marks.ppp(), for sample mean and sd of log1p-transformed fnl.size-by-fire.type:cause
nbfL |>
  aggregate_marks(by = fnl.size ~ fire.type:cause, FUN = \(z) {
    c(mean = mean(z), sd = sd(z))
  })
#   fire.type cause fnl.size.mean fnl.size.sd
# 1    forest  rrds     0.8647287   0.9269129
# 2     grass  rrds     0.3416982   0.4392035
# 3    forest   rec     0.4799249   0.7390220
# 4     grass   rec     0.3740750   0.5717532
Listing 23.24: Example: function aggregate_marks.ppp(), for sample mean and sd of log1p-transformed fnl.size-by-fire.type:cause, vectorized
nbfL |>
  aggregate_marks(by = fnl.size ~ fire.type:cause, FUN = \(z) {
    c(mean = mean(z), sd = sd(z))
  }, vectorize = TRUE)
# forest.rrds.fnl.size.mean   forest.rrds.fnl.size.sd  grass.rrds.fnl.size.mean    grass.rrds.fnl.size.sd 
#                 0.8647287                 0.9269129                 0.3416982                 0.4392035 
#  forest.rec.fnl.size.mean    forest.rec.fnl.size.sd   grass.rec.fnl.size.mean     grass.rec.fnl.size.sd 
#                 0.4799249                 0.7390220                 0.3740750                 0.5717532

Listing 23.25 and Listing 23.26 aggregate one multitype-mark season by another multitype-mark group of the point-pattern object gorillas (Section 8.11), using the (relative) frequency. Listing 23.27 and Listing 23.28 vectorize the returns.

Listing 23.25: Example: function aggregate_marks.ppp(), for frequency of season-by-group
spatstat.data::gorillas |>
  aggregate_marks(by = season ~ group, FUN = table)
#   group season.dry season.rainy
# 1 major        150          200
# 2 minor        125          172
Listing 23.26: Example: function aggregate_marks.ppp(), for relative frequency of season-by-group
spatstat.data::gorillas |>
  aggregate_marks(by = season ~ group, FUN = \(z) table(z)/length(z))
#   group season.dry season.rainy
# 1 major  0.4285714    0.5714286
# 2 minor  0.4208754    0.5791246
Listing 23.27: Example: function aggregate_marks.ppp(), for frequency of season-by-group, vectorized
spatstat.data::gorillas |>
  aggregate_marks(by = season ~ group, FUN = table, vectorize = TRUE)
#   major.season.dry major.season.rainy   minor.season.dry minor.season.rainy 
#                150                200                125                172
Listing 23.28: Example: function aggregate_marks.ppp(), for relative frequency of season-by-group, vectorized
spatstat.data::gorillas |>
  aggregate_marks(by = season ~ group, FUN = \(z) {
    table(z)/length(z)
  }, vectorize = TRUE)
#   major.season.dry major.season.rainy   minor.season.dry minor.season.rainy 
#          0.4285714          0.5714286          0.4208754          0.5791246

23.9 Extract via [

In an email to the authors, Prof. Adrian Baddeley (Baddeley and Turner 2005) has kindly explained that in the package spatstat.geom (v3.6.0.3)

The design of the S3 class 'ppp' specifies that if the marks are a data.frame with only 1 column, then the marks will be converted to a vector.

This design choice was made a long time ago, in order to avoid problems that would otherwise occur in the rest of the spatstat code.

If we were to retrospectively change the specification, we would have a lot of work to do in the rest of the code, and the documentation.

On the other hand, function grouped_ppp() (Chapter 3, Section 16.1) relies on the support of ncol-1L 'dataframe'-marks. As an ad hoc solution, the authors defined

  • an (internal) derived class 'ppp_tzh' (initials of T. Zhan) that inherits from a ppp.object, and
  • an S3 method dispatch `[.ppp_tzh` that respects the ncol-1L 'dataframe'-marks. The S3 method dispatch `[.ppp_tzh` is a teeny-tiny modification of the S3 method dispatch spatstat.geom::`[.ppp`. Permision from Dr. Baddeley? GPL-2?

Listing 23.29 retains the name of the mark hladr as the column name of the ncol-1L 'dataframe'-marks (Listing 23.30), for downstream analysis.

Listing 23.29: Example: function grouped_ppp() with one mark
s_a = wrobel_lung |>
   grouped_ppp(formula = hladr ~ OS + gender + age | patient_id/image_id)
Listing 23.30: Example: support of ncol-1L 'dataframe'-markformat, name of mark retained
s_a$ppp.[[1L]] |>
  spatstat.geom::marks.ppp(drop = FALSE) |> 
  head(n = 3L)
#   hladr
# 1 0.115
# 2 0.239
# 3 0.268

23.10 Append to (Existing) marks

The S3 generic syntactic sugar append_marks<-() appends an additional mark to the existing marks.

The S3 method dispatch append_marks<-.ppp() appends an additional mark to the (existing) marks of an ppp.object.

Example: function append_marks<-.ppp(), no existing marks
ves = spatstat.data::vesicles
(npt = spatstat.geom::npoints.ppp(ves))
# [1] 37
set.seed(12); append_marks(ves) = rlnorm(n = npt)
ves |> 
  spatstat.geom::print.ppp()
# Marked planar point pattern: 37 points
# marks are numeric, of storage type  'double'
# window: polygonal boundary
# enclosing rectangle: [22.6796, 586.2292] x [11.9756, 1030.7] nm
set.seed(31); append_marks(ves) = replicate(n = 2L, expr = rpois(n = npt, lambda = 4), simplify = FALSE)
ves |> 
  spatstat.geom::print.ppp()
# Marked planar point pattern: 37 points
# Mark variables: m1, m2, m3 
# window: polygonal boundary
# enclosing rectangle: [22.6796, 586.2292] x [11.9756, 1030.7] nm
Example: function append_marks<-.ppp(), existing numeric-marks
spru = spatstat.data::spruces
set.seed(23); append_marks(spru) = rlnorm(n = spatstat.geom::npoints.ppp(spru))
spru |> 
  spatstat.geom::print.ppp()
# Marked planar point pattern: 134 points
# Mark variables: m1, m2 
# window: rectangle = [0, 56] x [0, 38] metres
Example: function append_marks<-.ppp(), existing multitype marks
ant = spatstat.data::ants
set.seed(42); append_marks(ant) = rlnorm(n = spatstat.geom::npoints.ppp(ant))
ant |> 
  spatstat.geom::print.ppp()
# Marked planar point pattern: 97 points
# Mark variables: m1, m2 
# window: polygonal boundary
# enclosing rectangle: [-25, 803] x [-49, 717] units (one unit = 0.5 feet)
Example: function append_marks<-.ppp(), existing dataframe marks
goril = spatstat.data::gorillas
set.seed(33); append_marks(goril) = rlnorm(n = spatstat.geom::npoints.ppp(goril))
goril |> 
  spatstat.geom::print.ppp()
# Marked planar point pattern: 647 points
# Mark variables: group, season, date, m4 
# window: polygonal boundary
# enclosing rectangle: [580457.9, 585934] x [674172.8, 678739.2] metres

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

The S3 generic function .rmax() provides the default \(r_\text{max}\) used in functions from package spatstat.explore (v3.5.3.3) that return an fv.object, i.e., the workhorse functions in Table 23.9 and Table 23.10.

The S3 method dispatch .rmax.ppp() finds the default \(r_\text{max}\) used by various functions applicable to a ppp.object and returning an fv.object. It is

Table 23.4: an off-label use of functions spatstat.explore::rmax.rule() and spatstat.geom::handle.r.b.args()
Table 23.4: Default \(r_\text{max}\) used in functions from package spatstat.explore (v3.5.3.3) that return an fv.object
Function from package spatstat.explore Call of spatstat.explore::rmax.rule Default \(r_\text{max}\) via .rmax()
markcorr, the workhorse function of Emark and Vmark, markvario rmax.rule(fun = 'K', ...) .rmax(fun = 'K')
Kinhom, the workhorse function of Kmark and markcorrint rmax.rule(fun = 'K', ...) .rmax(fun = 'K')
Kcross, and its workhorse functions Kest and Kmulti rmax.rule(fun = 'K', ...) .rmax(fun = 'K') or .rmax(fun = 'K', i, j)
Gcross, and its workhorse functions Gest and Gmulti rmax.rule(fun = 'G', ...) .rmax(fun = 'G') or .rmax(fun = 'G', i, j)
Jcross, and its workhorse functions Jest and Jmulti rmax.rule(fun = 'J', ...) .rmax(fun = 'J') or .rmax(fun = 'J', i, j)
Advanced: function .rmax.ppp() for spatstat.explore::markcorr() on numeric- and/or integer-mark in 'vector'-markformat
sprucesK_r1 = spatstat.data::spruces |>
  spatstat.explore::markcorr() |>
  .rmax.fv()
sprucesK_r2 = spatstat.data::spruces |> 
  .rmax(fun = 'K')
stopifnot(identical(sprucesK_r1, sprucesK_r2))
anemonesK_r1 = spatstat.data::anemones |>
  spatstat.explore::markcorr() |>
  .rmax.fv()
anemonesK_r2 = spatstat.data::anemones |> 
  .rmax(fun = 'K')
stopifnot(identical(anemonesK_r1, anemonesK_r2))
Advanced: function .rmax.ppp() for spatstat.explore::markcorr() on numeric-mark in 'dataframe'-markformat
spatstat.data::finpines |>
  spatstat.explore::markcorr() |>
  vapply(FUN = .rmax.fv, FUN.VALUE = NA_real_)
# diameter   height 
#      2.5      2.5
spatstat.data::finpines |>
  .rmax(fun = 'K')
# [1] 2.5
Advanced: function .rmax.ppp() for spatstat.explore::Gcross() on multitype-mark
r1a = spatstat.data::ants |>
  spatstat.explore::Gcross(i = 'Messor', j = 'Cataglyphis') |>
  .rmax.fv()
r2a = spatstat.data::ants |> 
  .rmax(fun = 'G', i = 'Messor', j = 'Cataglyphis')
stopifnot(identical(r1a, r2a))
r1b = spatstat.data::ants |>
  spatstat.explore::Gcross(i = 'Cataglyphis', j = 'Messor') |>
  .rmax.fv()
r2b = spatstat.data::ants |> 
  .rmax(fun = 'G', i = 'Cataglyphis', j = 'Messor')
stopifnot(identical(r1b, r2b))
r1c = spatstat.data::ants |>
  spatstat.explore::Gcross(i = 'Messor', j = 'Messor') |>
  .rmax.fv()
r2c = spatstat.data::ants |> 
  .rmax(fun = 'G', i = 'Messor', j = 'Messor')
stopifnot(identical(r1c, r2c))
r1d = spatstat.data::ants |>
  spatstat.explore::Gcross(i = 'Cataglyphis', j = 'Cataglyphis') |>
  .rmax.fv()
r2d = spatstat.data::ants |> 
  .rmax(fun = 'G', i = 'Cataglyphis', j = 'Cataglyphis')
stopifnot(identical(r1d, r2d))
Advanced: function .rmax.ppp() for spatstat.explore::Jcross() on multitype-mark
r1a = spatstat.data::ants |>
  spatstat.explore::Jcross(i = 'Messor', j = 'Cataglyphis') |>
  .rmax.fv()
r2a = spatstat.data::ants |> 
  .rmax(fun = 'J', i = 'Messor', j = 'Cataglyphis')
stopifnot(identical(r1a, r2a))
r1b = spatstat.data::ants |>
  spatstat.explore::Jcross(i = 'Cataglyphis', j = 'Messor') |>
  .rmax.fv()
r2b = spatstat.data::ants |> 
  .rmax(fun = 'J', i = 'Cataglyphis', j = 'Messor')
stopifnot(identical(r1b, r2b))
r1c = spatstat.data::ants |>
  spatstat.explore::Jcross(i = 'Messor', j = 'Messor') |>
  .rmax.fv()
r2c = spatstat.data::ants |> 
  .rmax(fun = 'J', i = 'Messor', j = 'Messor')
stopifnot(identical(r1c, r2c))
r1d = spatstat.data::ants |>
  spatstat.explore::Jcross(i = 'Cataglyphis', j = 'Cataglyphis') |>
  .rmax.fv()
r2d = spatstat.data::ants |> 
  .rmax(fun = 'J', i = 'Cataglyphis', j = 'Cataglyphis')
stopifnot(identical(r1d, r2d))

Additional S3 method dispatches are those to fv.object (Section 13.2), ppplist (Section 24.5) and to hyperframe (Section 17.9).

23.12 \(k\)-Means Clustering

The S3 generic function .kmeans() performs \(k\)-means clustering (Hartigan and Wong 1979). The authors name this generic function with a prefix . because the workhorse function stats::kmeans() shipped with R version 4.5.1 (2025-06-13) is not an S3 generic function. Note that to reproduce a \(k\)-means clustering using stats::kmeans(), readers must set the .Random.seed beforehand.

The S3 method dispatch .kmeans.ppp() has parameters

  • formula, \(x\)- and/or \(y\)- coordinate(s) and/or (one or more of the) numeric-marks
  • (optional) centers, number of clusters
  • (optional) clusterSize, “expected” number of points per cluster.

User should specify one of the two optional parameters centers and clusterSize. If both are specified, then parameter clusterSize takes priority and parameter centers is ignored.

The S3 method dispatch .kmeans.ppp() returns an object of S3 class 'pppkm', which inherits from the class 'ppp' with additional attributes,

  • attr(.,'f'), a factor indicating the \(k\)-means clustering indices.

Package groupedHyperframe (v0.3.0.20251020) implements the following S3 method dispatches to the class 'pppkm' (Table 23.1),

Table: S3 method dispatches groupedHyperframe::*.pppkm
methods2kable(class = 'pppkm', package = 'groupedHyperframe', all.names = TRUE)
Table 23.5: S3 method dispatches groupedHyperframe::*.pppkm (v0.3.0.20251020)
visible from generic isS4
print.pppkm TRUE groupedHyperframe base::print FALSE
split.pppkm TRUE groupedHyperframe base::split FALSE

Most of the S3 method dispatches in Table 23.5 are straightforward extensions of their counterparts for the class 'ppp' (Chapter 23).

Table 23.6: Inheritance of S3 Class 'pppkm'
S3 Method Dispatch … is a trivial wrapper of
print.pppkm() spatstat.geom::print.ppp()
split.pppkm(), Section 23.12.2 spatstat.geom::split.ppp()

23.12.1 Examples

Listing 23.31 - Listing 23.33 perform \(k\)-means clustering in the \(x\)- and \(y\)-coordinates-only point-pattern object vesicles (Section 8.19).

Listing 23.31: Example: function .kmeans.ppp(); cluster vesicles by ~ x
set.seed(12); spatstat.data::vesicles |> 
  .kmeans(formula = ~ x, centers = 3L)
# Planar point pattern: 37 points
# window: polygonal boundary
# enclosing rectangle: [22.6796, 586.2292] x [11.9756, 1030.7] nm
# with k-means clustering of 10, 15, 12 points
Listing 23.32: Example: function .kmeans.ppp(); cluster vesicles by ~ x + y
set.seed(21); spatstat.data::vesicles |> 
  .kmeans(formula = ~ x + y, centers = 3L)
# Planar point pattern: 37 points
# window: polygonal boundary
# enclosing rectangle: [22.6796, 586.2292] x [11.9756, 1030.7] nm
# with k-means clustering of 11, 10, 16 points
Listing 23.33: Example: function .kmeans.ppp(); cluster vesicles by ~ x + y and parameter clusterSize
set.seed(43); spatstat.data::vesicles |> 
  .kmeans(formula = ~ x + y, clusterSize = 10L)
# Planar point pattern: 37 points
# window: polygonal boundary
# enclosing rectangle: [22.6796, 586.2292] x [11.9756, 1030.7] nm
# with k-means clustering of 9, 10, 12, 6 points

Listing 23.34 - Listing 23.35 perform \(k\)-means clustering in the point-pattern object spruces (Section 8.17) with 'vector'-markformat.

Listing 23.34: Example: function .kmeans.ppp(); cluster spruces by ~ x + marks
set.seed(30); spatstat.data::spruces |> 
  .kmeans(formula = ~ x + marks, centers = 3L)
# Marked planar point pattern: 134 points
# marks are numeric, of storage type  'double'
# window: rectangle = [0, 56] x [0, 38] metres
# with k-means clustering of 47, 39, 48 points
Listing 23.35: Example: function .kmeans.ppp(); cluster spruces by ~ x + y + marks
set.seed(62); spatstat.data::spruces |> 
  .kmeans(formula = ~ x + y + marks, centers = 3L)
# Marked planar point pattern: 134 points
# marks are numeric, of storage type  'double'
# window: rectangle = [0, 56] x [0, 38] metres
# with k-means clustering of 40, 38, 56 points

Listing 23.36 - Listing 23.37 perform \(k\)-means clustering in the point-pattern object finpines (Section 8.9) with 'dataframe'-markformat.

Listing 23.36: Example: function .kmeans.ppp(); cluster finpines by ~ x + y + height
set.seed(18); spatstat.data::finpines |> 
  .kmeans(formula = ~ x + y + height, centers = 3L)
# Marked planar point pattern: 126 points
# Mark variables: diameter, height 
# window: rectangle = [-5, 5] x [-8, 2] metres
# with k-means clustering of 38, 42, 46 points
Listing 23.37: Example: function .kmeans.ppp(); cluster finpines by ~ x + diameter + height
set.seed(20); spatstat.data::finpines |> 
  .kmeans(formula = ~ x + diameter + height, centers = 3L)
# Marked planar point pattern: 126 points
# Mark variables: diameter, height 
# window: rectangle = [-5, 5] x [-8, 2] metres
# with k-means clustering of 37, 45, 44 points

23.12.2 Split by \(k\)-Means Clustering

The S3 method dispatch split.pppkm() splits a 'pppkm'-object by its \(k\)-means clustering indices.

The authors use the hyper data frame flu (Section 8.10) to illustrate the concept of split-ting by \(k\)-means clustering.

Example: function split.pppkm()
set.seed(15); spatstat.data::flu$pattern[[1L]] |> 
  .kmeans(formula = ~ x + y, centers = 3L) |>
  split()
# Point pattern split by factor 
# 
# 1:
# Marked planar point pattern: 169 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# 2:
# Marked planar point pattern: 157 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm
# 
# 3:
# Marked planar point pattern: 145 points
# Multitype, with levels = M2, M1 
# window: rectangle = [0, 3331] x [0, 3331] nm

23.13 Pairwise Tjøstheim (1978)’s Coefficient

The S3 generic function pairwise_cor_spatial() calculates the nonparametric, rank-based, Tjøstheim (1978)’s correlation coefficients (Hubert and Golledge 1982) in a pairwise-combination fashion, using the workhorse function SpatialPack::cor.spatial() (Vallejos, Osorio, and Bevilacqua 2020, v0.4.1).

All S3 method dispatches of the generic function pairwise_cor_spatial() return an object of S3 class 'pairwise_cor_spatial', which inherits from the class 'dist' defined in package stats shipped with R version 4.5.1 (2025-06-13). Such inheritance enables us to make use of existing S3 method dispatches to class 'dist', e.g., stats:::print.dist(), stats:::as.matrix.dist(), stats:::format.dist() and stats:::labels.dist(). Table 23.7 explains the motivation of this inheritance, that the class 'pairwise_cor_spatial' shares intrinsic similarity in data structure as the class 'dist'.

Table 23.7: Similarity in Data Structure, 'pairwise_cor_spatial' & 'dist'
'pairwise_cor_spatial' object 'dist' object
Constant diagonal values of 1; i.e., perfect correlation of 0; i.e., zero distance

Package groupedHyperframe implements the following S3 method dispatches to the class 'pairwise_cor_spatial' (Table 23.8),

Table: S3 method dispatches groupedHyperframe::*.ppp
methods2kable(class = 'pairwise_cor_spatial', package = 'groupedHyperframe', all.names = TRUE)
Table 23.8: S3 method dispatches groupedHyperframe::*.pairwise_cor_spatial (v0.3.0.20251020)
visible from generic isS4
as.matrix.pairwise_cor_spatial TRUE groupedHyperframe base::as.matrix FALSE

The S3 method dispatch pairwise_cor_spatial.ppp() finds the nonparametric Tjøstheim (1978)’s correlation coefficients from the pairwise-combinations of all numeric-marks in a ppp.object, e.g., finpines (Section 8.9).

Example: function pairwise_cor_spatial.ppp()
finpines_paircor = spatstat.data::finpines |>
  pairwise_cor_spatial()

The S3 method dispatch stats:::print.dist() displays a 'pairwise_cor_spatial' object.

A pairwise_cor_spatial object finpines_paircor
finpines_paircor
#         diameter
# height 0.7287879

The S3 method dispatch as.matrix.pairwise_cor_spatial() returns a matrix of pairwise Tjøstheim (1978)’s coefficients with diagonal values of 1. This matrix, however, is not a correlation matrix, because Tjøstheim (1978)’s correlation coefficient

  • is nonparametric, i.e., there is no definition of the corresponding covariance, standard deviation sd, nor the conversion cov2cor method;
  • does not provide a mathematical mechanism to ensure that this matrix is positive definite.
Example: function as.matrix.pairwise_cor_spatial()
finpines_paircor |> 
  as.matrix()
#           diameter    height
# diameter 1.0000000 0.7287879
# height   0.7287879 1.0000000

23.14 🚧 Global Envelope Test

🚧 This section is under construction. Expected delivery by 2025-12-31.

Global envelope test GET::global_envelope_test() (Myllymäki and Mrkvička 2024, v1.0.7).

23.15 Batch Process on Eligible marks

Table 23.9, Table 23.10 and Table 23.11 delve into the intricate mechanics of the batch processes (Section 3.2), offering insights that will resonate with advanced R practitioners.

Each batch process listed in Table 23.9,

  • identifies all eligible numeric-marks in the input ppp.object (Chapter 23);
  • applies the workhorse function from package spatstat.explore per numeric-mark;
  • returns a list of fv.objects, named in the fashion of <numeric-mark>.<suffix>, where <suffix> is determined by <fv.object> |> attr(which = 'fname'). Note that the returned object is not an fvlist (Chapter 14).
Table 23.9: Batch processes; eligible numeric-marks to fv.objects
Batch Process Workhorse function in Package spatstat.explore fv.object Suffix
Emark_.ppp(), Vmark_.ppp() Emark and Vmark, conditional mean \(E(r)\) and variance \(V(r)\), diagnostics for dependence between the points and the marks (Schlather, Ribeiro, and Diggle 2003) .E, .V
markcorr_.ppp() markcorr, marked correlation \(k_{mm}(r)\) or generalized mark correlation \(k_f(r)\) (Stoyan and Stoyan 1994) .k
markvario_.ppp() markvario, mark variogram \(\gamma(r)\) (Wälder and Stoyan 1996) .gamma
Kmark_.ppp() Kmark, mark-weighted \(K_f(r)\) function (Penttinen, Stoyan, and Henttonen 1992) .K

Each batch process listed in Table 23.10,

  • identifies all eligible multitype-marks in the input ppp.object (Chapter 23);
  • applies the workhorse function from package spatstat.explore per multitype-mark;
  • returns a list of fv.objects, named in the fashion of <multitype-mark>.<suffix>, where <suffix> is determined by <fv.object> |> attr(which = 'fname'). Note that the returned object is not an fvlist (Chapter 14).
Table 23.10: Batch processes; eligible multitype-marks to fvlists
Batch Process Workhorse function in Package spatstat.explore fv.object Suffix
Gcross_.ppp() Gcross, multi-type nearest-neighbor distance \(G_{ij}(r)\) .G
Kcross_.ppp() Kcross, multi-type \(K_{ij}(r)\) .K
Jcross_.ppp() Jcross, multi-type \(J_{ij}(r)\) (Van Lieshout and Baddeley 1999) .J
Lcross_.ppp() Lcross, multi-type \(L_{ij}(r)=\sqrt{\frac{K_{ij}(r)}{\pi}}\) .L
markconnect_.ppp() markconnect, multi-type \(p_{ij}(r)\) .p

Each batch process listed in Table 23.11,

  • identifies all eligible multitype-marks in the input ppp.object (Chapter 23);
  • applies the workhorse function from package spatstat.explore per multitype-mark;
  • returns a list of numeric vectors, named in the fashion of <multitype-mark>.<suffix>, where <suffix> is the deparsed function call. Note that the returned object is not a vectorlist (Chapter 27).
Table 23.11: Batch processes; eligible multitype-marks to numeric-vectors
Batch Process Workhorse function numeric-vector Suffix
nncross_.ppp() .nncross() (Section 23.7), nearest neighbor distance .nncross

23.15.1 Examples for Table 23.9 and Table 23.10

The S3 method dispatches in Table 23.9 and Table 23.10 are user-friendly wrappers of the low-level utility functions ppp_numeric2fv() and ppp_multitype2fv(), respectively.

Listing 23.38 identifies the eligible numeric-mark area in the point-pattern object betacells (Section 8.4) for the workhorse function spatstat.explore::Emark(), and applies it.

Listing 23.38: Example: function Emark_.ppp()
spatstat.data::betacells |>
  Emark_() |>
  names()
# [1] "area.E"

Listing 23.39 identifies the eligible multitype-mark type in the point-pattern object betacells (Section 8.4), which contains (at least) the two levels of 'off' and 'on', for the workhorse function spatstat.explore::Gcross(), and applies it.

Listing 23.39: Example: function Gcross_.ppp(., i = 'off', j = 'on')
spatstat.data::betacells |>
  Gcross_(i = 'off', j = 'on') |>
  names()
# [1] "type.G"

Listing 23.40 shows that a generic name m is used for the 'vector'-markformat of the point-pattern object spruces (Section 8.17).

Listing 23.40: Example: function Emark_.ppp(), 'vector'-markformat
spatstat.data::spruces |> 
  Emark_() |>
  names()
# [1] "m.E"

Listing 23.41 returns an invisible NULL-value for the point-pattern object ants (Section 8.2) without any numeric-mark for the workhorse function spatstat.explore::Emark().

Listing 23.41: Exception: function Emark_.ppp(), no numeric-mark
Code
spatstat.data::ants |> 
  Emark_() |>
  is.null()
# [1] TRUE

A similar batch mechanism already exists in package spatstat.explore (v3.5.3.3) for ppp.object with 'dataframe'-markformat. Table 23.12 shows the difference between the batch mechanism in Table 23.9 versus that in package spatstat.explore.

Table 23.12: Batch mechanism for ppp.object with 'dataframe'-markformat: Table 23.9 vs. package spatstat.explore
In Package spatstat.explore (v3.5.3.3) In Table 23.9
marks of input ppp.object must be all-numeric select only the eligible numeric-marks
Output list of fv.object not named with suffix of attr(.,'fname') named with suffix of attr(.,'fname')

Listing 23.42 - Listing 23.44 show the differences and connections between the batch processes in Table 23.9 versus those in package spatstat.explore, using a point-pattern object finpines (Section 8.9) with two numeric-marks.

Listing 23.42: Advanced: function markcorr_.ppp() vs. spatstat.explore::markcorr()
Code
finpinesK = spatstat.data::finpines |> 
  spatstat.explore::markcorr()
names(finpinesK) = paste0(names(finpinesK), '.k')
finpinesK_ = spatstat.data::finpines |>
  markcorr_()
stopifnot(identical(finpinesK, finpinesK_))
Listing 23.43: Advanced: function Emark_.ppp() vs. spatstat.explore::Emark()
Code
finpinesE = spatstat.data::finpines |> 
  spatstat.explore::Emark()
names(finpinesE) = paste0(names(finpinesE), '.E')
finpinesE_ = spatstat.data::finpines |>
  Emark_()
stopifnot(identical(finpinesE, finpinesE_))
Listing 23.44: Advanced: function Vmark_.ppp() vs. spatstat.explore::Vmark()
Code
finpinesV = spatstat.data::finpines |>
  spatstat.explore::Vmark()
names(finpinesV) = paste0(names(finpinesV), '.V')
finpinesV_ = spatstat.data::finpines |>
  Vmark_()
stopifnot(identical(finpinesV, finpinesV_))

23.15.2 Examples for Table 23.11

The S3 method dispatches in Table 23.11 are user-friendly wrappers of the low-level utility function ppp2dist().

Listing 23.45 identifies the eligible multitype-mark type in the point-pattern object betacells (Section 8.4), which contains (at least) the two levels of 'off' and 'on', for the workhorse function .nncross() (Section 23.7), and applies it.

Listing 23.45: Example: function nncross_.ppp(., i = 'off', j = 'on')
spatstat.data::betacells |>
  nncross_(i = 'off', j = 'on') |>
  names()
# [1] "type.nncross"

Listing 23.46 identifies the eligible multitype-mark group in the point-pattern object gorillas (Section 8.11), which contains (at least) the two levels of 'major' and 'minor', for the workhorse function .nncross() (Section 23.7), and applies it;

Listing 23.46: Example: function nncross_.ppp(., i = 'major', j = 'minor')
spatstat.data::gorillas |>
  nncross_(i = 'major', j = 'minor') |>
  names()
# [1] "group.nncross"

Listing 23.47 identifies the eligible multitype-mark season in the point-pattern object gorillas (Section 8.11), which contains (at least) the two levels of 'rainy' and 'dry' for the workhorse function .nncross() (Section 23.7), and applies it.

Listing 23.47: Example: function nncross_.ppp(., i = 'rainy', j = 'dry')
spatstat.data::gorillas |>
  nncross_(i = 'rainy', j = 'dry') |>
  names()
# [1] "season.nncross"

Listing 23.48 determines that no eligible multitype-mark exists in the point-pattern object gorillas (Section 8.11) that contains both levels 'alpha' and 'beta', and therefore returns an invisible NULL-value.

Listing 23.48: Exception: function nncross_.ppp(., i = 'male', j = 'female')
Code
spatstat.data::gorillas |>
  nncross_(i = 'male', j = 'female') |>
  is.null()
# [1] TRUE

Listing 23.49 shows that a generic name m is used for the 'vector'-markformat of the point-pattern object ants (Section 8.2),

Listing 23.49: Example: function nncross_.ppp(), 'vector'-markformat
spatstat.data::ants |> 
  nncross_(i = 'Cataglyphis', j = 'Messor') |>
  names()
# [1] "m.nncross"

Listing 23.50 returns an invisible NULL-value, since the input point-pattern object spruces (Section 8.17) does not contain a multitype-mark for the workhorse function .nncross() (Section 23.7).

Listing 23.50: Exception: function nncross_.ppp(), no multitype-mark
Code
spatstat.data::spruces |> 
  nncross_() |>
  is.null()
# [1] TRUE