--- title: "ascent: decomposing functional community restructuring" author: "Rogelio R. Muñoz-Li & Flavia Alvarez-Denis" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{ascent: decomposing functional community restructuring} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", warning = FALSE, message = FALSE, fig.width = 10, fig.height = 5.5 ) ``` ## Part I - Conceptual Framework The `ascent` package implements the **ASC-CFD** (Assemblage Shift Characterization — Community Functional Dynamics) framework. Communities can show the same amount of functional turnover while following very different trajectories: one moves to another region of trait space, another redistributes abundance internally, a third gains or loses extreme phenotypes. `ascent` separates these processes into three layers measured in a functional space (PCoA of Gower distances between species traits): **Table 1**. The three layers of ASC-CFD. | Layer | Metric | Ecological question | | ---------- | ------ | -------------------------------------------------- | | Position | ΔC | Where is the community moving? | | Dispersion | ΔFDis | How is abundance being redistributed? | | Volume | ΔFRic | Are functional boundaries expanding or collapsing? | Each layer is tested against two null models that ask different questions: * **A (sampling)**: individuals (or whole surveys, when both states have replicates) are reassigned at random between the two states. *Is the change larger than expected from sampling alone?* * **B stratified (traits)**: trait positions are shuffled among species of similar abundance. *Does the change depend on which traits the species have?* **Table 2**. Reading A and B together. | A | B | Reading | | --- | ---------- | -------------------------------------------------------------- | | no | — | no evidence of change beyond sampling | | yes | upper tail | real change, directed by traits | | yes | no | real change without detectable trait structure | | yes | lower tail | less change than expected from traits (compensation) | B has little power to detect compensation; functional entities (Part V) are the recommended tool for that. ### The simulated data The package includes `birds_sim`, a simulated data set: 24 bird species with four traits (two continuous, two categorical) and counts of individuals. Two sites were surveyed five times before and five times after an event. At `Logged`, forest-dependent and fruit-eating species decline and small open-habitat species increase; at `Control` nothing changes, so any difference there is sampling noise. A second matrix holds five communities along a landscape gradient. ```{r data} library(ascent) # birds_sim is loaded with the package traits <- birds_sim$traits # species x traits (categorical traits as factors) counts <- birds_sim$counts # surveys x species, counts of individuals samples <- birds_sim$samples # site and time of each survey head(traits) table(samples$site, samples$time) ``` Abundances must be **counts of individuals** (whole numbers): null model A needs them. Species are matched to traits by name, so the column order does not matter. --- ## Part II - Quality of the functional space ```{r space} assess_functional_space(traits) ``` If the quality is below 0.8, the package warns: use `correction = "sqrt"` (Gower 1971) in all functions, which makes the space Euclidean. FRic can only be computed in samples with more species than retained axes. --- ## Part III - Paired contrasts `asc_paired()` compares a reference state with a later state at each site. Here each site is surveyed five times in each state, so the surveys act as replicates and null model A permutes whole surveys between states (accounting for variation between surveys). ```{r paired} res <- asc_paired(traits, counts, sites = samples$site, time = samples$time, ref_time = "Before") res <- asc_null(res, n_perm = 199, seed = 1) summary(res) ``` *`n_perm = 199` keeps the vignette fast; use 999 for research. A by samples can only produce as many distinct results as there are ways of splitting the surveys between the two states: with 5 surveys per state there are `choose(10, 5) = 252`; with 4 there would be only 70 (all of them are enumerated when they are fewer than `n_perm`), and because swapping the two groups gives the same distance, the smallest possible P for position would be 2/70 ≈ 0.03. With few replicates, A has a floor on its p-values.* ```{r paired_table, echo = FALSE} nm <- res$null_models pos <- nm[nm$Layer == "Position", c("Contrast", "Test", "SES", "P_upper", "P_lower")] ``` **Reading.** At `Logged`, A gives P = `r signif(pos$P_upper[pos$Contrast == "Logged" & grepl("^A", pos$Test)], 2)` for the centroid shift and B gives P = `r signif(pos$P_upper[pos$Contrast == "Logged" & grepl("^B", pos$Test)], 2)`. At `Control`, where nothing changed in the simulation, A gives P = `r signif(pos$P_upper[pos$Contrast == "Control" & grepl("^A", pos$Test)], 2)`: `r if (pos$P_upper[pos$Contrast == "Control" & grepl("^A", pos$Test)] > 0.05) "no evidence of change beyond sampling, as expected" else "a false alarm; under no change about one site in twenty gives P < 0.05 by chance"`. Read B only where A detects a change: it tells whether the change is organised by the species' traits (upper tail) or smaller than expected from them (lower tail). At `Logged`, the volume shrank more than expected from sampling: some species at the edge of the functional space disappeared. At `Control`, the Volume rows show `NA`: the convex hull (set by the extreme species) did not change, so ΔFRic is 0 in the data and in every permutation. FRic only responds when species at the edges of the functional space appear or disappear. --- ## Part IV - Which species moved the community? `asc_transitions()` describes the change without p-values. The **leverage** of a species is its contribution to the centroid shift; leverages add up exactly to the shift. The **partition** says how much of the shift comes from species shared by both states, gained or lost; its range across reference points shows how robust that split is. ```{r transitions} tr <- asc_transitions(res) summary(tr, n_sp = 5) plot(res, contrast = "Logged", type = "both", n_sp = 5) ``` Positive leverage: the species pushes the centroid in the direction of the change (it increased in the direction of the shift, or declined on the opposite side). Negative leverage: the species resists the shift. --- ## Part V - Functional entities Functional entities are groups of species with similar traits. They separate a change that reorganises the functional groups (shift **between** entities) from the replacement of species by functionally similar ones (shift **within** entities: redundancy or compensation). ```{r entities, fig.width = 7, fig.height = 5} ent <- asc_entities(traits, k_max = 10, B = 50, seed = 1) summary(ent) plot(ent) ``` If `k` is not given, the number of entities with the highest mean silhouette is used. Mean silhouette below 0.25 means no clear groups; a gap statistic of 1 means the entities divide a continuum (still useful, but check that conclusions hold for neighbouring values of `k`). Entities are used in the null models (A on counts summed by entity) and in the transitions (split between and within entities): ```{r entities_use} res_e <- asc_null(res, n_perm = 199, seed = 1, entities = ent) res_e$null_models[res_e$null_models$Layer == "Position", c("Contrast", "Test", "SES", "P_upper")] asc_transitions(res, entities = ent)$Logged$entities ``` **Reading.** If A is significant for species but not for entities, and most of the shift is *within* entities, species were replaced by functionally similar ones. Here `r wf <- asc_transitions(res, entities = ent)$Logged$entities[["Within_fraction"]]; round(100 * (1 - wf))` % of the shift at `Logged` occurs *between* entities: `r if (wf < 0.5) "the change reorganised the functional groups" else "most of the change is replacement among similar species"`. --- ## Part VI - Many communities: a global test `asc_pairwise()` describes all pairs of communities. With many communities, one p-value per pair would multiply false alarms, so `asc_null()` runs **one global test per layer**: the mean distance of the community centroids to their common centroid, and the standard deviation of FDis and FRic among communities. It also reports how far each community lies from the common centroid. ```{r pairwise} pw <- asc_pairwise(traits, birds_sim$landscape) pw <- asc_null(pw, n_perm = 199, seed = 1) summary(pw) plot(pw, contrast = "Primary_vs_Farmland", type = "both", n_sp = 5) ``` For the global test, P is the upper tail (more variation among communities than expected). For B, the lower tail means *less* variation than expected from the traits (compensation among communities). --- ## Part VII - Baseline topology `asc_baseline()` reports the absolute metrics (CWM, FDis, FRic) of each community, without contrasts. ```{r baseline} summary(asc_baseline(traits, birds_sim$landscape)) ``` **Table 3**. Functions of `ascent`. | Objective | Function | | ---------------------------------- | --------------------------- | | Quality of the functional space | `assess_functional_space()` | | Paired (temporal) contrasts | `asc_paired()` | | All pairs of communities | `asc_pairwise()` | | Null models A and B, global test | `asc_null()` | | Species leverage and partition | `asc_transitions()` | | Functional entities | `asc_entities()` | | Absolute metrics per community | `asc_baseline()` | --- ## Part VIII - Methodological notes ### Choosing `stratum_size` Null model B shuffles traits among groups of `stratum_size` species of similar abundance (default 5). In the validation, groups of 5 and of 10 species behaved almost identically, whereas shuffling among all species of the contrast (`stratum_size = Inf`) gave up to 12 % false positives for FRic; it is not recommended. As a rule of thumb (not evaluated by simulation), use 3 when samples have fewer than 10 species and 5 to 10 when they have more than 60. ### Known limits of the null models * B is slightly liberal for dispersion (about 6.5 % false positives instead of 5 %) and when individuals are captured in groups (about 7 %): treat p-values close to 0.05 with caution. * Without replicates, A reassigns individuals. If individuals are captured in groups (flocks, traps), this underestimates sampling variation and A becomes liberal: use replicate surveys whenever possible. * Many tests at once: consider adjusting p-values within each family of tests, e.g. `p.adjust(p, method = "BH")`. ### Interpreting FRic FRic is the volume of the convex hull in the retained axes. It depends only on extreme species and is not abundance-weighted: interpret ΔFRic as a descriptor of the functional boundary, complementary to ΔFDis. When a community has no more species than retained axes, FRic is `NA`. ### Normalisation Only the positional shift (rΔC %) is normalised by the regional functional diameter and comparable across studies. ΔFDis and ΔFRic are absolute differences in the retained space. ### Leverage: additive decomposition $$\sum_{i=1}^{S} \text{Leverage}_i = \|\Delta C\|, \qquad \text{Leverage}_i = \Delta p_i \, (\mathbf{f}_i - \mathbf{r}) \cdot \hat{v}$$ where $\Delta p_i$ is the change in relative abundance, $\mathbf{f}_i$ the species' coordinates, $\hat{v}$ the unit vector of the shift and $\mathbf{r}$ the reference point (by default the midpoint between the two centroids). The total does not depend on $\mathbf{r}$; how it is shared among species does.