Skip to contents


This vignette presents the different strategies available to account for uncertainty in ancestral trait values/states/ranges and diversification rate estimates within deepSTRAPP.

It uses continuous data as an example, but the rationale applies similarly to categorical and biogeographic data.


# ------ Step 0: Load data ------ #

## Load trait df
data("Ponerinae_trait_tip_data", package = "deepSTRAPP")

dim(Ponerinae_trait_tip_data)
View(Ponerinae_trait_tip_data)

# Extract continuous trait data as a named vector
Ponerinae_cont_tip_data <- setNames(object = Ponerinae_trait_tip_data$fake_cont_tip_data,
                                    nm = Ponerinae_trait_tip_data$Taxa)

# This is not valid biological data. For the sake of this example, we will assume this is size data.

# Select a color scheme from lowest to highest values (i.e., smallest to largest ants)
color_scale = c("darkgreen", "limegreen", "orange", "red")

## Load phylogeny with old time-calibration
data("Ponerinae_tree_old_calib", package = "deepSTRAPP")

plot(Ponerinae_tree_old_calib)
ape::Ntip(Ponerinae_tree_old_calib) == length(Ponerinae_cont_tip_data)

## Check that trait data and phylogeny are named and ordered similarly
all(names(Ponerinae_cont_tip_data) == Ponerinae_tree_old_calib$tip.label)


## Inputs needed for Step 1 are the tip_data (Ponerinae_cont_tip_data) and the phylogeny
# (Ponerinae_tree_old_calib), and optionally, a color scheme (color_scale).
# ------ Step 1: Prepare trait data ------ #

## Goal: Map trait evolution on the time-calibrated phylogeny

# 1.1/ Fit evolutionary models to trait data using Maximum Likelihood (ML).
# 1.2/ Select the best fitting model comparing AICc.
# 1.3/ Infer ancestral character estimates (ACE) at nodes.
# 1.4/ Infer ancestral states along branches using interpolation to produce a `contMap`.
# 1.5/ Produce simulations of trait evolutionary history conditioned on tip data and model fit 
#      (i.e., continuous stochastic mapping) to produce a set of `contMaps`.

library(deepSTRAPP)

# All these actions are performed by a single function: deepSTRAPP::prepare_trait_data()
?deepSTRAPP::prepare_trait_data()

# Run prepare_trait_data with default options
# For continuous trait, a BM model is assumed by default.
Ponerinae_trait_object <- prepare_trait_data(tip_data = Ponerinae_cont_tip_data,
                                             trait_data_type = "continuous",
                                             phylo = Ponerinae_tree_old_calib,
                                             # Set to 'TRUE' to produce the stochastic maps
                                             run_stochastic_maps = TRUE,
                                             nb_simulations = 100,
                                             seed = 1234) # Set seed for reproducibility

# Explore output
str(Ponerinae_trait_object, 1)

# Extract the contMap representing the Maximum Likelihood (ML) estimates 
# of continuous trait evolution on the phylogeny
Ponerinae_contMap <- Ponerinae_trait_object$contMap
plot_contMap(Ponerinae_contMap)
title(main = "\nML estimates")

# The contMap is the main input needed to perform a deepSTRAPP run on continuous trait data.
# However, since it only represents Maximum Likelihood (ML) estimates of ancestral trait values,
# it does not allow us to account for uncertainty in trait estimates.
# For this, we need to provide the full set of stochastic maps as contMaps

# Extract the contMaps representing independent simulated evolutionary histories 
# conditioned on the observed trait data and model fit.
# The variance observed across the maps represents the uncertainty in ancestral trait estimates.
Ponerinae_contMaps <- Ponerinae_trait_object$contMaps

# Plot contMap n°1
plot_contMap(Ponerinae_contMaps[[1]],
             fsize = c(0.6, 1)) # Adjust tip label size
title(main = "\nStochastic Mapping simulation n°1")
# Plot contMap n°10
plot_contMap(Ponerinae_contMaps[[10]],
             fsize = c(0.6, 1)) # Adjust tip label size
