add-yardstick-metric — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited add-yardstick-metric (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Guide for developing new metrics that extend the yardstick package. This skill provides best practices, code templates, and testing patterns for creating custom performance metrics.
--------------------------------------------------------------------------------
This skill supports two distinct development contexts:
Creating a new R package that extends yardstick with custom metrics.
:::)Contributing directly to yardstick via pull requests.
--------------------------------------------------------------------------------
INSTRUCTIONS FOR CLAUDE: Run the verification script first to determine the development context:
Rscript -e 'source(Sys.glob(path.expand("~/.claude/plugins/cache/tidymodels-skills/tidymodels-dev/*/tidymodels/shared-references/scripts/verify-setup.R"))[1])'Then follow the appropriate path based on the output:
Development Guide](references/source-guide.md)
Go to Extension Development Guide
Prerequisites](references/package-extension-prerequisites.md) to resolve warnings first
--------------------------------------------------------------------------------
Creating a custom yardstick metric provides:
metric_set()INSTRUCTIONS FOR CLAUDE: Check if repos/yardstick/ exists in the current working directory. Use this to guide development:
If `repos/yardstick/` exists:
repos/yardstick/R/prob-brier_class.R) to studyimplementation patterns
repos/yardstick/tests/testthat/test-prob-roc_auc.R)for testing patterns
If `repos/yardstick/` does NOT exist:
Guide](references/package-repository-access.md)
https://github.com/tidymodels/yardstick/blob/main/R/[file-name].Rhttps://github.com/tidymodels/yardstick/blob/main/R/prob-brier_class.R
When to use repository references:
averaging?")
See Repository Access Guide for setup instructions.
Development Guides:
packages that extend yardstick
yardstick itself
Reference Files:
new\_\*\_metric() works, .estimator column, design considerations
multiple metrics
disparity/fairness metrics
RMSE, MSE)
(accuracy, precision, recall)
metrics (ROC AUC, log loss)
Ordinal classification metrics (Ranked Probability Score)
survival metrics (Concordance Index)
Time-dependent survival metrics (Brier Survival)
Integrated survival metrics (Integrated Brier)
Metrics](references/linear-predictor-survival-metrics.md) - Linear predictor metrics (Royston's D)
metrics (Weighted Interval Score)
yardstick_table()
confusion matrices)
Shared References (Extension Development):
Extension prerequisites
iteration cycle
(Extension)](references/package-extension-requirements.md#testing-requirements)
Documentation templates
(Extension)](references/package-extension-requirements.md#best-practices) - Extension code style
(Extension)](references/package-extension-requirements.md#common-issues-solutions)
Source Development Specific:
internal test helpers
internal functions
Source-specific issues
See Development Workflow for complete details.
Fast iteration cycle (run repeatedly):
devtools::document() - Generate documentationdevtools::load_all() - Load your packagedevtools::test() - Run testsFinal validation (run once at end):
devtools::check() - Full R CMD checkWARNING: Do NOT run check() during iteration. It takes 1-2 minutes and is unnecessary until you're done.
--------------------------------------------------------------------------------
These requirements ensure your code aligns with tidymodels patterns and passes review.
When contributing to yardstick itself, you MUST:
1. Use internal helpers - Don't reimplement what exists:
# ✅ CORRECT - Use internal helper
mae_impl <- function(truth, estimate, case_weights = NULL) {
errors <- abs(truth - estimate)
yardstick_mean(errors, case_weights = case_weights) # Use this
}
# ❌ WRONG - Reimplementing what already exists
mae_impl <- function(truth, estimate, case_weights = NULL) {
errors <- abs(truth - estimate)
if (is.null(case_weights)) {
mean(errors)
} else {
weighted.mean(errors, w = as.double(case_weights))
}
}Common internal helpers: yardstick_mean(), finalize_estimator_internal(), check_*_metric(), yardstick_remove_missing(), yardstick_any_missing()
2. NO package prefix - Functions are in the same package:
# ✅ CORRECT - No prefix
yardstick_mean(errors, case_weights = case_weights)
check_numeric_metric(truth, estimate, case_weights)
# ❌ WRONG - Don't prefix your own package
yardstick::yardstick_mean(errors, case_weights = case_weights)
yardstick::check_numeric_metric(truth, estimate, case_weights)3. Include @examples - Required for all exported functions:
#' @examples
#' # Basic usage
#' mae(solubility_test, solubility, prediction)
#'
#' # With case weights
#' library(dplyr)
#' solubility_test %>%
#' mutate(weight = 1:nrow(.)) %>%
#' mae(solubility, prediction, case_weights = weight)When creating packages that extend yardstick, you MUST:
1. ALWAYS use package prefix - Explicitly namespace all yardstick functions:
# ✅ CORRECT - Always prefix
yardstick::check_numeric_metric(truth, estimate, case_weights)
yardstick::new_numeric_metric(wape, direction = "minimize")
yardstick::numeric_metric_summarizer(...)
# ❌ WRONG - Missing prefix (will fail R CMD check without imports)
check_numeric_metric(truth, estimate, case_weights)
new_numeric_metric(wape, direction = "minimize")2. NEVER use internal functions - Cannot access ::::
# ❌ FORBIDDEN - Extension developers cannot use :::
yardstick:::yardstick_mean(errors, case_weights)
yardstick:::finalize_estimator_internal(estimator, ...)
# ✅ CORRECT - Implement manually or use exported functions
if (!is.null(case_weights)) {
case_weights <- as.double(case_weights)
weighted.mean(errors, w = case_weights)
} else {
mean(errors)
}3. Include @examples - Required for usability:
#' @examples
#' library(modeldata)
#' data(solubility_test)
#'
#' # Calculate WAPE
#' wape(solubility_test, solubility, prediction)ALWAYS convert hardhat weight objects to numeric. This is required in all \_impl functions that accept case weights:
Extension development example:
wape_impl <- function(truth, estimate, case_weights = NULL) {
errors <- abs(truth - estimate)
# REQUIRED: Convert hardhat weight objects to numeric
if (!is.null(case_weights)) {
if (inherits(case_weights, c("hardhat_importance_weights",
"hardhat_frequency_weights"))) {
case_weights <- as.double(case_weights) # ← CRITICAL
}
weighted.mean(errors, w = case_weights)
} else {
mean(errors)
}
}Source development example:
mae_impl <- function(truth, estimate, case_weights = NULL) {
errors <- abs(truth - estimate)
# REQUIRED: Convert hardhat weight objects to numeric
if (!is.null(case_weights)) {
if (inherits(case_weights, c("hardhat_importance_weights",
"hardhat_frequency_weights"))) {
case_weights <- as.double(case_weights) # ← CRITICAL
}
}
# Then use internal helper
yardstick_mean(errors, case_weights = case_weights)
}Why this matters: Hardhat weights are S3 objects, not plain numerics. Without conversion, arithmetic operations and functions like weighted.mean() will fail. This conversion is mandatory in every _impl function that handles case weights.
--------------------------------------------------------------------------------
┌─────────────────────────────────────────────────────────────┐
│ What type of data do you have? │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────────┼───────────────────┬───────────────────┐
│ │ │ │
▼ ▼ ▼ ▼
Regression Classification Survival Analysis Quantile Forecasting
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ NUMERIC │ │ CLASS-BASED │ │ SURVIVAL │ │ QUANTILE │
│ METRICS │ │ METRICS │ │ METRICS │ │ METRICS │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
│ ├───────────────────┤ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Classes │ │Probabilities│ │
│ └─────────────┘ └─────────────┘ │
│ │ │ │
│ ├───────────────────┤ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │Unordered? │ │Unordered? │ │
│ │ CLASS │ │ PROBABILITY │ │
│ └─────────────┘ └─────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │Ordered? │ │Ordered? │ │
│ │ORDERED PROB │ │ORDERED PROB │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ┌───────────────────┬───────────────┬─────────┤
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
Examples: Examples: Examples: Examples: Examples:
- MAE - Accuracy - ROC AUC - Concordance - WIS
- RMSE - Precision - Log Loss - Brier - Pinball
- R² - F1 Score - PR AUC - Royston's D - Coverage
- Ranked Prob Score
Survival Metrics Breakdown:
- STATIC: Single overall value (e.g., Concordance)
- DYNAMIC: Value per time point (e.g., Time-dependent Brier)
- INTEGRATED: Averaged across time (e.g., Integrated Brier)
- LINEAR PRED: From Cox models (e.g., Royston's D)Decision guide:
Metrics](references/numeric-metrics.md)
Metrics](references/class-metrics.md)
probabilities → Probability Metrics
probabilities → Ordered Probability Metrics
from Cox model → Linear Predictor Survival Metrics
Metrics](references/quantile-metrics.md)
For a complete, step-by-step implementation of a numeric metric (MAE), see the comprehensive example with all required components in:
👉 [Numeric Metrics Reference](references/numeric-metrics.md)
This reference includes:
mae_impl) with case weights handlingmae_vec) with validation and NA handlingnew_numeric_metric() wrappercase weights
Quick preview of the pattern:
_impl() function: Core calculation logic_vec() function: Validation and NA handling.data.frame() method: Integration with yardstick systemSee also Extension Development Guide for the complete implementation walkthrough.
Use for: Regression metrics where truth and predictions are continuous numbers.
Pattern: Three-function approach (_impl, _vec, data.frame method)
Complete guide: Numeric Metrics
Key points:
check_numeric_metric() for validationweighted.mean()yardstick_remove_missing() for NA handling.estimator = "standard"Examples: MAE, RMSE, MSE, Huber Loss, R-squared
Reference implementations:
R/num-mae.R, R/num-rmse.R, R/num-mse.RR/num-huber_loss.R (has delta parameter)R/num-ccc.R (correlation-based)Use for: Classification metrics where truth and predictions are factor classes.
Pattern: Uses confusion matrix from yardstick_table()
Complete guide: Class Metrics
Key points:
yardstick_table() to create weighted confusion matrix_binary and _estimator_impl functionsevent_level parameterExamples: Accuracy, Precision, Recall, F1, Specificity
Reference implementations:
R/class-accuracy.R, R/class-precision.R,R/class-recall.R
R/class-f_meas.R (F1 score)R/class-bal_accuracy.R (handles class imbalance)Use for: Metrics that evaluate predicted probabilities against true classes.
Pattern: Similar to class metrics but uses probability columns
Complete guide: Probability Metrics
Key points:
check_prob_metric() for validationExamples: ROC AUC, Log Loss, Brier Score, PR AUC
Reference implementations:
R/prob-roc_auc.R, R/prob-pr_auc.RR/prob-brier_class.R, R/prob-mn_log_loss.RUse for: Ordinal classification metrics where class ordering matters.
Pattern: Three-function approach with cumulative probabilities
Complete guide: Ordered Probability Metrics
Key points:
check_ordered_prob_metric() for validationExamples: Ranked Probability Score (RPS)
Use for: Overall survival metrics with single numeric predictions.
Pattern: Three-function approach with Surv objects
Complete guide: Static Survival Metrics
Key points:
check_static_survival_metric() for validationExamples: Concordance Index (C-index)
Use for: Time-dependent survival metrics at specific evaluation times.
Pattern: Three-function approach with list-column predictions
Complete guide: Dynamic Survival Metrics
Key points:
.eval_time, .pred_survival,.weight_censored
Examples: Time-dependent Brier Score, Time-dependent ROC AUC
Use for: Overall survival metrics integrated across evaluation times.
Pattern: Two-function approach (calls dynamic metric, then integrates)
Complete guide: Integrated Survival Metrics
Key points:
Examples: Integrated Brier Score, Integrated ROC AUC
Use for: Metrics for linear predictors from Cox models.
Pattern: Three-function approach with transformations
Complete guide: Linear Predictor Survival Metrics
Key points:
check_linear_pred_survival_metric() for validationExamples: Royston's D statistic, R²_D
Use for: Quantile prediction metrics for uncertainty quantification.
Pattern: Three-function approach with quantile_pred objects
Complete guide: Quantile Metrics
Key points:
hardhat::quantile_pred objectfn_options for additional parametersExamples: Weighted Interval Score (WIS), Pinball Loss
See Roxygen Documentation for complete templates.
Required roxygen tags:
#' @family [metric category] metrics
#' @export
#' @inheritParams [similar_metric]
#' @param data A data frame
#' @param truth Unquoted column with true values
#' @param estimate Unquoted column with predictions
#' @param na_rm Remove missing values (default TRUE)
#' @param case_weights Optional case weights column
#' @return A tibble with .metric, .estimator, .estimate columnsSee Testing Patterns (Extension) for comprehensive guide.
Required test categories: 1. Correctness: Metric calculates correctly 2. NA handling: Both na_rm = TRUE and FALSE 3. Input validation: Wrong types, mismatched lengths 4. Case weights: Weighted and unweighted differ 5. Edge cases: All correct, all wrong, empty data
See Confusion Matrix for complete guide.
# Get confusion matrix
xtab <- yardstick::yardstick_table(truth, estimate, case_weights)
# Extract values (for binary classification)
tp <- xtab[2, 2] # True positives: truth = second, pred = second
tn <- xtab[1, 1] # True negatives: truth = first, pred = first
fp <- xtab[1, 2] # False positives: truth = first, pred = second
fn <- xtab[2, 1] # False negatives: truth = second, pred = firstSee Case Weights for complete guide.
IMPORTANT: Always convert hardhat weights to numeric using `as.double()`
# Extension development pattern (no internal functions):
# ALWAYS include this conversion pattern in your _impl function
if (!is.null(case_weights)) {
# Convert hardhat weight objects to numeric vector
# This is REQUIRED - hardhat weights are S3 objects, not plain numerics
if (inherits(case_weights, c("hardhat_importance_weights",
"hardhat_frequency_weights"))) {
case_weights <- as.double(case_weights) # ← CRITICAL: Must convert to double
}
}
# Use in calculations
if (is.null(case_weights)) {
mean(values)
} else {
weighted.mean(values, w = case_weights) # Now safe to use with base R functions
}Why `as.double()` is required:
weighted.mean() expect numeric vectorsyardstick_mean() which handles this internallySee Class Metrics for complete guide.
Macro averaging: Average of per-class metrics (treats all classes equally) Micro averaging: Pool all observations, calculate once (treats all observations equally) Macro-weighted averaging: Weighted average by class prevalence
Once you've created your metric, you can combine it with other metrics using metric_set():
my_metrics <- metric_set(mae, rmse, my_custom_metric)
my_metrics(data, truth = y, estimate = y_pred)Key benefits:
See Combining Metrics for complete guide.
Groupwise metrics measure disparity in metric values across groups (useful for fairness):
accuracy_diff <- new_groupwise_metric(
fn = accuracy,
name = "accuracy_diff",
aggregate = function(x) diff(range(x$.estimate))
)
accuracy_diff_by_group <- accuracy_diff(group_column)
accuracy_diff_by_group(data, truth, estimate)Use cases:
See Groupwise Metrics for complete guide.
If you're contributing to yardstick itself, you have access to internal functions and conventions not available in extension development.
Yardstick uses strict naming patterns:
R/num-[name].R → R/num-mae.RR/class-[name].R → R/class-accuracy.RR/prob-[name].R → R/prob-roc_auc.Rtests/testthat/test-num-mae.RWhen developing yardstick itself, you can use:
yardstick_mean() - Weighted mean with case weight handlingfinalize_estimator_internal() - Estimator selection for multiclasscheck_numeric_metric(), check_class_metric(), check_prob_metric() -Validation
data_altman(), data_three_class(), data_hpc_cv1()Yardstick uses templates in man-roxygen/:
#' @templateVar fn mae
#' @template return
#' @template event_firstYardstick uses testthat::expect_snapshot() extensively:
test_that("mae returns correct structure", {
expect_snapshot(mae(df, truth, estimate))
})Complete source development guide: Source Development Guide
--------------------------------------------------------------------------------
See Package Imports for complete guide.
Create R/{packagename}-package.R:
#' @keywords internal
"_PACKAGE"
#' @importFrom rlang .data := !! enquo enquos
#' @importFrom yardstick new_numeric_metric new_class_metric new_prob_metric
NULLAll metrics must be exported:
#' @export
mae <- function(data, ...) {
UseMethod("mae")
}See Best Practices (Extension) for complete guide.
Key principles:
|> not magrittr pipe %>%purrr::map() for better error messagescli::cli_abort() for error messages_vec), trust data in _implSee Troubleshooting (Extension) for complete guide.
Common issues:
devtools::load_all() before testingna_rm = TRUE and FALSE cases--------------------------------------------------------------------------------
Extension development (creating new package):
Typically creates 2 files (3 if README needed); acceptable to reach 4-6 if implementation requires it.
Source development (PR to yardstick):
Creates exactly 2 files; acceptable to reach 3-5 if implementation requires it.
❌ AVOID creating these files:
Where everything goes:
--------------------------------------------------------------------------------
outputs that need custom metrics
require custom tuning parameters for hyperparameter optimization
predictions from model specifications
predictions from model engines
For Extension Development (creating new packages):
Prerequisites](references/package-extension-prerequisites.md) - START HERE
For Source Development (contributing to yardstick):
Access](references/package-repository-access.md)
R/num-*.R, R/class-*.R, etc.(Source)](references/testing-patterns-source.md)
PR process
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.