Skip to contents

Evaluates trait-imputation performance by rotating held-out values through k test folds. Hold out trait cells, whole species, or rooted clades. Returns per-fold and aggregated metrics.

Displays the number of folds and replicates, then reports each trait's mean and standard deviation for every metric. Returns x invisibly. Additional arguments are ignored by this print method.

Usage

cross_validate(
  data,
  tree,
  k = 5L,
  seeds = NULL,
  epochs = 500L,
  verbose = TRUE,
  holdout = c("cell", "species", "clade"),
  ...
)

# S3 method for class 'pigauto_cv'
print(x, ...)

# S3 method for class 'pigauto_cv'
summary(object, ...)

Arguments

data

pigauto_data object (output of preprocess_traits).

tree

phylo object.

k

integer. Number of folds (default 5).

seeds

optional integer vector. Seeds for replicate runs. The default NULL performs one run using the current RNG stream.

epochs

integer. Training epochs per fold (default 500).

verbose

logical. Print progress (default TRUE).

holdout

One of "cell" (default), "species", or "clade". Species and clade modes mask every trait and every observation of each held-out species. They require at least three folds and retain the full tree, including held-out tips.

...

Additional arguments forwarded by cross_validate() to fit_pigauto(); ignored by the print and summary methods.

x

A pigauto_cv object returned by cross_validate().

object

A pigauto_cv object returned by cross_validate().

Value

A list of class "pigauto_cv" with:

results

Data.frame with columns: fold, rep, trait, type, metric, value.

summary

Data.frame with mean and sd across folds/reps for each trait + metric.

conformal_coverage

Data.frame of coverage per trait across folds (if available).

k

Number of folds.

n_reps

Number of replicates.

holdout

The chosen holdout mode.

fold_assignments

For grouped modes, a data frame identifying the held-out species for each fold and replicate; NULL for cell mode.

Invisibly returns the summary data frame stored in object$summary.

Details

cross_validate() precomputes one graph shared across folds and uses k_eigen = 8L for that graph when k_eigen is omitted. An explicit k_eigen is used for the shared graph and forwarded to fit_pigauto(). Cell folds divide observed cells within each trait. Species folds assign entire species at random; clade folds partition the rooted tree into k disjoint rooted subtrees by repeatedly splitting the largest eligible subtree. A rooted ape::phylo representation is required, as determined by ape::is.rooted(); a root polytomy may require a root edge. Clade folds can be unequal in size. Multifurcating trees can defeat this greedy partition even when a partition exists. None of the modes are stratified by outcome class. The next fold is used for validation, and the rest for training. Grouped folds require at least two training observations and two training species for each trait.

In species and clade modes, trait centering, scaling, observed ranges, and other trait-map statistics are recalculated using the training rows only. The trait types, factor levels and log-transform choices from the supplied preprocessed data remain fixed across folds. Specify these in advance when they would otherwise be inferred from held-out values. The phylogeny and any supplied covariates remain available for held-out species. Grouped modes require data freshly constructed by the current preprocess_traits(). Cell-mode preprocessing retains its existing whole-data behaviour. Interpret each metric on its documented scale in evaluate_imputation; continuous and count RMSE use latent scales. Conformal coverage is calculated separately after predictions are decoded. Clade-structured holdouts need not be exchangeable with the validation cells, so nominal conformal coverage is not guaranteed.

Printing

print.pigauto_cv() reports k folds and the replicate count, followed by each trait's metric means and SDs. Displayed n is the number of represented fold-by-replicate metric rows, not just the fold count. This count includes rows with missing metric values; means and SDs omit those values. When available, a separate conformal block shows mean coverage and its SD against a nominal 95% target; the target is not a guarantee. Empty summaries print “No results.” The method ignores ... and returns x invisibly.

Summary method

summary.pigauto_cv() prints a compact per-trait table with RMSE, Pearson \(r\) for continuous traits, Spearman \(\rho\) for ordinal traits, and accuracy when returned (binary/categorical accuracy or multi-proportion dominant-component accuracy). A dash means the metric row is absent. A present but nonfinite metric may display NA or NaN. An empty summary prints “No results.” The full object$summary data frame, which can contain metrics omitted from this display, is returned invisibly. The method ignores ....

Examples

# \donttest{
data(avonet300, tree300, package = "pigauto")
tree <- ape::keep.tip(tree300, tree300$tip.label[seq_len(12L)])
traits <- avonet300[match(tree$tip.label, avonet300$Species_Key),
                     c("Mass", "Wing.Length"), drop = FALSE]
rownames(traits) <- tree$tip.label
cv <- cross_validate(preprocess_traits(traits, tree), tree, k = 3L,
                     seeds = 1L, epochs = 1L, verbose = FALSE,
                     k_eigen = 4L, hidden_dim = 8L,
                     use_transformer_blocks = FALSE)
