A model-agnostic decomposition framework for discrete-time simulations.
# install.packages("remotes")
remotes::install_github("aucarter/shapleydecomp")
New to the package? Start with the Get started article. The Methods article covers the maths.
shapleydecomp attributes changes in simulation outcomes to individual input
factors using Shapley value decomposition. Any calibrated discrete-time
simulation model can use this package by implementing a small set of S3
methods.
The package supports two modes of counterfactual analysis:
| Type | cf_type |
Counterfactual value | Interpretation |
|---|---|---|---|
| Change since | "change_since" |
Value at a baseline year | Impact of changes in the factor since the baseline year |
| Total impact | "total_impact" |
Zero / no intervention | Total impact of the factor (as if it never existed) |
To use shapleydecomp with your model, create an S3 class and implement these
required methods:
# 1. Run your simulation with given parameters
run_simulation.my_model <- function(model, params, ...) { ... }
# 2. Summarize simulation output into a data.table with a 'year' column
# plus one column per summary variable (e.g., deaths, infections)
summarize_results.my_model <- function(model, sim_output, ...) { ... }
# 3. Construct counterfactual parameters for a single factor
build_counterfactual.my_model <- function(model, params, factor_name,
decomp_year, cf_type,
baseline_values, ...) { ... }
# 4. Get the baseline value for a factor
get_baseline_value.my_model <- function(model, params, factor_name, cf_type,
baseline_year, ...) { ... }
Methods defined at the top level of a script are found by ordinary S3
dispatch. Methods defined anywhere else need registering with
.S3method(). In a package, put @export on each method so that NAMESPACE
declares it with S3method().
And these optional methods (for performance/setup):
# One-time preparation before decomp loop (e.g., initial simulation,
# count-to-percentage conversion)
prepare_decomp.my_model <- function(model, params, ...) { ... }
# Warm-start support (avoid re-simulating years before decomp year)
supports_warm_start.my_model <- function(model, ...) { TRUE }
get_warm_start_state.my_model <- function(model, sim_output, year, ...) { ... }
run_from_warm_start.my_model <- function(model, warm_state, params,
output_years, ...) { ... }
library(shapleydecomp)
# Create your model object (S3 class with methods above)
model <- create_my_model(...)
# Run decomposition
results <- shapley_decompose(
model = model,
params = my_params,
factors = c("treatment_a", "treatment_b", "demographic_x"),
summary_vars = c("deaths", "infections"),
decomp_years = 2000:2023,
method = "shapley", # only supported method
cf_type = "change_since" # or "total_impact"
)
# Results
results$decomp # Standard decomposition (factor effects per year)
results$step_scenarios # All-on time series per decomp year
results$extended_decomp # Extended decomposition (effects at future years)
results$run_table # Factorial design table
Before running a full decomposition, validate that your model correctly implements the interface:
validate_model_interface(
model = my_model,
params = my_params,
factors = c("factor_a", "factor_b"),
summary_vars = c("outcome_1"),
baseline_year = 2000
)
Shapley value decomposition weights each factor's marginal effect by
1 / (n × C(n-1, k)), where k is the number of other factors in the "on"
state, averaging the factor's contribution over all coalition sizes
(equivalently, over all factor orderings). This guarantees factor effects sum
to the total difference between the fully-observed and fully-counterfactual
scenarios.
Das Gupta's symmetric standardization is the special case of the Shapley value
for binary on/off factors and produces identical weights, so method = "das_gupta" is accepted as a deprecated alias for the same Shapley
computation.