13  fv

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

library(groupedHyperframe)

Function spatstat.explore::fv() creates a function-value-table object (fv.object), i.e., an R object of S3 class 'fv'. In addition to the existing S3 method dispatches spatstat.explore::*.fv (v3.5.3.3, Listing 13.1),

Listing 13.1: Existing S3 method dispatches spatstat.explore::*.fv
Code
suppressPackageStartupMessages(library(spatstat.explore))
methods(class = 'fv', all.names = TRUE) |> 
  attr(which = 'info', exact = TRUE) |>
  subset.data.frame(subset = from == 'spatstat.explore')
#                  visible             from       generic  isS4
# [.fv                TRUE spatstat.explore             [ FALSE
# [<-.fv              TRUE spatstat.explore           [<- FALSE
# $<-.fv              TRUE spatstat.explore           $<- FALSE
# as.data.frame.fv    TRUE spatstat.explore as.data.frame FALSE
# as.function.fv      TRUE spatstat.explore   as.function FALSE
# as.fv.fv            TRUE spatstat.explore         as.fv FALSE
# cbind.fv            TRUE spatstat.explore         cbind FALSE
# collapse.fv         TRUE spatstat.explore      collapse FALSE
# compatible.fv       TRUE spatstat.explore    compatible FALSE
# Complex.fv          TRUE spatstat.explore       Complex FALSE
# deriv.fv            TRUE spatstat.explore         deriv FALSE
# formula.fv          TRUE spatstat.explore       formula FALSE
# formula<-.fv        TRUE spatstat.explore     formula<- FALSE
# harmonise.fv        TRUE spatstat.explore     harmonise FALSE
# harmonize.fv        TRUE spatstat.explore     harmonize FALSE
# integral.fv         TRUE spatstat.explore      integral FALSE
# Math.fv             TRUE spatstat.explore          Math FALSE
# names<-.fv          TRUE spatstat.explore       names<- FALSE
# Ops.fv              TRUE spatstat.explore           Ops FALSE
# pcf.fv              TRUE spatstat.explore           pcf FALSE
# plot.fv             TRUE spatstat.explore          plot FALSE
# pool.fv             TRUE spatstat.explore          pool FALSE
# print.fv            TRUE spatstat.explore         print FALSE
# rose.fv             TRUE spatstat.explore          rose FALSE
# Smooth.fv           TRUE spatstat.explore        Smooth FALSE
# StieltjesCalc.fv    TRUE spatstat.explore StieltjesCalc FALSE
# Summary.fv          TRUE spatstat.explore       Summary FALSE
# with.fv             TRUE spatstat.explore          with FALSE

Package groupedHyperframe (v0.3.0.20251020) implements more S3 method dispatches to the class 'fv' (Listing 13.2, Table 13.1),

Listing 13.2: Table: S3 method dispatches groupedHyperframe::*.fv
Code
methods2kable(class = 'fv', package = 'groupedHyperframe', all.names = TRUE)
Table 13.1: S3 method dispatches groupedHyperframe::*.fv (v0.3.0.20251020)
visible from generic isS4
.disrecommend2theo.fv TRUE groupedHyperframe groupedHyperframe::.disrecommend2theo FALSE
.illegal2theo.fv TRUE groupedHyperframe groupedHyperframe::.illegal2theo FALSE
.rmax.fv TRUE groupedHyperframe groupedHyperframe::.rmax FALSE
cumvtrapz.fv TRUE groupedHyperframe groupedHyperframe::cumvtrapz FALSE
visualize_vtrapz.fv TRUE groupedHyperframe groupedHyperframe::visualize_vtrapz FALSE
vtrapz.fv TRUE groupedHyperframe groupedHyperframe::vtrapz FALSE

13.2 \(r_\text{max}\)

The S3 generic function .rmax() has been introduced in Section 23.11. The S3 method dispatch .rmax.fv() (Listing 13.11), often used as an internal utility function, simply grabs the maximum value of the \(r\)-vector in an fv.object.

Listing 13.11: Example: function .rmax.fv()
Code
sprucesK_r1 = spatstat.data::spruces |>
  spatstat.explore::Emark() |>
  .rmax.fv()
sprucesK_r2 = spatstat.data::spruces |>
  spatstat.explore::Emark() |>
  spatstat.explore::with.fv(expr = r) |>
  max()
stopifnot(identical(sprucesK_r1, sprucesK_r2))

13.3 Legal \(r_\text{max}\)

Function spatstat.explore::markcorr() is the workhorse inside functions Emark(), Vmark() and markvario() (v3.5.3.3). Function markcorr() relies on the un-exported workhorse function spatstat.explore:::sewsmod(), whose default method = "density" contains a ratio of two kernel densities. Due to the floating-point precision of R (Listing 13.12), such density ratios may have exceptional/illegal returns of

  • 0 from \(0/\delta\), or Inf from \(\delta/0\), with a real number \(\delta\geq\) (approximately) 2.6e-324
  • NaN from \(0/\varepsilon\) or \(\varepsilon/0\), with a real number \(\varepsilon\leq\) (approximately) 2.5e-324
