The package FairTargeting implements procedures for Fair
Policy Targeting in treatment allocation problems. It estimates policy
rules that trade off group-specific welfare, Pareto efficiency, and
fairness criteria.
The package follows the framework of:
Viviano, D., & Bradic, J. (2022). Fair Policy Targeting. arXiv:2005.12395v3.
The package is intended for causal policy-learning applications with an observed outcome, a binary treatment, a binary sensitive attribute, and a set of policy covariates. It works by estimating nuisance functions, constructing doubly robust welfare scores, approximating a Pareto frontier, and solving a constrained optimization problem with Gurobi.
The main user-facing function is fit_fair_policy(). The
same workflow can also be run step by step with
estimate_nuisance(), compute_dr_scores(), and
estimate_pareto_frontier().
Policy optimization requires a working installation of Gurobi and the
Gurobi R package. For this reason, the computational chunks in this
vignette are marked with eval = FALSE.
We first construct a small synthetic data set. The variable
Y is the observed outcome, D is the binary
treatment, S is the binary sensitive attribute, and
X1, X2 are policy covariates.
n <- 120
df <- data.frame(
S = rbinom(n, 1, 0.45),
X1 = rnorm(n),
X2 = rnorm(n)
)
df$D <- rbinom(
n,
1,
plogis(-0.2 + 0.5 * df$X1 - 0.3 * df$S)
)
tau <- 0.4 + 0.6 * df$X1 - 0.5 * df$S
df$Y <- 1 + 0.5 * df$X1 + 0.2 * df$X2 + tau * df$D + rnorm(n)We now fit a fair policy. In this first example, the policy class is
"maxscore", so the estimated policy is a deterministic
threshold rule. The fairness criterion is demographic parity, selected
with distance = "parity". The capacity constraint allows
treatment of at most 50 percent of the sample.
fit <- fit_fair_policy(
data = df,
outcome = "Y",
treatment = "D",
sensitive = "S",
covariates = c("X1", "X2"),
policy_class = "maxscore",
distance = "parity",
two_directions = TRUE,
capacity = 0.50,
discretization = 10,
lambda = 0,
solver = "gurobi",
nuisance_method = "glm",
n_folds = 5,
seed = 123,
max_time = 300
)After the optimization has been solved, standard methods can be used to inspect the result.
The fitted object reports the policy class, optimization backend, fairness criterion, alpha grid, selected alpha value, and the selected unfairness objective. The summary additionally reports group-specific welfare summaries.
Predicted treatment decisions can be obtained with
predict().
For deterministic maximum-score policies,
type = "response" returns treatment indicators and
type = "score" returns the underlying linear policy
score.
The main function fit_fair_policy() is a convenience
wrapper around three conceptual stages: nuisance estimation, score
construction, and Pareto-frontier approximation followed by fair policy
optimization. This section illustrates the first three stages
explicitly.
The nuisance functions include treatment propensity scores and
conditional outcome functions. The default nuisance method is
"glmnet", which uses penalized regression. The
"glm" method is useful for smaller examples and
debugging.
nuisance <- estimate_nuisance(
data = df,
outcome = "Y",
treatment = "D",
sensitive = "S",
covariates = c("X1", "X2"),
method = "glm",
cross_fit = TRUE,
n_folds = 5,
seed = 123
)
nuisanceThe returned object has class fairtargeting_nuisance and
stores the nuisance estimates needed for doubly robust scoring.
Given the nuisance estimates, compute_dr_scores()
constructs the group-specific welfare components used by the
optimizer.
scores <- compute_dr_scores(
outcome = df$Y,
treatment = df$D,
sensitive = df$S,
nuisance = nuisance
)
str(scores)The score object contains treatment-effect scores for the sensitive group and the comparison group, baseline welfare components, propensity scores, and conditional outcome estimates.
The Pareto frontier is approximated over a grid of alpha values. Each alpha value defines a welfare maximization problem with a different relative weight on the two group-specific welfare objectives.
frontier <- estimate_pareto_frontier(
data = df,
scores = scores,
sensitive = "S",
covariates = c("X1", "X2"),
policy_class = "maxscore",
capacity = 0.50,
alpha_grid = seq(0, 1, length.out = 10),
solver = "gurobi",
max_time = 300
)
frontier
head(frontier$candidates)
head(frontier$efficient_candidates)The second-stage problem in fit_fair_policy() searches
for a fair policy among policies satisfying approximate Pareto-frontier
constraints.
The argument policy_class chooses the class of treatment
rules:
policy_class = "maxscore"
policy_class = "probabilistic"
policy_class = "threshold_probabilistic"
policy_class = "tree"The maximum-score class estimates deterministic linear threshold rules. The probabilistic class estimates continuous treatment probabilities. The threshold-probabilistic class combines a threshold rule with treatment probabilities on either side of the threshold. The tree class searches over simple tree-based rules.
The argument distance selects the unfairness
criterion:
For welfare, relative-welfare, and parity criteria,
two_directions = TRUE computes absolute two-sided
disparities. Setting two_directions = FALSE uses signed
disparities. The argument fairness can also be used as an
alternative name for distance.
The capacity argument limits the number of treated observations. Values between 0 and 1 are interpreted as treatment fractions; values greater than 1 are interpreted as treatment counts.
If capacity = NULL, the package does not impose a
binding capacity limit beyond the sample size.
The alpha grid controls the discretized Pareto-frontier approximation.
In fit_fair_policy(), the grid can be passed through
alpha_seq, or constructed automatically with
discretization. The slack parameter lambda
allows approximate Pareto feasibility in the second-stage optimization
problem. Larger values permit more slack relative to the empirical
frontier.
By default, the policy design matrix contains the sensitive attribute
as the first column and then the listed covariates. In some
applications, the analyst may wish to exclude the sensitive attribute
from the policy rule while still using it for group-specific evaluation.
This can be done with no_parity_constraint = TRUE.
The fitted policy can be evaluated under counterfactual values of the sensitive attribute. This is useful for inspecting how the policy changes when the sensitive attribute is set to 0 or 1 while holding other covariates fixed.