This noteboook tests some ideas for finding the niche optima (a vector) for a given set of traits from a trait environment model.

knitr::opts_chunk$set(cache = TRUE, dev = c("png", "pdf"))
library(here)
source(here("src", "model_linear_validation_grampians.R"))
Warning: Column `taxon` joining factor and character vector, coercing into
character vector
Warning: Column `ibra_subregion` joining factor and character vector, coercing
into character vector

Warning: Removed 60 rows containing missing values (geom_point).
Warning: Removed 3 rows containing missing values (geom_point).
Warning: Removed 60 rows containing missing values (geom_point).
Warning: Removed 3 rows containing missing values (geom_point).

Warning: Removed 30 rows containing missing values (geom_point).

Warning: Removed 3 rows containing missing values (geom_point).
Warning: Removed 30 rows containing missing values (geom_point).
Warning: Removed 3 rows containing missing values (geom_point).

Warning: Column `taxon` joining factor and character vector, coercing into
character vector
Warning: Column `ibra_subregion` joining factor and character vector, coercing
into character vector

library(geometry) # use dev version from github
library(ks) # use own fork with bugfix in kde

Pick a region and a taxon

region <- "South East Coastal Ranges"
taxon <- "Angophora costata subsp. costata"
traits <- unique(mds[mds$taxon == taxon, c("sla", "sm", "mh")])
vars <- c("mlq", "twi", "r1k", "tn")
environ <- as.matrix(
  mds[mds$ibra_subregion == region & mds$taxon == taxon, c("y", vars)]
)
environ_extent <- apply(environ[, vars], 2, range)
gridsize <- rep(30, length(vars))

Calculate the kernel density of the absence and presence points separately.

d <- lapply(
  0:1,
  function(x) {
    kde(
      environ[environ[, "y"] == x, vars],
      xmin = as.vector(environ_extent[1, ]),
      xmax = as.vector(environ_extent[2, ]),
      gridsize = gridsize
    )
  }
)

Calculate a convex hull for the environmental space.

chull <- convhulln(environ[, vars])

Calculate the empirical niche optima from the presence absence data. The optima is the point inside the hull that maximises the ratio of kernel densities of presence and absence.

empirical_niche_optima <- arrayInd( # get the indices
  which.max(
    d[[2]]$estimate / d[[1]]$estimate * # ratio of kernel densities
      # check if inside hull
      inhulln(chull, as.matrix(do.call(expand.grid, d[[1]]$eval.points)))
  ),
  gridsize
)

# extract values from the grid
empirical_niche_optima <- sapply(
  1:4, function(i) d[[1]]$eval.points[[i]][empirical_niche_optima[, i]]
)

names(empirical_niche_optima) <- vars

Convert convex hull object to a matrix of hull vertices

chull <- unique(t(sapply(chull, function(x) environ[x, vars])))

Calculate the predicted niche optima based on the model by finding which vertex of the environment convex hull has the highest predicted probability of occurrance.

predicted_niche_optima <- as.vector(
  chull[
    which.max(
      predict(
        model_grampians,
        data.frame(traits[rep(1, nrow(chull)), ], chull, row.names = NULL),
        re.form = ~0
      )
    ),
  ]
)

names(predicted_niche_optima) <- vars
environ_extent
          mlq       twi       r1k        tn
[1,] 1.698242 -2.356902 -1.747737 -1.608506
[2,] 5.519156  2.507559  1.587113  3.089763
predicted_niche_optima
     mlq      twi      r1k       tn 
4.611756 1.052513 1.384686 1.193711 
empirical_niche_optima
        mlq         twi         r1k          tn 
 3.54282107 -1.18272135 -0.82777814  0.01158644 