Listing 13.12: Review: exceptional/illegal ratio due to floating-point precision (R Core Team 2025)
Code
list(
  0 / c(2.6e-324, 2.5e-324),
  c(2.5e-324, 2.6e-324) / 0
)
# [[1]]
# [1]   0 NaN
# 
# [[2]]
# [1] NaN Inf

Function spatstat.explore::markcorr() provides a default argument of parameter \(r\)-vector (Section 23.11), at which the mark correlation function \(k_f(r)\) are evaluated. The S3 method dispatch spatstat.explore::print.fv() (Listing 13.13) prints the recommended range (and available range) of \(r\)-vector.

Listing 13.13: Review: function spatstat.explore::print.fv() (Baddeley, Rubak, and Turner 2015)
Code
spatstat.data::spruces |> 
  spatstat.explore::markcorr() |>
  spatstat.explore::print.fv()
# Function value object (class 'fv')
# for the function r -> k[mm](r)
# ................................................................................
#       Math.label              Description                                       
# r     r                       distance argument r                               
# theo  {k[mm]^{iid}}(r)        theoretical value (independent marks) for k[mm](r)
# trans {hat(k)[mm]^{trans}}(r) translation-corrected estimate of k[mm](r)        
# iso   {hat(k)[mm]^{iso}}(r)   Ripley isotropic correction estimate of k[mm](r)  
# ................................................................................
# Default plot formula:  .~r
# where "." stands for 'iso', 'trans', 'theo'
# Recommended range of argument r: [0, 9.5]
# Available range of argument r: [0, 9.5]
# Unit of length: 1 metre

Exceptional/illegal values of 0, Inf and/or NaN may appear in the mark correlation function \(k_f(r)\), if the \(r\)-vector goes well beyond the recommended range. The authors construct a malformed function-value-table object fv_mal (Listing 13.14, Listing 13.15) to demonstrate various recovery procedures applicable in such cases.

Listing 13.14: Data: a malformed function-value-table object fv_mal with \(r\)-vector out-of-range
fv_mal = spatstat.data::spruces |> 
  spatstat.explore::markcorr(r = 0:100)
Listing 13.15: Review: spatstat.explore::plot.fv() on fv_mal
Code
fv_mal |> 
  spatstat.explore::plot.fv(xlim = c(0, 100))

The helper function lastLegal() (Listing 13.19, Listing 13.20) returns the index of the last consecutive legal values in a double (Listing 13.16) vector, i.e., the first exceptional/illegal value of 0, Inf, or NaN appears at the next index. The term “legal”, as in the function lastLegal(), is defined as

a double scalar being not-NA_real_, not-NaN, not-Inf, and with absolute value greater than .Machine$double.eps (Listing 13.17, Listing 13.18).

Listing 13.16: Advanced: NaN and Inf are double, not integer (R Core Team 2025)
Code
list(Inf, NaN) |> 
  vapply(FUN = typeof, FUN.VALUE = '')
# [1] "double" "double"
Listing 13.17: Advanced: base::is.finite()
Code
c(NA_real_, NaN, Inf) |>
  is.finite()
# [1] FALSE FALSE FALSE
Listing 13.19: Example: function lastLegal(), toy examples
Code
list(
  c(exp(1), pi),
  c(exp(1), pi, NaN),
  c(exp(1), pi, NaN, 1, 0, Inf)
) |> 
  lapply(FUN = lastLegal)
# [[1]]
# [1] 2
# attr(,"value")
# [1] 3.141593
# 
# [[2]]
# [1] 2
# attr(,"value")
# [1] 3.141593
# 
# [[3]]
# [1] 2
# attr(,"value")
# [1] 3.141593
Listing 13.20: Example: lastLegal() of keyval.fv()
spruces_k_lastLegal = fv_mal |>
  keyval.fv() |>
  lastLegal()
spruces_k_lastLegal
# [1] 75
# attr(,"value")
#       74 
# 1.549766

The term Legal \(r_\text{max}\) indicates (the index) of the \(r\)-vector, where the last of the consecutive legal recommended-function-values appears. In Listing 13.20, the last consecutive legal recommended-function-value of fv_mal (Listing 13.14) of \(k_f(r)=1.550\) appears at the 75-th index of the \(r\)-vector, i.e., \(r=74\).

Legality of the function spatstat.explore::markcorr() returns depends not only on the input ppp.object, but also on the values of the \(r\)-vector. In other words, the creation of an fv.object by function markcorr() is a numerical procedure. Therefore, the discussion of Legal \(r_\text{max}\) pertains to the fv.object (Chapter 13), instead of to the ppp.object (Chapter 23).

Example: Legality of spatstat.explore::markcorr() return depends on \(r\)-vector
spatstat.data::spruces |> 
  spatstat.explore::markcorr(r = seq.int(from = 0, to = 100, by = .1)) |>
  keyval.fv() |>
  lastLegal()