title(main = "\nStochastic Mapping simulation n°10")
# Plot contMap n°100
plot_contMap(Ponerinae_contMaps[[100]],
             fsize = c(0.6, 1)) # Adjust tip label size
title(main = "\nStochastic Mapping simulation n°100")

# Each simulation is different, but on average, 
# they converge around the ML estimates shown in the contMap

## The minimal input needed to run deepSTRAPP is the contMap (Ponerinae_contMap).
## If we want to account for uncertainty in trait estimates,
## we need to provide the full sets of stochastic maps (Ponerinae_contMaps),
## and choose either 'paired' (the default) or 'full' as the uncertainty_strategy.
# ------ Step 2: Prepare diversification data ------ #

## Goal: Map evolution of diversification rates and regime shifts on the time-calibrated phylogeny

# Run a BAMM (Bayesian Analysis of Macroevolutionary Mixtures)

# You need the BAMM C++ program installed on your machine to run this step.
# See the BAMM website: http://bamm-project.org/ and the companion R package [BAMMtools].

# 2.1/ Set BAMM - Record BAMM settings and generate all input files needed for BAMM.
# 2.2/ Run BAMM - Run BAMM and move output files into a dedicated directory.
# 2.3/ Evaluate BAMM - Produce evaluation plots and ESS data.
# 2.4/ Import BAMM outputs - Load `BAMM_object` in R and subset posterior samples.
# 2.5/ Clean BAMM files - Remove files generated during the BAMM run.

# All these actions are performed by a single function: deepSTRAPP::prepare_diversification_data()
?deepSTRAPP::prepare_diversification_data()

# Run BAMM workflow with deepSTRAPP
## This step is time-consuming. You can skip it and load the result directly if needed
Ponerinae_BAMM_object_old_calib <- prepare_diversification_data(
   BAMM_install_directory_path = "./software/bamm-2.5.0/", # To adjust to your own path to BAMM
   phylo = Ponerinae_tree_old_calib,
   prefix_for_files = "Ponerinae",
   seed = 1234, # Set seed for reproducibility
   numberOfGenerations = 10^7, # Set high for optimal run, but will take a long time
   BAMM_output_directory_path =  "./BAMM_outputs/")

# Load directly the result
data(Ponerinae_BAMM_object_old_calib)
# This dataset is only available in development versions installed from GitHub.
# It is not available in CRAN versions.
# Use remotes::install_github(repo = "MaelDore/deepSTRAPP") to get the latest development version.

## For the sake of example, we will use a BAMM_object with only 100 posterior samples
Ponerinae_BAMM_object <- subset_BAMM_object(
   BAMM_object = Ponerinae_BAMM_object_old_calib,
   nb_posterior_samples = 100,
   seed = 1234)

# Explore output
str(Ponerinae_BAMM_object, 1)
# Record the regime shift events and macroevolutionary regime parameters across 100 posterior samples
str(Ponerinae_BAMM_object$eventData, 1) 
# Mean speciation rates at tips aggregated across all 100 posterior samples
head(Ponerinae_BAMM_object$meanTipLambda) 
# Mean extinction rates at tips aggregated across all 100 posterior samples
head(Ponerinae_BAMM_object$meanTipMu) 

# Plot mean net diversification rates and regime shifts on the phylogeny
plot_BAMM_rates(Ponerinae_BAMM_object,
                labels = FALSE, legend = TRUE)

## Input needed for Step 3 is the BAMM_object (Ponerinae_BAMM_object)
# ------ Step 3: Run deepSTRAPP workflows ------ #

## Goal: Extract traits, diversification rates and regimes at a given time in the past
# to test for differences with a STRAPP test

# All these actions are performed by a single function:
#  For a single 'focal_time': deepSTRAPP::run_deepSTRAPP_for_focal_time()
#  For multiple 'time_steps': deepSTRAPP::run_deepSTRAPP_over_time()
?deepSTRAPP::run_deepSTRAPP_for_focal_time()
?deepSTRAPP::run_deepSTRAPP_over_time()