IycgLS0tCiMnIHRpdGxlOiAiUHJlZGljdGluZyBuaWNoZSBvcHRpbWEgZnJvbSB0cmFpdC1lbnZpcm9ubWVudCBtb2RlbHMiCiMnIGF1dGhvcjogIldpbGxpYW0gSy4gTW9ycmlzICYgUGV0ZXIgQS4gVmVzayIKIycgZGF0ZTogImByIFN5cy5EYXRlKClgIgojJyBvdXRwdXQ6CiMnICAgcm1hcmtkb3duOjpodG1sX25vdGVib29rOgojJyAgICAgY29kZV9mb2xkaW5nOiBzaG93CiMnIC0tLQoKIycgVGhpcyBub3RlYm9vb2sgdGVzdHMgc29tZSBpZGVhcyBmb3IgZmluZGluZyB0aGUgbmljaGUgb3B0aW1hIChhIHZlY3RvcikgZm9yCiMnIGEgZ2l2ZW4gc2V0IG9mIHRyYWl0cyBmcm9tIGEgdHJhaXQgZW52aXJvbm1lbnQgbW9kZWwuCgojKyBzZXR1cCwgbWVzc2FnZSA9IEZBTFNFCmtuaXRyOjpvcHRzX2NodW5rJHNldChjYWNoZSA9IFRSVUUsIGRldiA9IGMoInBuZyIsICJwZGYiKSkKbGlicmFyeShoZXJlKQpzb3VyY2UoaGVyZSgic3JjIiwgIm1vZGVsX2xpbmVhcl92YWxpZGF0aW9uX2dyYW1waWFucy5SIikpCmxpYnJhcnkoZ2VvbWV0cnkpICMgdXNlIGRldiB2ZXJzaW9uIGZyb20gZ2l0aHViCmxpYnJhcnkoa3MpICMgdXNlIG93biBmb3JrIHdpdGggYnVnZml4IGluIGtkZQoKIycgKioqKioKCiMnIFBpY2sgYSByZWdpb24gYW5kIGEgdGF4b24KIysgcmVnaW9udGF4b24KcmVnaW9uIDwtICJTb3V0aCBFYXN0IENvYXN0YWwgUmFuZ2VzIgp0YXhvbiA8LSAiQW5nb3Bob3JhIGNvc3RhdGEgc3Vic3AuIGNvc3RhdGEiCnRyYWl0cyA8LSB1bmlxdWUobWRzW21kcyR0YXhvbiA9PSB0YXhvbiwgYygic2xhIiwgInNtIiwgIm1oIildKQp2YXJzIDwtIGMoIm1scSIsICJ0d2kiLCAicjFrIiwgInRuIikKZW52aXJvbiA8LSBhcy5tYXRyaXgoCiAgbWRzW21kcyRpYnJhX3N1YnJlZ2lvbiA9PSByZWdpb24gJiBtZHMkdGF4b24gPT0gdGF4b24sIGMoInkiLCB2YXJzKV0KKQplbnZpcm9uX2V4dGVudCA8LSBhcHBseShlbnZpcm9uWywgdmFyc10sIDIsIHJhbmdlKQpncmlkc2l6ZSA8LSByZXAoMzAsIGxlbmd0aCh2YXJzKSkKCiMnICoqKioqCgojJyBDYWxjdWxhdGUgdGhlIGtlcm5lbCBkZW5zaXR5IG9mIHRoZSBhYnNlbmNlIGFuZCBwcmVzZW5jZSBwb2ludHMgc2VwYXJhdGVseS4KIysgZApkIDwtIGxhcHBseSgKICAwOjEsCiAgZnVuY3Rpb24oeCkgewogICAga2RlKAogICAgICBlbnZpcm9uW2Vudmlyb25bLCAieSJdID09IHgsIHZhcnNdLAogICAgICB4bWluID0gYXMudmVjdG9yKGVudmlyb25fZXh0ZW50WzEsIF0pLAogICAgICB4bWF4ID0gYXMudmVjdG9yKGVudmlyb25fZXh0ZW50WzIsIF0pLAogICAgICBncmlkc2l6ZSA9IGdyaWRzaXplCiAgICApCiAgfQopCgojJyAqKioqKgoKIycgQ2FsY3VsYXRlIGEgY29udmV4IGh1bGwgZm9yIHRoZSBlbnZpcm9ubWVudGFsIHNwYWNlLgojKyBjaHVsbApjaHVsbCA8LSBjb252aHVsbG4oZW52aXJvblssIHZhcnNdKQoKIycgKioqKioKCiMnIENhbGN1bGF0ZSB0aGUgZW1waXJpY2FsIG5pY2hlIG9wdGltYSBmcm9tIHRoZSBwcmVzZW5jZSBhYnNlbmNlIGRhdGEuCiMnIFRoZSBvcHRpbWEgaXMgdGhlIHBvaW50IGluc2lkZSB0aGUgaHVsbCB0aGF0IG1heGltaXNlcyB0aGUgcmF0aW8gb2Yga2VybmVsCiMnIGRlbnNpdGllcyBvZiBwcmVzZW5jZSBhbmQgYWJzZW5jZS4KIysgZW1waXJpY2Fsb3B0aW1hCmVtcGlyaWNhbF9uaWNoZV9vcHRpbWEgPC0gYXJyYXlJbmQoICMgZ2V0IHRoZSBpbmRpY2VzCiAgd2hpY2gubWF4KAogICAgZFtbMl1dJGVzdGltYXRlIC8gZFtbMV1dJGVzdGltYXRlICogIyByYXRpbyBvZiBrZXJuZWwgZGVuc2l0aWVzCiAgICAgICMgY2hlY2sgaWYgaW5zaWRlIGh1bGwKICAgICAgaW5odWxsbihjaHVsbCwgYXMubWF0cml4KGRvLmNhbGwoZXhwYW5kLmdyaWQsIGRbWzFdXSRldmFsLnBvaW50cykpKQogICksCiAgZ3JpZHNpemUKKQoKIyBleHRyYWN0IHZhbHVlcyBmcm9tIHRoZSBncmlkCmVtcGlyaWNhbF9uaWNoZV9vcHRpbWEgPC0gc2FwcGx5KAogIDE6NCwgZnVuY3Rpb24oaSkgZFtbMV1dJGV2YWwucG9pbnRzW1tpXV1bZW1waXJpY2FsX25pY2hlX29wdGltYVssIGldXQopCgpuYW1lcyhlbXBpcmljYWxfbmljaGVfb3B0aW1hKSA8LSB2YXJzCgojJyAqKioqKgoKIydDb252ZXJ0IGNvbnZleCBodWxsIG9iamVjdCB0byBhIG1hdHJpeCBvZiBodWxsIHZlcnRpY2VzCiMrY2h1bGx2ZXJ0CmNodWxsIDwtIHVuaXF1ZSh0KHNhcHBseShjaHVsbCwgZnVuY3Rpb24oeCkgZW52aXJvblt4LCB2YXJzXSkpKQoKIycgKioqKioKCiMnIENhbGN1bGF0ZSB0aGUgcHJlZGljdGVkIG5pY2hlIG9wdGltYSBiYXNlZCBvbiB0aGUgbW9kZWwgYnkgZmluZGluZyB3aGljaAojJyB2ZXJ0ZXggb2YgdGhlIGVudmlyb25tZW50IGNvbnZleCBodWxsIGhhcyB0aGUgaGlnaGVzdCBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkKIycgb2Ygb2NjdXJyYW5jZS4KIysgcHJlZGljdGVkb3B0aW1hCnByZWRpY3RlZF9uaWNoZV9vcHRpbWEgPC0gYXMudmVjdG9yKAogIGNodWxsWwogICAgd2hpY2gubWF4KAogICAgICBwcmVkaWN0KAogICAgICAgIG1vZGVsX2dyYW1waWFucywKICAgICAgICBkYXRhLmZyYW1lKHRyYWl0c1tyZXAoMSwgbnJvdyhjaHVsbCkpLCBdLCBjaHVsbCwgcm93Lm5hbWVzID0gTlVMTCksCiAgICAgICAgcmUuZm9ybSA9IH4wCiAgICAgICkKICAgICksCiAgXQopCgpuYW1lcyhwcmVkaWN0ZWRfbmljaGVfb3B0aW1hKSA8LSB2YXJzCgojKwplbnZpcm9uX2V4dGVudAoKIysKcHJlZGljdGVkX25pY2hlX29wdGltYQoKIysKZW1waXJpY2FsX25pY2hlX29wdGltYQo=