#> Warning: Small validation set for 2 trait(s): Mass (n=4), Wing.Length (n=4). Calibrated gate and conformal scores will be noisy for these trait(s). 95% split-conformal coverage is NOT achievable for 2 trait(s) with fewer than 19 validation cells (Mass (n=4), Wing.Length (n=4)): the achievable ceiling is n_val / (n_val + 1), which only reaches 0.95 at n_val >= 19. See `?fit_pigauto` under 'Calibration at small n' for smoothing options.
#> Warning: Small validation set for 2 trait(s): Mass (n=4), Wing.Length (n=4). Calibrated gate and conformal scores will be noisy for these trait(s). 95% split-conformal coverage is NOT achievable for 2 trait(s) with fewer than 19 validation cells (Mass (n=4), Wing.Length (n=4)): the achievable ceiling is n_val / (n_val + 1), which only reaches 0.95 at n_val >= 19. See `?fit_pigauto` under 'Calibration at small n' for smoothing options.
#> Warning: Small validation set for 2 trait(s): Mass (n=4), Wing.Length (n=4). Calibrated gate and conformal scores will be noisy for these trait(s). 95% split-conformal coverage is NOT achievable for 2 trait(s) with fewer than 19 validation cells (Mass (n=4), Wing.Length (n=4)): the achievable ceiling is n_val / (n_val + 1), which only reaches 0.95 at n_val >= 19. See `?fit_pigauto` under 'Calibration at small n' for smoothing options.
print(cv)
#> 3-fold cross-validation (1 replicate)
#> ────────────────────────────────────────────────── 
#> Mass                      [continuous]
#>   rmse                0.9445 +/- 0.3935  (n=3)
#>   pearson_r          -0.2631 +/- 0.7741  (n=3)
#>   coverage_95         0.8333 +/- 0.1443  (n=3)
#> Wing.Length               [continuous]
#>   rmse                1.1610 +/- 0.6086  (n=3)
#>   pearson_r           0.0371 +/- 0.4233  (n=3)
#>   coverage_95         0.8333 +/- 0.2887  (n=3)
#> 
#> Conformal coverage (95% target):
#>   Mass                      0.833 +/- 0.289
#>   Wing.Length               0.750 +/- 0.250
summary(cv)
#> 3-fold cross-validation (1 replicate)
#> ────────────────────────────────────────────────────────────────────── 
#> Trait                     Type         RMSE (mean+/-sd)   r (mean+/-sd) Acc (mean+/-sd)
#> ───────────────────────────────────────────────────────────────────────────────────── 
#> Mass                      continuous   0.945 +/- 0.393 -0.263 +/- 0.774             ─
#> Wing.Length               continuous   1.161 +/- 0.609 0.037 +/- 0.423             ─
# Whole-species validation, with the full tree retained:
cv_species <- cross_validate(preprocess_traits(traits, tree), tree,
                             k = 3L, seeds = 1L, holdout = "species",
                             gnn = FALSE, verbose = FALSE)
#> Warning: Small validation set for 2 trait(s): Mass (n=4), Wing.Length (n=4). Calibrated gate and conformal scores will be noisy for these trait(s). 95% split-conformal coverage is NOT achievable for 2 trait(s) with fewer than 19 validation cells (Mass (n=4), Wing.Length (n=4)): the achievable ceiling is n_val / (n_val + 1), which only reaches 0.95 at n_val >= 19. See `?fit_pigauto` under 'Calibration at small n' for smoothing options.
#> Warning: Small validation set for 2 trait(s): Mass (n=4), Wing.Length (n=4). Calibrated gate and conformal scores will be noisy for these trait(s). 95% split-conformal coverage is NOT achievable for 2 trait(s) with fewer than 19 validation cells (Mass (n=4), Wing.Length (n=4)): the achievable ceiling is n_val / (n_val + 1), which only reaches 0.95 at n_val >= 19. See `?fit_pigauto` under 'Calibration at small n' for smoothing options.
#> Warning: Small validation set for 2 trait(s): Mass (n=4), Wing.Length (n=4). Calibrated gate and conformal scores will be noisy for these trait(s). 95% split-conformal coverage is NOT achievable for 2 trait(s) with fewer than 19 validation cells (Mass (n=4), Wing.Length (n=4)): the achievable ceiling is n_val / (n_val + 1), which only reaches 0.95 at n_val >= 19. See `?fit_pigauto` under 'Calibration at small n' for smoothing options.
cv_species$fold_assignments
#>    rep fold                  species
#> 1    1    1         Eudromia_formosa
#> 2    1    1           Rhea_americana
#> 3    1    1     Ptilopachus_petrosus
#> 4    1    1 Megapodius_nicobariensis
#> 5    1    2      Bambusicola_fytchii
#> 6    1    2  Francolinus_psilolaemus
#> 7    1    2     Dendragapus_obscurus
#> 8    1    2      Tragopan_temminckii
#> 9    1    3   Nothoprocta_pentlandii
#> 10   1    3       Meleagris_ocellata
#> 11   1    3 Tympanuchus_phasianellus
#> 12   1    3    Arborophila_campbelli
# }