knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 4, fig.align = "center" )
Analytical precision varies across the measurement range of most laboratory assays. Near the limit of detection, imprecision (expressed as coefficient of variation, CV) is typically higher, while at elevated concentrations, CV approaches an asymptotic minimum. Understanding this relationship is essential for:
This vignette demonstrates how to construct precision profiles and estimate functional sensitivity using valytics.
library(valytics) library(ggplot2)
A precision profile plots CV (or SD) against analyte concentration. For many immunoassays and clinical chemistry methods, this relationship follows a characteristic hyperbolic pattern:
$$CV = \sqrt{a^2 + \left(\frac{b}{x}\right)^2}$$
where: - $a$ is the asymptotic CV at high concentrations (the "floor" of precision) - $b$ is the concentration-dependent component - $x$ is the analyte concentration
At low concentrations, the $(b/x)^2$ term dominates, causing CV to increase sharply. At high concentrations, CV approaches $a$.
We will use the troponin_precision dataset, which contains multi-level precision data for a simulated high-sensitivity cardiac troponin I assay.
data("troponin_precision") head(troponin_precision)
The dataset includes measurements at six concentration levels from 5 to 500 ng/L, with a nested design of 5 days × 2 runs × 2 replicates per level.
# Summarize the experimental design table(troponin_precision$level, troponin_precision$day)
First, we analyze precision at each concentration level using precision_study(). The sample argument indicates that each level should be analyzed separately.
prec <- precision_study( data = troponin_precision, value = "value", sample = "level", day = "day", run = "run" ) print(prec)
The results show variance components and precision estimates. For precision profiles, we are primarily interested in within-laboratory precision (or repeatability) at each concentration level.
We can examine results for individual levels:
# Access results for each concentration level names(prec$by_sample) # Example: Level 1 (5 ng/L) prec$by_sample$L1$precision
Now we generate the precision profile by modeling CV as a function of concentration:
profile <- precision_profile( prec, model = "hyperbolic", cv_targets = c(10, 20) ) print(profile)
The output shows: - Model parameters: The fitted values of $a$ (asymptotic CV) and $b$ - Fit quality: R² and RMSE indicating how well the model fits the data - Functional sensitivity: The concentration at which target CVs are achieved
# Extract model parameters a <- profile$model$parameters["a"] b <- profile$model$parameters["b"] cat(sprintf("Asymptotic CV (a): %.2f%%\n", a)) cat(sprintf("Concentration component (b): %.2f\n", b)) cat(sprintf("\nModel equation: %s\n", profile$model$equation))
The asymptotic CV tells us the best precision achievable at high concentrations. In this case, ~3% CV represents the analytical "floor" of the assay.
plot(profile)
The plot displays: - Points: Observed CV values at each concentration level - Blue curve: Fitted hyperbolic model - Dashed lines: Target CV thresholds (10% and 20%) - Dotted lines: Concentrations at which targets are achieved (functional sensitivity)
For a logarithmic concentration scale (often more informative across wide ranges):
plot(profile, log_x = TRUE)
Functional sensitivity is the lowest concentration at which a measurement procedure achieves a specified level of precision. Common thresholds include:
profile$functional_sensitivity
In this example:
- At 10% CV, the functional sensitivity is approximately r round(profile$functional_sensitivity$concentration[1], 1) ng/L
- At 20% CV, the functional sensitivity is approximately r round(profile$functional_sensitivity$concentration[2], 1) ng/L
If a target CV is below the asymptotic minimum ($a$), it cannot be achieved at any concentration:
# Try a target CV of 2% (below asymptotic ~3%) profile_strict <- precision_profile( prec, cv_targets = c(2, 5, 10) ) profile_strict$functional_sensitivity
The 2% CV target is marked as "not achievable" because it is below the assay's asymptotic CV floor.
For uncertainty quantification on functional sensitivity estimates, use bootstrap resampling:
# Note: This takes longer to run profile_boot <- precision_profile( prec, cv_targets = c(10, 20), bootstrap = TRUE, boot_n = 999 ) profile_boot$functional_sensitivity
The confidence intervals help assess the precision of the functional sensitivity estimates.
For some assays, a simpler linear model may be adequate:
$$CV = a + \frac{b}{x}$$
profile_linear <- precision_profile( prec, model = "linear", cv_targets = c(10, 20) ) print(profile_linear)
Compare model fits:
cat("Hyperbolic model R²:", round(profile$fit_quality$r_squared, 4), "\n") cat("Linear model R²:", round(profile_linear$fit_quality$r_squared, 4), "\n")
The hyperbolic model typically provides better fit because it correctly captures the asymptotic behavior at high concentrations.
The summary() function provides comprehensive output:
summary(profile)
This includes: - Fitted values at each concentration level - Residual analysis - Complete interpretation of functional sensitivity results
If you have pre-calculated CV values (e.g., from external sources), you can create a precision profile directly from a data frame:
# Create a data frame with concentration and CV values cv_data <- data.frame( concentration = c(5, 10, 25, 50, 100, 500), cv_pct = c(5.8, 4.2, 3.5, 3.2, 3.1, 3.0) ) profile_df <- precision_profile( cv_data, concentration = "concentration", cv = "cv_pct" ) print(profile_df)
For high-sensitivity cardiac troponin assays, regulatory and clinical guidelines suggest:
Then functional sensitivity should be ≤ 10 ng/L
CV at the 99th percentile should be ≤ 10%
Let us check our simulated assay against these criteria:
# Assume 99th percentile URL = 20 ng/L url_99th <- 20 # Check functional sensitivity at 10% CV fs_10pct <- profile$functional_sensitivity$concentration[ profile$functional_sensitivity$cv_target == 10 ] cat(sprintf("99th percentile URL: %.0f ng/L\n", url_99th)) cat(sprintf("Functional sensitivity (10%% CV): %.1f ng/L\n", fs_10pct)) cat(sprintf("Ratio (FS / URL): %.2f\n", fs_10pct / url_99th)) if (fs_10pct <= 0.5 * url_99th) { cat("\n✓ Meets criterion: FS ≤ 50% of 99th percentile URL\n") } else { cat("\n✗ Does not meet criterion: FS > 50% of 99th percentile URL\n") }
Based on established methodology:
The precision profile workflow in valytics:
precision_study() with sample argument to analyze each levelprecision_profile() to model CV-concentration relationshipplot() to communicate findings# Complete workflow data("troponin_precision") # Step 1: Precision study prec <- precision_study( data = troponin_precision, value = "value", sample = "level", day = "day", run = "run" ) # Step 2: Precision profile profile <- precision_profile( prec, model = "hyperbolic", cv_targets = c(10, 20) ) # Step 3: Interpret and visualize summary(profile) plot(profile, log_x = TRUE)
Armbruster DA, Pry T (2008). Limit of blank, limit of detection and limit of quantitation. Clinical Biochemist Reviews, 29(Suppl 1):S49-S52.
Apple FS, et al. (2017). Cardiac Troponin Assays: Guide to Understanding Analytical Characteristics and Their Impact on Clinical Care. Clinical Chemistry, 63(1):73-81.
Chesher D (2008). Evaluating assay precision. Clinical Biochemist Reviews, 29(Suppl 1):S23-S26.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.