vignettes/estimating_effect_sizes.Rmd
estimating_effect_sizes.RmdIn the GLOW (inteGrative anaLysis using Optimized Weights) framework,
accurate estimation of allelic effect sizes
as a function of minor allele frequency (MAF)
is crucial for calculating optimal weights. This vignette demonstrates
how to estimate effect sizes from literature data using both the
all-in-one get_B() function and the train/predict workflow
(train_B_model() + predict_B()).
The GLOW framework assigns optimal weights to variants in a gene set based on two key components:
Accurate B estimation improves power by:
GLOWr provides two workflows for B estimation:
get_B() trains a model and
predicts B in a single call. Best for simple, one-shot analyses.train_B_model() trains
a model once, then predict_B() applies it to new MAF
values. Best for per-chromosome or repeated-prediction workflows.get_B() supports two complementary methods:
1. Direct Beta Method
2. P-value/Z-score Method
When both methods are applicable (same trait type, both BETA and p-values available), running both provides validation of estimates.
This vignette covers:
method="both"
get_B()
Here’s a complete example in ~10 lines:
library(GLOWr)
# Load example ALS data (lazy-loaded package data)
data(ALS_snvs_B_training)
# Define target MAFs (your study variants)
target_mafs <- seq(0.01, 0.4, length.out = 50)
# Estimate effect sizes
B <- get_B(
training_trait = "binary",
training_MAF = ALS_snvs_B_training$MAF,
training_BETA = ALS_snvs_B_training$BETA,
target_trait = "binary",
target_MAF = target_mafs,
target_case_prop = 0.001 # ALS case proportion in study
)
# Plot results
plot(target_mafs, B, type = "l", lwd = 2, col = "darkblue",
xlab = "Minor Allele Frequency", ylab = "Estimated Effect Size (B)",
main = "Effect Size vs MAF for ALS")That’s it! You now have effect size estimates for each target variant that can be used in optimal weight calculation.
train_B_model() +
predict_B()
When you need to predict B for multiple chromosome or variant sets, train the model once and reuse it:
library(GLOWr)
# Load example ALS data (lazy-loaded package data)
data(ALS_snvs_B_training)
# Step 1: Train the model (once)
b_model <- train_B_model(
training_trait = "binary",
training_MAF = ALS_snvs_B_training$MAF,
training_BETA = ALS_snvs_B_training$BETA,
method = "beta"
)
print(b_model)
# Step 2: Predict B for different target MAF sets (many times)
# E.g., per chromosome in a genome-wide analysis
for (chr in 1:22) {
chr_mafs <- get_chromosome_mafs(chr) # your function to get MAFs
B_chr <- predict_B(b_model, target_MAF = chr_mafs)
# ... use B_chr in downstream analysis
}
# The trained model can also be saved/loaded for later use
save_B_model(b_model, "my_trained_B_model.rds")
loaded_model <- load_B_model("my_trained_B_model.rds")The train/predict workflow avoids redundant model fitting and is the recommended approach for pipelines that process data chromosome-by-chromosome.
When you have effect sizes (BETA values) from studies of the same trait, the direct beta method models the relationship:
where is the effect size and is the MAF.
Mathematical Details:
The function:
Eight Candidate Models:
The function automatically tests 8 transformations:
| Model | Formula | When It Works Best |
|---|---|---|
| 1 | Linear MAF-effect relationship | |
| 2 | Effect peaks at intermediate MAF | |
| 3 | Effect increases with rarer variants | |
| 4 | Combines frequency function with log | |
| 5 | Exponential relationship | |
| 6 | Log-linear with frequency function | |
| 7 | Power law relationship | |
| 8 | Complex power law |
When to Use:
This method converts p-values to a trait-independent measure that can transfer across trait types.
Mathematical Details:
The transformation proceeds in steps:
Convert p-values to :
Calculate trait-independent : where is the sample size.
Model as function of MAF:
Convert back to trait-specific :
where is the standard error for continuous traits and is the case proportion in the target case-control study.
Key Insight:
The function represents a standardized effect that is independent of trait measurement units. This allows transfer of information between:
When to Use:
The method parameter controls which estimation approach
to use:
# method = "auto" (default)
# Automatically chooses:
# - "both" if same trait AND BETA available
# - "pvalue" otherwise
# method = "beta"
# Direct beta method only (requires BETA)
# method = "pvalue"
# P-value/Z-score method only (requires P, N)
# method = "both"
# Run both methods for comparison (when applicable)Decision Tree:
Training trait == Target trait?
├─ Yes → BETA available?
│ ├─ Yes → Use "both" (direct beta + p-value)
│ └─ No → Use "pvalue"
└─ No → Use "pvalue" only
Use direct beta method when you have effect sizes from the same trait:
# Simulate training data (or load from literature)
set.seed(123)
training_MAF <- runif(100, 0.001, 0.4)
# Effect size decreases with MAF (typical pattern)
training_BETA <- 0.3 * sqrt(training_MAF * (1 - training_MAF)) + rnorm(100, 0, 0.05)
# Your study variants
target_MAF <- c(0.01, 0.05, 0.1, 0.2, 0.3)
# Estimate B using direct beta method
B_beta <- get_B(
training_trait = "binary",
training_MAF = training_MAF,
training_BETA = training_BETA,
target_trait = "binary",
target_MAF = target_MAF,
method = "beta" # Direct beta method only
)
print(data.frame(MAF = target_MAF, B = B_beta))When you only have p-values (no effect sizes):
# Training data with p-values
set.seed(456)
training_MAF <- runif(100, 0.001, 0.4)
training_P <- runif(100, 1e-8, 0.1)
training_N <- sample(1000:10000, 100, replace = TRUE)
# Target parameters
target_MAF <- seq(0.01, 0.4, by = 0.05)
target_case_prop <- 0.1 # 10% cases in target case-control study
# Estimate B using p-value method
B_pval <- get_B(
training_trait = "binary",
training_MAF = training_MAF,
training_P = training_P,
training_N = training_N,
target_trait = "binary",
target_MAF = target_MAF,
target_case_prop = target_case_prop,
method = "pvalue"
)
print(head(data.frame(MAF = target_MAF, B = B_pval)))For continuous traits (e.g., height, BMI), you need standard errors:
# Training data from continuous trait GWAS
set.seed(789)
training_MAF <- runif(80, 0.01, 0.5)
training_P <- runif(80, 1e-6, 0.05)
training_N <- rep(10000, 80)
# Target continuous trait with SE
target_MAF <- seq(0.05, 0.45, by = 0.05)
target_SE <- rep(0.1, length(target_MAF)) # Standard error in target study
# Estimate B
B_continuous <- get_B(
training_trait = "continuous",
training_MAF = training_MAF,
training_P = training_P,
training_N = training_N,
target_trait = "continuous",
target_MAF = target_MAF,
target_SE = target_SE,
method = "pvalue"
)
print(head(data.frame(MAF = target_MAF, B = B_continuous)))Transfer information from continuous trait (BMD) to binary trait (osteoporosis):
# Training data from BMD GWAS (continuous trait)
set.seed(101)
training_MAF <- runif(200, 0.01, 0.5)
training_P <- runif(200, 1e-10, 0.01)
training_N <- sample(50000:100000, 200, replace = TRUE)
# Target is binary osteoporosis outcome
target_MAF <- seq(0.01, 0.5, length.out = 50)
target_case_prop <- 0.20 # 20% osteoporosis cases in study
# Cross-trait estimation
B_cross <- get_B(
training_trait = "continuous", # BMD is continuous
training_MAF = training_MAF,
training_P = training_P,
training_N = training_N,
target_trait = "binary", # Osteoporosis is binary
target_MAF = target_MAF,
target_case_prop = target_case_prop,
method = "pvalue", # Only p-value method works for cross-trait
verbose = 2
)
# Plot the transfer
plot(target_MAF, B_cross, type = "l", lwd = 2, col = "purple",
xlab = "MAF", ylab = "Estimated B (for binary outcome)",
main = "Cross-trait: BMD (continuous) to Osteoporosis (binary)")The package includes processed ALS (Amyotrophic Lateral Sclerosis) data from literature studies:
library(GLOWr)
# Load ALS data (lazy-loaded package data)
data(ALS_snvs_B_training)
# Examine the data
str(ALS_snvs_B_training)
head(ALS_snvs_B_training)
# Training data components
training_MAF <- ALS_snvs_B_training$MAF
training_BETA <- ALS_snvs_B_training$BETA
training_P <- ALS_snvs_B_training$P
training_N <- ALS_snvs_B_training$N
# Remove any missing values
valid_idx <- complete.cases(data.frame(training_MAF, training_BETA, training_P, training_N)) &
training_MAF > 0 & training_MAF < 1 &
abs(training_BETA) > 1e-10
training_MAF <- training_MAF[valid_idx]
training_BETA <- training_BETA[valid_idx]
training_P <- training_P[valid_idx]
training_N <- training_N[valid_idx]
cat(sprintf("ALS training data: %d variants after QC\n", length(training_MAF)))
# Define target MAFs (e.g., variants in your ALS study)
target_MAF <- seq(0.001, 0.4, length.out = 100)
# ALS case proportion (rare disease)
# This is the proportion of cases in your case-control study
# NOT the population prevalence
target_case_prop <- 0.001 # Example: 200 cases, 200,000 controls
# Estimate B with both methods for comparison
B_als <- get_B(
training_trait = "binary",
training_MAF = training_MAF,
training_BETA = training_BETA,
training_P = training_P,
training_N = training_N,
target_trait = "binary",
target_MAF = target_MAF,
target_case_prop = target_case_prop,
method = "both", # Run both for comparison
return_full = TRUE,
verbose = 2
)
# View method comparison
# The model is nested under $model (a glow_B_model object)
print(B_als$model$method_used)
if (!is.null(B_als$model$comparison$prediction)) {
cat("\nMethod Comparison (on predictions):\n")
cat(sprintf(" Correlation: %.3f\n", B_als$model$comparison$prediction$correlation))
cat(sprintf(" Mean %% difference: %.1f%%\n",
B_als$model$comparison$prediction$mean_percent_diff))
}
# Plot both methods
# B predictions are stored as B_beta_method / B_pvalue_method
par(mfrow = c(1, 2))
plot(target_MAF, B_als$B_beta_method, type = "l", lwd = 2, col = "blue",
xlab = "MAF", ylab = "B", main = "Direct Beta Method")
plot(target_MAF, B_als$B_pvalue_method, type = "l", lwd = 2, col = "red",
xlab = "MAF", ylab = "B", main = "P-value/Z-score Method")
par(mfrow = c(1, 1))
# Use primary estimates
B_final <- B_als$BExample with continuous trait (Bone Mineral Density):
# Load BMD data (lazy-loaded package data)
data(BMD_snvs_B_training)
# Examine data
str(BMD_snvs_B_training)
head(BMD_snvs_B_training)
# Training data
training_MAF <- BMD_snvs_B_training$MAF
training_P <- BMD_snvs_B_training$P
training_N <- BMD_snvs_B_training$N
# QC
valid_idx <- complete.cases(data.frame(training_MAF, training_P, training_N)) &
training_MAF > 0 & training_MAF < 1 &
training_P > 0 & training_P < 1
training_MAF <- training_MAF[valid_idx]
training_P <- training_P[valid_idx]
training_N <- training_N[valid_idx]
cat(sprintf("BMD training data: %d variants after QC\n", length(training_MAF)))
# Target parameters
target_MAF <- seq(0.01, 0.5, length.out = 100)
# For binary osteoporosis outcome
target_case_prop <- 0.20 # 20% cases in study
# Estimate B (cross-trait: continuous to binary)
B_bmd <- get_B(
training_trait = "continuous",
training_MAF = training_MAF,
training_P = training_P,
training_N = training_N,
target_trait = "binary",
target_MAF = target_MAF,
target_case_prop = target_case_prop,
method = "pvalue", # P-value method for cross-trait
return_full = TRUE,
verbose = 2
)
# Plot (B_pvalue_method is the same as B when only p-value method was used)
plot(target_MAF, B_bmd$B, type = "l", lwd = 2, col = "darkgreen",
xlab = "MAF", ylab = "B (binary outcome)",
main = "BMD to Osteoporosis: Cross-trait B Estimation")method="both"
When both methods are applicable, compare them to validate estimates:
# Use ALS data (same trait, both BETA and P available)
data(ALS_snvs_B_training)
# Prepare data
training_MAF <- ALS_snvs_B_training$MAF
training_BETA <- ALS_snvs_B_training$BETA
training_P <- ALS_snvs_B_training$P
training_N <- ALS_snvs_B_training$N
# Remove invalid
valid_idx <- complete.cases(data.frame(training_MAF, training_BETA, training_P, training_N)) &
training_MAF > 0 & training_MAF < 1
training_MAF <- training_MAF[valid_idx]
training_BETA <- training_BETA[valid_idx]
training_P <- training_P[valid_idx]
training_N <- training_N[valid_idx]
# Target
target_MAF <- seq(0.01, 0.4, length.out = 50)
# Run with both methods
result <- get_B(
training_trait = "binary",
training_MAF = training_MAF,
training_BETA = training_BETA,
training_P = training_P,
training_N = training_N,
target_trait = "binary",
target_MAF = target_MAF,
target_case_prop = 0.001,
method = "both", # Run both methods
return_full = TRUE,
verbose = 2
)
# Compare results
# Prediction-based comparison is in result$model$comparison$prediction
pred_comp <- result$model$comparison$prediction
cat("\n=== Method Comparison ===\n")
cat(sprintf("Correlation: %.4f\n", pred_comp$correlation))
cat(sprintf("RMSE: %.4f\n", pred_comp$rmse))
cat(sprintf("Mean percent diff: %.2f%%\n", pred_comp$mean_percent_diff))
cat(sprintf("Max percent diff: %.2f%%\n", pred_comp$max_percent_diff))
# Visualize comparison
par(mfrow = c(2, 2))
# Scatterplot (note: B_beta_method / B_pvalue_method field names)
plot(result$B_beta_method, result$B_pvalue_method,
xlab = "Direct Beta Method", ylab = "P-value Method",
main = "Method Comparison", pch = 16, col = rgb(0,0,1,0.5))
abline(0, 1, col = "red", lwd = 2, lty = 2)
legend("topleft", legend = sprintf("r = %.3f", pred_comp$correlation),
bty = "n")
# Both methods vs MAF
plot(target_MAF, result$B_beta_method, type = "l", lwd = 2, col = "blue",
xlab = "MAF", ylab = "B", main = "Both Methods",
ylim = range(c(result$B_beta_method, result$B_pvalue_method)))
lines(target_MAF, result$B_pvalue_method, lwd = 2, col = "red", lty = 2)
legend("topright", legend = c("Beta", "P-value"),
col = c("blue", "red"), lty = c(1, 2), lwd = 2, bty = "n")
# Difference vs MAF
diff <- result$B_beta_method - result$B_pvalue_method
plot(target_MAF, diff, type = "l", lwd = 2, col = "purple",
xlab = "MAF", ylab = "Difference (Beta - Pvalue)",
main = "Method Differences")
abline(h = 0, col = "gray", lty = 2)
# Percent difference
pct_diff <- 100 * abs(diff) / ((result$B_beta_method + result$B_pvalue_method) / 2)
plot(target_MAF, pct_diff, type = "l", lwd = 2, col = "orange",
xlab = "MAF", ylab = "Percent Difference (%)",
main = "Percent Differences")
abline(h = 10, col = "red", lty = 2)
par(mfrow = c(1, 1))
# Decide which to use
# If correlation > 0.9 and mean % diff < 10%: Either method is fine
# If correlation > 0.7 and mean % diff < 20%: Reasonable agreement
# Otherwise: Investigate discrepancies
if (pred_comp$correlation > 0.9) {
cat("\n[OK] High correlation between methods - both are reliable\n")
cat("Recommendation: Use direct beta method (primary)\n")
} else if (pred_comp$correlation > 0.7) {
cat("\n[NOTE] Moderate correlation between methods\n")
cat("Recommendation: Investigate differences, consider averaging\n")
} else {
cat("\n[WARNING] Low correlation between methods\n")
cat("Recommendation: Check data quality and parameter specifications\n")
}return_full=TRUE
Get detailed results including fitted models:
# Run with full output
result <- get_B(
training_trait = "binary",
training_MAF = training_MAF,
training_BETA = training_BETA,
target_trait = "binary",
target_MAF = target_MAF,
method = "beta",
return_full = TRUE # Get full result object
)
# Access components
names(result)
# [1] "B" "B_beta_method" "B_pvalue_method"
# [4] "model" "target_summary"
# The fitted models are nested under result$model$models
# result$model is a glow_B_model object
names(result$model)
# [1] "method_used" "models" "all_models_info"
# [4] "outliers" "training_summary" "training_data"
# [7] "comparison" "selection_criterion"
# Examine fitted model for the beta method
summary(result$model$models$beta_method)
# Check which transformation was selected
formula(result$model$models$beta_method)
# Model fit quality
cat(sprintf("R-squared: %.4f\n",
summary(result$model$models$beta_method)$r.squared))
cat(sprintf("Adjusted R-squared: %.4f\n",
summary(result$model$models$beta_method)$adj.r.squared))plot()
Visualize model fit and diagnostics:
# Get full result
result <- get_B(
training_trait = "binary",
training_MAF = training_MAF,
training_BETA = training_BETA,
training_P = training_P,
training_N = training_N,
target_trait = "binary",
target_MAF = target_MAF,
target_case_prop = 0.001,
method = "both",
return_full = TRUE
)
# Dashboard view (2x2 grid)
plot(result) # Equivalent to plot(result, type = "all")
# Individual plot types
plot(result, type = "fit") # Model fit
plot(result, type = "residuals") # Residual diagnostics
plot(result, type = "comparison") # Method comparison (if both methods)The dashboard includes:
get_B_diagnostics()
Comprehensive diagnostic report:
# Get full result first
result <- get_B(
training_trait = "binary",
training_MAF = training_MAF,
training_BETA = training_BETA,
training_P = training_P,
training_N = training_N,
target_trait = "binary",
target_MAF = target_MAF,
target_case_prop = 0.001,
method = "both",
return_full = TRUE
)
# Run diagnostics
diag <- get_B_diagnostics(
result,
show_plots = TRUE, # Display diagnostic plots
outlier_threshold = 4, # Cook's distance threshold
verbose = 1 # Print summary
)
# Examine components
names(diag)
# [1] "model_fit" "residual_stats" "outliers"
# [4] "method_comparison" "qc_flags" "summary_text"
# Model fit statistics
diag$model_fit$beta$R2 # R-squared
diag$model_fit$beta$adj_R2 # Adjusted R-squared
diag$model_fit$beta$formula # Selected model
# Residual statistics
diag$residual_stats$mean # Should be ~0
diag$residual_stats$normality_p # Shapiro-Wilk p-value
# Outliers
diag$outliers$n_outliers # Number of outliers
diag$outliers$proportion # Proportion of data
diag$outliers$indices # Which observations
# Quality control flags
if (length(diag$qc_flags) > 0) {
cat("\nQuality Control Issues:\n")
for (flag in diag$qc_flags) {
cat(" [!]", flag, "\n")
}
} else {
cat("\n[OK] No quality control issues detected\n")
}Model Fit (R²):
Residuals:
Outliers (Cook’s Distance):
Method Comparison:
The function tests 8 different transformations of X (MAF) and Y (effect size measure):
X transformations:
Y transformations:
Why multiple models?
Different genetic architectures produce different MAF-effect relationships:
The function automatically selects the best-fitting model by R², but you can compare all models:
# Get full result with fitted model
result <- get_B(
training_trait = "binary",
training_MAF = training_MAF,
training_BETA = training_BETA,
target_trait = "binary",
target_MAF = target_MAF,
return_full = TRUE
)
# See which model was selected
cat("Selected model formula:\n")
print(formula(result$model$models$beta_method))
# Check R-squared
cat(sprintf("\nR-squared: %.4f\n",
summary(result$model$models$beta_method)$r.squared))
# Note: compare_B_models() has limited functionality in current implementation
# It can only show the selected model, not re-fit all candidates
# This is documented in the function's limitationsIf direct beta and p-value methods give different results:
Common Causes:
target_case_prop or target_SE are correctWhat to do:
# If methods disagree, investigate:
# 1. Check correlation (prediction comparison is under $model$comparison$prediction)
pred_comp <- result$model$comparison$prediction
if (pred_comp$correlation < 0.7) {
cat("Low correlation - investigate further\n")
# 2. Plot difference vs MAF
plot(target_MAF, result$B_beta_method - result$B_pvalue_method,
xlab = "MAF", ylab = "Difference", main = "Method Differences by MAF")
abline(h = 0, col = "red", lty = 2)
# Are differences systematic or random?
# 3. Check outliers
diag <- get_B_diagnostics(result, show_plots = FALSE)
if (diag$outliers$n_outliers > 0.1 * length(training_MAF)) {
cat("Many outliers detected - may affect fit\n")
}
# 4. Check model fit quality (models are in result$model$models)
cat(sprintf("Beta method R²: %.3f\n",
summary(result$model$models$beta_method)$r.squared))
cat(sprintf("Pvalue method R²: %.3f\n",
summary(result$model$models$pvalue_method)$r.squared))
# If one R² is much lower, that method may be unreliable
}
# Decision rule:
# - High correlation + similar R²: Use either (prefer beta method)
# - Low correlation + similar R²: Average the two
# - Low correlation + different R²: Use method with higher R²
# - Very low correlation: Investigate data quality issuesTraining Data QC:
Before running get_B(), ensure:
# Check MAF range
summary(training_MAF)
stopifnot(all(training_MAF > 0 & training_MAF <= 0.5))
# Check for missing values
stopifnot(all(!is.na(training_MAF)))
stopifnot(all(!is.na(training_BETA)))
# Check effect sizes are reasonable
hist(abs(training_BETA), breaks = 30, main = "Distribution of |BETA|")
# Flag extremely large effects (potential errors)
if (any(abs(training_BETA) > 2)) {
warning("Some effect sizes |BETA| > 2 - check for data errors")
}
# Check sample sizes
if (!is.null(training_N)) {
summary(training_N)
# Recommend N > 500 for reliable estimates
if (any(training_N < 500)) {
warning(sprintf("%d variants from studies with N < 500", sum(training_N < 500)))
}
}Target Data QC:
# Ensure MAF in valid range
stopifnot(all(target_MAF > 0 & target_MAF <= 0.5))
# Check target parameters are sensible
if (!is.null(target_case_prop)) {
stopifnot(all(target_case_prop > 0 & target_case_prop < 1))
# For case-control: should be case proportion in STUDY, not population
if (any(target_case_prop < 0.001)) {
warning("Very low case proportions - verify this is case prop in study, not population prevalence")
}
}
if (!is.null(target_SE)) {
stopifnot(all(target_SE > 0))
# SE should be on same scale as training data
}Very rare variants (MAF < 0.001):
# Extrapolation to very rare MAF may be unreliable
target_MAF_rare <- c(0.0001, 0.0005, 0.001)
# Check if training data includes similarly rare variants
min_training_MAF <- min(training_MAF)
cat(sprintf("Minimum training MAF: %.5f\n", min_training_MAF))
if (min(target_MAF_rare) < min_training_MAF) {
warning("Extrapolating to MAF rarer than training data - estimates may be unreliable")
# Consider: Use only target MAF within training range
target_MAF_safe <- target_MAF_rare[target_MAF_rare >= min_training_MAF]
}Common variants (MAF > 0.4):
# Similar issue at upper end
max_training_MAF <- max(training_MAF)
cat(sprintf("Maximum training MAF: %.3f\n", max_training_MAF))
# If target includes MAF > max training, consider limiting
target_MAF_safe <- target_MAF[target_MAF <= max_training_MAF]Small training sample:
# With < 20 variants, model may overfit
if (length(training_MAF) < 20) {
warning("Small training sample (N < 20) - consider simpler model")
# Simpler models: prefer Y ~ X or Y ~ f(X)
# Current implementation always tests all 8 models
}
# Recommendation: ≥ 50 variants for reliable fit
if (length(training_MAF) < 50) {
cat("Note: Training N =", length(training_MAF), "- more data recommended\n")
}1. Harmonize MAF:
# Ensure MAF ≤ 0.5 (not > 0.5)
# If you have risk allele frequency (RAF), convert:
harmonize_MAF <- function(freq) {
ifelse(freq > 0.5, 1 - freq, freq)
}
training_MAF <- harmonize_MAF(training_RAF)2. Remove duplicates:
# If multiple studies report same variant, keep largest study
if (anyDuplicated(training_rsID)) {
# Order by sample size
training_data <- training_data[order(training_data$N, decreasing = TRUE), ]
# Remove duplicates
training_data <- training_data[!duplicated(training_data$rsID), ]
}3. Apply sample size filter:
# Exclude small studies (N < 500)
min_N <- 500
keep_idx <- training_N >= min_N
training_MAF <- training_MAF[keep_idx]
training_BETA <- training_BETA[keep_idx]
training_P <- training_P[keep_idx]
training_N <- training_N[keep_idx]
cat(sprintf("Retained %d/%d variants with N >= %d\n",
sum(keep_idx), length(keep_idx), min_N))Use Direct Beta Method when:
Use P-value Method when:
Use Both Methods when:
Effect Size Magnitude:
# For binary traits (log odds ratios):
# |B| < 0.1: Small effect
# |B| 0.1-0.3: Moderate effect
# |B| > 0.3: Large effect
# For continuous traits (standardized):
# |B| < 0.2: Small effect
# |B| 0.2-0.5: Moderate effect
# |B| > 0.5: Large effect
# Check your results
summary(B_estimates)
hist(B_estimates, breaks = 30, main = "Distribution of Estimated B")
# Flag extreme values
extreme <- B_estimates > quantile(B_estimates, 0.99)
if (any(extreme)) {
cat(sprintf("%d extreme B values detected (top 1%%)\n", sum(extreme)))
cat("MAFs with extreme B:", target_MAF[extreme], "\n")
}MAF-Effect Relationship:
# Typical patterns:
# 1. Decreasing: B decreases as MAF increases (purifying selection)
# 2. U-shaped: Lowest B at intermediate MAF
# 3. Constant: B doesn't vary with MAF (neutral)
# Plot to visualize
plot(target_MAF, B_estimates, type = "l", lwd = 2,
xlab = "MAF", ylab = "B",
main = "Effect Size vs MAF Pattern")
# Check monotonicity
diffs <- diff(B_estimates)
if (all(diffs < 0)) {
cat("Pattern: Monotonically decreasing (typical for deleterious variants)\n")
} else if (all(diffs > 0)) {
cat("Pattern: Monotonically increasing (unusual - check data)\n")
} else {
cat("Pattern: Non-monotonic\n")
}1. Confusing case proportion with prevalence:
# WRONG: Using population prevalence
target_case_prop <- 0.00015 # ALS population prevalence
# This is NOT what you want!
# CORRECT: Using study case proportion
# If you have 200 cases and 200 controls:
n_cases <- 200
n_controls <- 200
target_case_prop <- n_cases / (n_cases + n_controls) # = 0.5
# This is the correct value to use2. Extrapolating beyond training range:
# Check training range
range(training_MAF)
# [1] 0.01 0.45
# AVOID: Target MAF outside this range
target_MAF_bad <- seq(0.0001, 0.5, length.out = 100) # Goes to 0.0001
# BETTER: Stay within training range
target_MAF_good <- seq(0.01, 0.45, length.out = 100)3. Using wrong standard error:
# For continuous traits with p-value method:
# SE should match the trait scale
# WRONG: Using SE from different trait
target_SE_wrong <- 0.001 # SE from height study
# Applied to BMI estimation - scales don't match!
# CORRECT: Use SE from target trait or similar trait
target_SE_correct <- 0.05 # SE appropriate for BMI scale4. Ignoring diagnostics:
# DON'T just take B estimates blindly
B <- get_B(...) # Get estimates
# DO check quality
result <- get_B(..., return_full = TRUE)
diag <- get_B_diagnostics(result)
if (length(diag$qc_flags) > 0) {
cat("WARNING: Quality issues detected!\n")
# Investigate before using estimates
}For beginners:
# Simplest approach - automatic method selection
B <- get_B(
training_trait = "binary",
training_MAF = training_MAF,
training_BETA = training_BETA, # Or use training_P if no BETA
target_trait = "binary",
target_MAF = target_MAF,
target_case_prop = 0.1 # Adjust for your study
)For production analysis:
# Full workflow with QC and diagnostics
result <- get_B(
training_trait = "binary",
training_MAF = training_MAF,
training_BETA = training_BETA,
training_P = training_P,
training_N = training_N,
target_trait = "binary",
target_MAF = target_MAF,
target_case_prop = target_case_prop,
method = "both", # Compare methods
return_full = TRUE, # Get full diagnostics
verbose = 2 # Informative output
)
# Check quality
diag <- get_B_diagnostics(result, show_plots = TRUE)
if (length(diag$qc_flags) == 0) {
cat("[OK] Ready for downstream analysis\n")
B_final <- result$B
} else {
cat("[WARNING] Review quality issues before proceeding\n")
}| Scenario | Method | Notes |
|---|---|---|
| Same trait, BETA available |
method="beta" or "both"
|
Direct beta preferred |
| Same trait, no BETA | method="pvalue" |
Only option |
| Different traits | method="pvalue" |
Only option |
| Validation needed | method="both" |
Compare for robustness |
| Publication | method="both" |
Show consistency |
Training data:
Target data:
Use estimated B values in optimal weight calculation:
# 1. Estimate B
B <- get_B(
training_trait = "binary",
training_MAF = training_MAF,
training_BETA = training_BETA,
target_trait = "binary",
target_MAF = target_MAF,
target_case_prop = 0.1
)
# 2. Get variant-importance scores (PI)
# (Assume you have these from annotation tools)
PI <- c(0.1, 0.3, 0.05, 0.8, 0.2, 0.4, ...) # One per variant
# 3. Get correlation matrix M
# (From LD or kinship matrix)
M <- cor(genotype_matrix) # Or from LD reference
# 4. Calculate optimal weights
g_identity <- function(x) x
weights <- Optimal_Weights_M(g_identity, B, PI, M)
# 5. Use weights in association test
# (See other vignettes for GLOW test functions)Related Functions:
get_B() - All-in-one effect size estimation (train +
predict)train_B_model() - Train model separately (returns
glow_B_model)predict_B() - Predict B from a trained modelsave_B_model() / load_B_model() - Persist
and reload trained modelsplot.glow_B_estimate() /
plot.glow_B_model() - Visualization methodsget_B_diagnostics() - Comprehensive diagnostic
reportcompare_B_models() - Model comparison toolsselect_best_model() - Internal model selectionRelated Vignettes:
References:
Zhang, H., Liu, M., Landers, J. E., & Wu, Z. (2026). Integrated weighted association test with application to genetic association studies. The Annals of Applied Statistics, 20(3), 2074-2097. https://doi.org/10.1214/26-AOAS2223
For reproducibility:
sessionInfo()
#> R version 4.3.3 (2024-02-29)
#> Platform: x86_64-conda-linux-gnu (64-bit)
#> Running under: Ubuntu 24.04.4 LTS
#>
#> Matrix products: default
#> BLAS/LAPACK: /home/zheyangwu/.conda/envs/r_env/lib/libopenblasp-r0.3.32.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C LC_TIME=C.UTF-8
#> [4] LC_COLLATE=C.UTF-8 LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8
#> [7] LC_PAPER=C.UTF-8 LC_NAME=C LC_ADDRESS=C
#> [10] LC_TELEPHONE=C LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: America/New_York
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> loaded via a namespace (and not attached):
#> [1] digest_0.6.39 desc_1.4.3 R6_2.6.1 fastmap_1.2.0
#> [5] xfun_0.55 cachem_1.1.0 knitr_1.51 htmltools_0.5.9
#> [9] rmarkdown_2.30 lifecycle_1.0.4 cli_3.6.5 sass_0.4.10
#> [13] pkgdown_2.2.0 textshaping_1.0.3 jquerylib_0.1.4 systemfonts_1.2.3
#> [17] compiler_4.3.3 tools_4.3.3 ragg_1.5.0 bslib_0.9.0
#> [21] evaluate_1.0.5 yaml_2.3.12 otel_0.2.0 jsonlite_2.0.0
#> [25] rlang_1.1.6 fs_1.6.6 htmlwidgets_1.6.4