Neighborhood basal area with calba

This vignette shows how to compute neighborhood basal area with calba. We start from a simple simulated plot, then use neigh_ba() for model-ready summaries. We then illustrate multiple radii, low-level helper functions, and basic edge correction options.

Example data

Simulate a modest plot with 500 trees, four species, 50x50 m extent, and basal areas between 20 and 200 (e.g., cm^2).

library(calba)
library(ggplot2)
set.seed(42)
side <- 50
n <- 500
trees <- data.frame(
  species = sample(c("oak", "pine", "birch", "maple"), n, replace = TRUE),
  gx = runif(n, 0, side),
  gy = runif(n, 0, side),
  ba = runif(n, 20, 200)
)

ggplot(trees, aes(gx, gy, col = species)) +
  geom_point() +
  coord_fixed() +
  theme_bw()

Core summaries: neigh_ba()

neigh_ba() computes conspecific and total basal area, neighbor counts, and derived metrics for a single radius.

Optionally, we can add decay-weighted summaries by supplying mu_values. This appends a long table of distance-weighted basal area for each decay parameter, which is returned in the decay component.

nb <- neigh_ba(
  sp = trees$species,
  gx = trees$gx,
  gy = trees$gy,
  ba = trees$ba,
  r = 5,
  mu_values = c(2, 6)
)

head(nb$summary)
##   tree_id species   con_ba total_ba con_count total_count prop_con_ba    het_ba
## 1       1     oak 551.5438 2539.628         4          19   0.2171751 1988.0838
## 2       2     oak 232.2601 1534.218         3          14   0.1513866 1301.9581
## 3       3     oak 592.0395 1105.628         6          12   0.5354780  513.5886
## 4       4     oak 509.0855 1245.698         6          12   0.4086749  736.6124
## 5       5    pine 601.2436 2226.411         5          19   0.2700506 1625.1673
## 6       6   maple 394.3903 1969.297         4          17   0.2002696 1574.9068
##   het_count competition_index
## 1        15         133.66461
## 2        11         109.58701
## 3         6          92.13568
## 4         6         103.80816
## 5        14         117.17952
## 6        13         115.84101

We can inspect the full structure of the returned object:

str(nb)
## List of 2
##  $ summary:'data.frame': 500 obs. of  10 variables:
##   ..$ tree_id          : int [1:500] 1 2 3 4 5 6 7 8 9 10 ...
##   ..$ species          : chr [1:500] "oak" "oak" "oak" "oak" ...
##   ..$ con_ba           : num [1:500] 552 232 592 509 601 ...
##   ..$ total_ba         : num [1:500] 2540 1534 1106 1246 2226 ...
##   ..$ con_count        : num [1:500] 4 3 6 6 5 4 6 8 1 1 ...
##   ..$ total_count      : num [1:500] 19 14 12 12 19 17 19 23 9 17 ...
##   ..$ prop_con_ba      : num [1:500] 0.217 0.151 0.535 0.409 0.27 ...
##   ..$ het_ba           : num [1:500] 1988 1302 514 737 1625 ...
##   ..$ het_count        : num [1:500] 15 11 6 6 14 13 13 15 8 16 ...
##   ..$ competition_index: num [1:500] 133.7 109.6 92.1 103.8 117.2 ...
##  $ decay  :'data.frame': 1000 obs. of  5 variables:
##   ..$ tree_id : int [1:1000] 1 2 3 4 5 6 7 8 9 10 ...
##   ..$ species : chr [1:1000] "oak" "oak" "oak" "oak" ...
##   ..$ mu      : num [1:1000] 2 2 2 2 2 2 2 2 2 2 ...
##   ..$ con_ba  : num [1:1000] 157.6 61.1 88.1 102.4 115.8 ...
##   ..$ total_ba: num [1:1000] 692 554 180 204 381 ...

nb$decay is a long table of decay-weighted basal area for each mu.

head(nb$decay)
##   tree_id species mu    con_ba total_ba
## 1       1     oak  2 157.61987 692.3895
## 2       2     oak  2  61.13062 554.2654
## 3       3     oak  2  88.08783 180.0602
## 4       4     oak  2 102.40837 203.6150
## 5       5    pine  2 115.84979 380.6603
## 6       6   maple  2  62.66209 491.2417

Edge handling and bounds

Use edge_correction = "safe" to return NA for focal trees within r of the plot boundary. By default, bounds are the min and max of gx and gy. Supply bounds = c(xmin, xmax, ymin, ymax) if your plot is larger than the observed coordinates.