## We can perform the test according to three different strategies designed to handle uncertainty
#   * `"rates_only"`: Only accounts for diversification-rate uncertainty across BAMM posterior samples.
#                     Uses ML estimates for continuous traits and the most frequent state/range observed
#                     across stochastic maps for categorical and biogeographic data.
#   * `"paired"`: Default option. Accounts for both diversification-rate and ancestral trait/range
#                 reconstruction uncertainty by pairing BAMM posterior samples with stochastic maps.
#                 When the number of BAMM samples and stochastic maps differ, random pairing
#                 with replacement from the smaller set is used so that all posterior samples
#                 and stochastic maps contribute to the analysis.
#   * `"full"`: Exhaustive option that accounts for trait/range- and rate- uncertainty by crossing
#               all BAMM posterior samples with all stochastic maps.
#               Accounts for both diversification-rate and ancestral reconstruction uncertainty
#               by evaluating every combination of BAMM posterior sample and stochastic map.
#               WARNING: This exhaustive approach can substantially increase computation time and 
#               memory requirements, and is therefore recommended only for moderate-sized analyses.

## Set the focal_time for analyses to 10 Mya.
focal_time <- 10

#### 3.1/ The "rates_only" strategy ####

# The "rates_only" strategy is the fastest option.
# It only accounts for diversification-rate uncertainty across BAMM posterior samples.
# Therefore, it uses Maximum Likelihood estimates for continuous traits as mapped on a unique 'contMap'.
# For categorical and biogeographic data, it uses the most frequent state/range
# as recorded in densityMaps/simmaps.

## Run deepSTRAPP on net diversification rates
deepSTRAPP_rates_only <- run_deepSTRAPP_for_focal_time(
    contMap = Ponerinae_contMap,
    # No need to provide stochastic maps if using the "rates_only" strategy
    # as only ML estimates from the contMap are used for testing
    # contMaps = Ponerinae_contMaps,
    trait_data_type = "continuous",
    BAMM_object = Ponerinae_BAMM_object,
    focal_time = focal_time,
    # Deal with uncertainty in estimates by combining trait ML estimates 
    # with all BAMM posterior samples
    uncertainty_strategy = "rates_only",
    seed = 1234, # Set seed for reproducibility
    # Needed to obtain STRAPP stats and plot evaluation histograms (See 4.2)
    return_perm_data = TRUE, 
    # Needed to get trait data and plot rates through time (See 4.3)
    extract_trait_data_melted_df = TRUE, 
    # Needed to get diversification data and plot rates through time (See 4.3)
    extract_diversification_data_melted_df = TRUE, 
    verbose = TRUE)

## Explore output
str(deepSTRAPP_rates_only, max.level = 1)

# See next step for comparison of outputs between strategies


#### 3.2/ The "paired" strategy ####

# The "paired" strategy is the default option.
# It accounts for both diversification-rate and ancestral trait/range reconstruction uncertainty.
# It pairs trait data extracted from stochastic maps with
# diversification data extracted from BAMM posterior samples.
# When the number of BAMM samples and stochastic maps differ, random pairing with replacement from 
# the smaller set is used so that all posterior samples and stochastic maps contribute to the analysis.

# It requires the full set of stochastic maps ('contMaps') as input to account 
# for uncertainty in ancestral trait estimates.
# For categorical and biogeographic data, it uses the states/ranges as recorded in 'densityMaps'/'simmaps'.
# If 'densityMaps' are provided, only the frequencies of states/ranges are recorded. 
# Trait data are then distributed accordingly across 'Dummy_maps' to reproduce the recorded frequencies.
# If 'simmaps' are provided, we can track which simulated history 
# (i.e., simmap) produced which trait data used for tests.