# [1] 742
# attr(,"value")
#      74.1 
# 0.3191326

13.3.1 Handling Illegal Recommended-Function-Value

The S3 generic functions .illegal2theo() and .disrecommend2theo() are exploratory approaches to remove the illegal recommended-function-values (Section 13.3) from an fv.object. These approaches replace the recommended-function-values with the theoretical values starting at different locations, and return an updated fv.object.

Function .illegal2theo.fv() (Listing 13.21) replaces the recommended function values after the first illegal \(r\) (Section 13.3) of the function-value-table object fv_mal (Listing 13.14) with its theoretical values.

Listing 13.21: Advanced: function .illegal2theo.fv()
Code
fv_mal |> 
  .illegal2theo() |>
  spatstat.explore::plot.fv(xlim = c(0, 100))
# r≥75.0 replaced with theo

Function .disrecommend2theo.fv() (Listing 13.22) replaces the recommended function values after the first \(r\) outside the recommended range attr(.,'alim')[2L] of the function-value-table object fv_mal (Listing 13.14) with its theoretical values.

Listing 13.22: Advanced: function .disrecommend2theo.fv()
Code
fv_mal |> 
  .disrecommend2theo() |>
  spatstat.explore::plot.fv(xlim = c(0, 100))
# r≥10.0 replaced with theo

13.4 Interpolation

The authors use the toy example of a “coarse” and a “fine” fv.object (Listing 13.23, Listing 13.24) to illustrate various interpolation methods of the \(x\)- and \(y\)-values (Listing 13.4, Listing 13.5) in the function-value-table object.

Listing 13.23: Data: coarse vs. fine \(r\)-vector
r_coarse = 0:9
r_fine = seq.int(from = 0, to = 9, by = .01)
Listing 13.24: Data: coarse vs fine fv.object
sprucesE0 = spatstat.data::spruces |>
  spatstat.explore::Emark(r = r_coarse)
sprucesE1 = spatstat.data::spruces |>
  spatstat.explore::Emark(r = r_fine)

Functions approxfun.fv() (Listing 13.25) performs a linear interpolation. This is a “psuedo” S3 method dispatch, as the workhorse function stats::approxfun() is not an S3 generic function.

Listing 13.25: Example: function approxfun.fv()
sprucesE_approx = sprucesE0 |>
  approxfun.fv() |>
  visualize_vtrapz(draw.rect = FALSE) +
  ggplot2::labs(title = '(A). linear interpolation via stats::approxfun()')

Functions splinefun.fv() (Listing 13.26) performs a spline interpolation. This is a “psuedo” S3 method dispatch, as the workhorse function stats::splinefun() is not an S3 generic function.

Listing 13.26: Example: function splinefun.fv()
sprucesE_spline = sprucesE0 |>
  splinefun.fv() |>
  visualize_vtrapz(draw.rect = FALSE) +
  ggplot2::labs(title = '(B). spline interpolation via stats::splinefun()')

An experienced reader may wonder: is it truly advantageous to compute a coarse fv.object and then perform interpolation, rather than computing a fine fv.object to start with? This is an excellent question! In fact, we observe no substantial difference in computation time via package microbenchmark (Mersmann 2024, v1.5.0) even when the grid of the \(r\)-vector is 100 times finer (Listing 13.27), as of package spatstat.explore (v3.5.3.3)! This observation justifies the use of the plain-and-naïve trapezoidal integration (Chapter 9, Section 9.1) on a fine fv.object (Figure 13.1 D), rather than employing more sophisticated numerical integration methods, e.g., the adaptive Simpson quadrature pracma::quad(), on an interpolation of a coarse fv.object (Figure 13.1 A,B).

Listing 13.27: Advanced: coarse vs fine fv.object, benchmarks
Code
suppressPackageStartupMessages(library(spatstat))
microbenchmark::microbenchmark(
  coarse = Emark(spruces, r = r_coarse),
  fine = Emark(spruces, r = r_fine)
) |>
  suppressWarnings()
# Unit: milliseconds
#    expr      min       lq     mean   median       uq      max neval cld
#  coarse 1.998053 2.049733 2.179651 2.086531 2.121689 5.239062   100  a 
#    fine 2.324495 2.399238 2.657508 2.456330 2.490196 5.386211   100   b
Figure: Interpolations vs. Trapezoidal Integration
spruce_fig0 = sprucesE0 |>
  visualize_vtrapz(draw.rect = FALSE) +
  ggplot2::labs(title = '(C). trapezoidal integration, coarse')
spruce_fig1 = sprucesE1 |>
  visualize_vtrapz(draw.rect = FALSE) +
  ggplot2::labs(title = '(D). trapezoidal integration, fine')
(sprucesE_approx + sprucesE_spline + spruce_fig0 + spruce_fig1 + patchwork::plot_layout(ncol = 2L)) & 
  ggplot2::theme_minimal()
Figure 13.1: Interpolations vs. Trapezoidal Integration