nb_bounds <- neigh_ba(
  sp = trees$species,
  gx = trees$gx,
  gy = trees$gy,
  ba = trees$ba,
  r = 5,
  edge_correction = "safe",
  bounds = c(0, 50, 0, 50)
)$summary

head(nb_bounds)
##   tree_id species   con_ba total_ba con_count total_count prop_con_ba    het_ba
## 1       1     oak 551.5438 2539.628         4          19   0.2171751 1988.0838
## 2       2     oak       NA       NA        NA          NA          NA        NA
## 3       3     oak 592.0395 1105.628         6          12   0.5354780  513.5886
## 4       4     oak 509.0855 1245.698         6          12   0.4086749  736.6124
## 5       5    pine 601.2436 2226.411         5          19   0.2700506 1625.1673
## 6       6   maple       NA       NA        NA          NA          NA        NA
##   het_count competition_index
## 1        15         133.66461
## 2        NA                NA
## 3         6          92.13568
## 4         6         103.80816
## 5        14         117.17952
## 6        NA                NA

Then in Low-level helpers you keep the detailed explanation of the exponential and exponential-normal kernels and the ba_decay() code.

Multiple radii in one pass: neigh_multi_r()

Reuse a single distance structure across radii.

multi <- neigh_multi_r(
  sp = trees$species,
  gx = trees$gx,
  gy = trees$gy,
  ba = trees$ba,
  r_values = c(3, 5, 10),
  dist_weighted = FALSE
)

multi_r5 <- subset(multi, radius == 5)
head(multi_r5)
##     tree_id species radius   con_ba total_ba con_count total_count prop_con_ba
## 501       1     oak      5 551.5438 2539.628         4          19   0.2171751
## 502       2     oak      5 232.2601 1534.218         3          14   0.1513866
## 503       3     oak      5 592.0395 1105.628         6          12   0.5354780
## 504       4     oak      5 509.0855 1245.698         6          12   0.4086749
## 505       5    pine      5 601.2436 2226.411         5          19   0.2700506
## 506       6   maple      5 394.3903 1969.297         4          17   0.2002696
##        het_ba het_count competition_index
## 501 1988.0838        15         133.66461
## 502 1301.9581        11         109.58701
## 503  513.5886         6          92.13568
## 504  736.6124         6         103.80816
## 505 1625.1673        14         117.17952
## 506 1574.9068        13         115.84101

Low-level helpers

calba provides several lower-level functions used by neigh_ba() and neigh_multi_r():

Decay kernels in ba_decay()

ba_decay() computes distance–weighted basal area using either of two kernels.

Exponential:

\[ w(d, \mu) = \exp\left(-\frac{d}{\mu}\right) \]

Exponential-normal (Gaussian in distance):

\[ w(d, \mu) = \exp\left(-\frac{d^2}{\mu^2}\right) \]

Neighbors contribute ba * w(d, mu) to the focal tree, where d is Euclidean distance and mu controls the decay scale.

mu_vals <- c(2, 6)

decay_exp <- ba_decay(
  mu_values = mu_vals,
  sp = trees$species,
  gx = trees$gx,
  gy = trees$gy,
  ba = trees$ba,
  r = 5,
  exponential_normal = FALSE
)
head(decay_exp$con_ba_matrix)
##           [,1]     [,2]
## [1,] 157.61987 334.6172
## [2,]  61.13062 143.7360
## [3,]  88.08783 306.0831
## [4,] 102.40837 281.1512
## [5,] 115.84979 342.8400
## [6,]  62.66209 210.8749
decay_expnorm <- ba_decay(
  mu_values = mu_vals,
  sp = trees$species,
  gx = trees$gx,
  gy = trees$gy,
  ba = trees$ba,
  r = 5,
  exponential_normal = TRUE
)
head(decay_expnorm$con_ba_matrix)
##           [,1]     [,2]
## [1,] 141.78479 394.8385
## [2,]  52.13371 177.6364
## [3,]  30.15202 375.5627
## [4,]  77.55324 338.8034
## [5,]  52.24974 433.3304
## [6,]  19.18204 263.5829

Unweighted or distance-weighted basal area: ba_simple()

ba_simple_out <- ba_simple(
  sp = trees$species,
  gx = trees$gx,
  gy = trees$gy,
  ba = trees$ba,
  r = 5,
  dist_weighted = TRUE
)

str(ba_simple_out)
## List of 2
##  $ con_ba  : num [1:500] 315.2 91.1 156 169.9 184.3 ...
##  $ total_ba: num [1:500] 1328 1224 307 356 668 ...

Neighbor counts

count_total_out <- count_total(trees$gx, trees$gy, r = 5)
head(count_total_out)
## [1] 19 14 12 12 19 17