## Run deepSTRAPP on net diversification rates
deepSTRAPP_paired <- run_deepSTRAPP_for_focal_time(
    # A contMap can be provided optionally to be used for plotting
    contMap = Ponerinae_contMap,
    # Need to provide stochastic maps if using the "paired" strategy
    # as data from all simmaps are required to account for uncertainty in trait estimates
    contMaps = Ponerinae_contMaps,
    trait_data_type = "continuous",
    BAMM_object = Ponerinae_BAMM_object,
    focal_time = focal_time,
    # Deal with uncertainty in estimates by pairing trait simulations 
    # with BAMM posterior samples
    uncertainty_strategy = "paired",
    seed = 1234, # Set seed for reproducibility
    # Needed to obtain STRAPP stats and plot evaluation histograms (See 4.2)
    return_perm_data = TRUE, 
    # Needed to get trait data and plot rates through time (See 4.3)
    extract_trait_data_melted_df = TRUE, 
    # Needed to get diversification data and plot rates through time (See 4.3)
    extract_diversification_data_melted_df = TRUE, 
    verbose = TRUE)

## Explore output
str(deepSTRAPP_paired, max.level = 1)

# See next step for comparison of outputs between strategies

#### 3.3/ The "full" strategy ####

# The "full" strategy is the most exhaustive option.
# It accounts for both diversification-rate and ancestral trait/range reconstruction uncertainty.
# It crosses all trait data extracted from stochastic maps with all diversification data 
# extracted from BAMM posterior samples.
# WARNING: This exhaustive approach can substantially increase computation time and memory requirements
# and is therefore recommended only for moderate-sized analyses.

# It requires the full set of stochastic maps ('contMaps') as input 
# to account for uncertainty in ancestral trait estimates.
# For categorical and biogeographic data, it uses the states/ranges as recorded in 'densityMaps'/'simmaps'.
# If 'densityMaps' are provided, only the frequencies of states/ranges are recorded. 
# Trait data are then distributed accordingly across 'Dummy_maps' to reproduce the recorded frequencies.
# If 'simmaps' are provided, we can track which simulated history
#  (i.e., simmap) produced which trait data used for tests. 

## Run deepSTRAPP on net diversification rates
deepSTRAPP_full <- run_deepSTRAPP_for_focal_time(
    # A contMap can be provided optionally to be used for plotting
    contMap = Ponerinae_contMap,
    # Need to provide stochastic maps if using the "full" strategy
    # as data from all simmaps are required to account for uncertainty in trait estimates
    contMaps = Ponerinae_contMaps,
    trait_data_type = "continuous",
    BAMM_object = Ponerinae_BAMM_object,
    focal_time = focal_time,
    # Deal with uncertainty in estimates by crossing all trait simulations 
    # with all BAMM posterior samples
    uncertainty_strategy = "full",
    seed = 1234, # Set seed for reproducibility
    # Needed to obtain STRAPP stats and plot evaluation histograms (See 4.2)
    return_perm_data = TRUE, 
    # Needed to get trait data and plot rates through time (See 4.3)
    extract_trait_data_melted_df = TRUE, 
    # Needed to get diversification data and plot rates through time (See 4.3)
    extract_diversification_data_melted_df = TRUE, 
    verbose = TRUE)

## Explore output
str(deepSTRAPP_full, max.level = 1)

# See next step for comparison of outputs between strategies
# ------ Step 4: Compare outputs across uncertainty strategies ------ #

### 4.1/ Compare trait and rates data ####

# For "rates_only":
# Trait data includes only the ML estimates
table(deepSTRAPP_rates_only$trait_data_df$Map_ID)
# Diversification data includes 100 BAMM posteriors
table(deepSTRAPP_rates_only$diversification_data_df$BAMM_sample_ID)

# For "paired":
# Trait data includes 100 stochastic maps
table(deepSTRAPP_paired$trait_data_df$Map_ID)
# Diversification data includes 100 BAMM posteriors
table(deepSTRAPP_paired$diversification_data_df$BAMM_sample_ID)
# Both were randomly paired in testing following this list:
head(as.data.frame(deepSTRAPP_paired$trait_maps_vs_BAMM_samples_list))

# For "full":
# Trait data includes 100 stochastic maps
table(deepSTRAPP_full$trait_data_df$Map_ID)
# Diversification data includes 100 BAMM posteriors
table(deepSTRAPP_full$diversification_data_df$BAMM_sample_ID)
# Both were combined to produce 100 X 100 iterations for the STRAPP test

### 4.2/ Compare test results and histograms ####

## Aggregate test results in a summary df
STRAPP_results_df <- rbind(
  deepSTRAPP_rates_only$STRAPP_results[c(10, 1:4)],
  deepSTRAPP_paired$STRAPP_results[c(10, 1:4)],
  deepSTRAPP_full$STRAPP_results[c(10, 1:4)])

print(STRAPP_results_df)

## We performed a STRAPP test as a two-tailed Spearman's rank correlation test:
# Null hypothesis: no correlation between trait data and diversification rates.
# Alternative hypothesis: negative or positive correlation between trait data and diversification rates.
# The 'estimate' stat is the 5% quantile of differences in absolute rho-stats
# between observed and permuted data.
# The null hypothesis is rejected if 'estimate' is higher than zero / p-value lower than 0.05.

# All strategies provide similar results with 'estimate' Q5% stats ranging between 0.01 - 0.03,
# and p-values between 0.01 - 0.03.
# All analyses support a correlation between rates and trait values for focal_time = 10 Mya.

# Therefore, "paired" is the recommended default strategy as it allows us to account
# for uncertainty in trait estimates without inflating computation / RAM requirements


## Plot histograms of STRAPP test stats

# The black line represents the expected value under the null hypothesis H0
# => Δ abs(Spearman rho stat) = 0.
# The histogram shows the distribution of the test statistics as observed
# across the combination of trait estimates with BAMM posterior samples.
# The red line represents the significance threshold for which 95% of the observed data
# exhibited a higher value than expected (alpha = 0.05).
# When the red line is above the null expectation (i.e., the black line), the test is significant.

# For "rates_only":
plot_histogram_STRAPP_test_for_focal_time(
   deepSTRAPP_outputs = deepSTRAPP_rates_only,
   focal_time = focal_time)
# Q5% = 0.013 across a distribution of 100 stats 
# based on ML trait estimates combined with rates from 100 BAMM posterior samples.

# For "paired":
plot_histogram_STRAPP_test_for_focal_time(
   deepSTRAPP_outputs = deepSTRAPP_paired,
   focal_time = focal_time)
# Q5% = 0.023 across a distribution of 100 stats 
# based on trait data from 100 stochastic maps
# paired with rates from 100 BAMM posterior samples.

# For "full":
plot_histogram_STRAPP_test_for_focal_time(
   deepSTRAPP_outputs = deepSTRAPP_full,
   focal_time = focal_time)
# Q5% = 0.022 across a distribution of 10000 stats 
# based on trait data from 100 stochastic maps
# combined with rates from all 100 BAMM posterior samples.


### 4.3/ Compare rates vs traits plots ####

# Those plots display the data used for the tests,
# therefore they allow us to visualize how accounting for uncertainty in estimates
# affects the distribution of data used for testing

# For "rates_only":
plot_rates_vs_trait_data_for_focal_time(
   deepSTRAPP_outputs = deepSTRAPP_rates_only,
   focal_time = focal_time,
   color_scale = color_scale)

# For "paired":
plot_rates_vs_trait_data_for_focal_time(
   deepSTRAPP_outputs = deepSTRAPP_paired,
   focal_time = focal_time,
   color_scale = color_scale)

# For "full":
plot_rates_vs_trait_data_for_focal_time(
   deepSTRAPP_outputs = deepSTRAPP_full,
   focal_time = focal_time,
   color_scale = color_scale)

# Mean rates vs. trait data recorded across branches are almost the same for "rates_only",
# and fully equal between "paired" and "full" strategies.
# This is because the mean trait data recorded across stochastic maps is 
# by design converging towards the ML trait estimates used in "rates_only".
# The "paired" and "full" strategies yield similar mean data but slightly different STRAPP results,
# because the difference lies in the way trait data are combined with rates data
# for testing through permutation, but their mean values for a given branch are the same.

## Overall, the "paired" strategy is the suggested default strategy as it allows us to 
## account for uncertainty in trait estimates without inflating computation / RAM requirements,
## while providing results that are similar to an exhaustive approach like the 'full' strategy.