Skip to content

Commit

Permalink
generalize methods to factorialEstimand [BREAKING]
Browse files Browse the repository at this point in the history
  • Loading branch information
olivierlabayle committed Mar 14, 2024
1 parent 69281c6 commit b0e3158
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 134 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "TMLE"
uuid = "8afdd2fb-6e73-43df-8b62-b1650cd9c8cf"
authors = ["Olivier Labayle"]
version = "0.14.2"
version = "0.15.0"

[deps]
AbstractDifferentiation = "c29ec348-61ec-40c8-8164-b8c60e9d9f3d"
Expand Down
4 changes: 2 additions & 2 deletions docs/src/user_guide/estimands.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ statisticalΨ = ATE(

- Factorial Treatments

It is possible to generate a `ComposedEstimand` containing all linearly independent IATEs from a set of treatment values or from a dataset. For that purpose, use the `factorialATE` function.
It is possible to generate a `ComposedEstimand` containing all linearly independent IATEs from a set of treatment values or from a dataset. For that purpose, use the `factorialEstimand` function.

## The Interaction Average Treatment Effect

Expand Down Expand Up @@ -182,7 +182,7 @@ statisticalΨ = IATE(

- Factorial Treatments

It is possible to generate a `ComposedEstimand` containing all linearly independent IATEs from a set of treatment values or from a dataset. For that purpose, use the `factorialIATE` function.
It is possible to generate a `ComposedEstimand` containing all linearly independent IATEs from a set of treatment values or from a dataset. For that purpose, use the `factorialEstimand` function.

## Composed Estimands

Expand Down
2 changes: 1 addition & 1 deletion src/TMLE.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ using SplitApplyCombine
export SCM, StaticSCM, add_equations!, add_equation!, parents, vertices
export CM, ATE, IATE
export AVAILABLE_ESTIMANDS
export factorialATE, factorialIATE
export factorialEstimand, factorialEstimands
export TMLEE, OSE, NAIVE
export ComposedEstimand
export var, estimate, pvalue, confint, emptyIC
Expand Down
221 changes: 106 additions & 115 deletions src/counterfactual_mean_based/estimands.jl
Original file line number Diff line number Diff line change
Expand Up @@ -242,36 +242,20 @@ the number of estimands passing the positivity constraint.
unique_treatment_values(dataset, colnames) =
(;(colname => get_treatment_values(dataset, colname) for colname in colnames)...)

get_transitive_treatments_contrasts(treatments_unique_values) =
"""
Generated from transitive treatment switches to create independent estimands.
"""
get_treatment_settings(::Union{typeof(ATE), typeof(IATE)}, treatments_unique_values) =
[collect(zip(vals[1:end-1], vals[2:end])) for vals in values(treatments_unique_values)]

function generateFactorialEstimandFromContrasts(
constructor,
treatments_levels::NamedTuple{names},
outcome;
confounders=nothing,
outcome_extra_covariates=(),
freq_table=nothing,
positivity_constraint=nothing
) where names
treatments_contrasts = get_transitive_treatments_contrasts(treatments_levels)
components = []
for combo Iterators.product(treatments_contrasts...)
treatments_contrast = [NamedTuple{(:control, :case)}(treatment_control_case) for treatment_control_case combo]
Ψ = constructor(
outcome=outcome,
treatment_values=NamedTuple{names}(treatments_contrast),
treatment_confounders = confounders,
outcome_extra_covariates=outcome_extra_covariates
)
if satisfies_positivity(Ψ, freq_table; positivity_constraint=positivity_constraint)
push!(components, Ψ)
end
end
return ComposedEstimand(joint_estimand, Tuple(components))
end
get_treatment_settings(::typeof(CM), treatments_unique_values) =
values(treatments_unique_values)

get_treatment_setting(combo::Tuple{Vararg{Tuple}}) = [NamedTuple{(:control, :case)}(treatment_control_case) for treatment_control_case combo]

get_treatment_setting(combo) = collect(combo)

GENERATE_DOCSTRING = """
FACTORIAL_DOCSTRING = """
The components of this estimand are generated from the treatment variables contrasts.
For example, consider two treatment variables T₁ and T₂ each taking three possible values (0, 1, 2).
For each treatment variable, the marginal transitive contrasts are defined by (0 → 1, 1 → 2). Note that (0 → 2) or (1 → 0) need not
Expand All @@ -295,145 +279,152 @@ A `ComposedEstimand` with causal or statistical components.
If `nothing`, causal estimands are generated.
- `outcome_extra_covariates=()`: The generated components will inherit these `outcome_extra_covariates`.
- `positivity_constraint=nothing`: Only components that pass the positivity constraint are added to the `ComposedEstimand`
- `verbosity=1`: Verbosity level.
"""

"""
factorialATE(
treatments_levels::NamedTuple{names}, outcome;
factorialEstimand(
constructor::Union{typeof(ATE), typeof(IATE)},
treatments_levels::NamedTuple{names},
outcome;
confounders=nothing,
outcome_extra_covariates=(),
freq_table=nothing,
positivity_constraint=nothing
) where names
positivity_constraint=nothing,
verbosity=1
) where names
Generate a `ComposedEstimand` from `treatments_levels`. $FACTORIAL_DOCSTRING
Generate a `ComposedEstimand` of ATEs from the `treatments_levels`. $GENERATE_DOCSTRING
# Examples:
# Example:
Average Treatment Effects:
To generate a causal composed estimand with 3 components:
```@example
factorialEstimand(ATE, (T₁ = (0, 1), T₂=(0, 1, 2)), :Y₁)
```
```@example
factorialATE((T₁ = (0, 1), T₂=(0, 1, 2)), :Y₁)
factorial(ATE, (T₁ = (0, 1, 2), T₂=(0, 1, 2)), :Y₁, confounders=[:W₁, :W₂])
```
To generate a statistical composed estimand with 9 components:
Interactions:
```@example
factorialATE((T₁ = (0, 1, 2), T₂=(0, 1, 2)), :Y₁, confounders=[:W₁, :W₂])
factorialEstimand(IATE, (T₁ = (0, 1), T₂=(0, 1, 2)), :Y₁)
```
```@example
factorialEstimand(IATE, (T₁ = (0, 1, 2), T₂=(0, 1, 2)), :Y₁, confounders=[:W₁, :W₂])
"""
function factorialATE(
treatments_levels::NamedTuple{names}, outcome;
function factorialEstimand(
constructor::Union{typeof(CM), typeof(ATE), typeof(IATE)},
treatments_levels::NamedTuple{names},
outcome;
confounders=nothing,
outcome_extra_covariates=(),
freq_table=nothing,
positivity_constraint=nothing
positivity_constraint=nothing,
verbosity=1
) where names
return generateFactorialEstimandFromContrasts(
ATE,
treatments_levels,
outcome;
confounders=confounders,
outcome_extra_covariates=outcome_extra_covariates,
freq_table=freq_table,
positivity_constraint=positivity_constraint
)
treatments_settings = get_treatment_settings(constructor, treatments_levels)
components = []
for combo Iterators.product(treatments_settings...)
Ψ = constructor(
outcome=outcome,
treatment_values=NamedTuple{names}(get_treatment_setting(combo)),
treatment_confounders = confounders,
outcome_extra_covariates=outcome_extra_covariates
)
if satisfies_positivity(Ψ, freq_table; positivity_constraint=positivity_constraint)
push!(components, Ψ)
else
verbosity > 0 && @warn("Sub estimand", Ψ, " did not pass the positivity constraint, skipped.")
end
end
return ComposedEstimand(joint_estimand, Tuple(components))
end

"""
factorialATE(dataset, treatments, outcome;
confounders=nothing,
outcome_extra_covariates=(),
positivity_constraint=nothing
factorialEstimand(
constructor::Union{typeof(ATE), typeof(IATE)},
dataset, treatments, outcome;
confounders=nothing,
outcome_extra_covariates=(),
positivity_constraint=nothing,
verbosity=1
)
Find all unique values for each treatment variable in the dataset and generate all possible ATEs from these values.
Identifies `treatment_levels` from `dataset` and construct the
factorialEstimand from it.
"""
function factorialATE(dataset, treatments, outcome;
function factorialEstimand(
constructor::Union{typeof(CM), typeof(ATE), typeof(IATE)},
dataset, treatments, outcome;
confounders=nothing,
outcome_extra_covariates=(),
positivity_constraint=nothing
positivity_constraint=nothing,
verbosity=1
)
treatments_levels = unique_treatment_values(dataset, treatments)
freq_table = positivity_constraint !== nothing ? frequency_table(dataset, keys(treatments_levels)) : nothing
return factorialATE(
return factorialEstimand(
constructor,
treatments_levels,
outcome;
confounders=confounders,
outcome_extra_covariates=outcome_extra_covariates,
freq_table=freq_table,
positivity_constraint=positivity_constraint
positivity_constraint=positivity_constraint,
verbosity=verbosity
)
end

"""
factorialIATE(
treatments_levels::NamedTuple{names}, outcome;
confounders=nothing,
outcome_extra_covariates=(),
freq_table=nothing,
positivity_constraint=nothing
) where names
Generates a `ComposedEstimand` of IATE from `treatments_levels`. $GENERATE_DOCSTRING
# Example:
To generate a causal composed estimand with 3 components:
```@example
factorialIATE((T₁ = (0, 1), T₂=(0, 1, 2)), :Y₁)
```
To generate a statistical composed estimand with 9 components:
```@example
factorialIATE((T₁ = (0, 1, 2), T₂=(0, 1, 2)), :Y₁, confounders=[:W₁, :W₂])
```
"""
function factorialIATE(
treatments_levels::NamedTuple{names}, outcome;
factorialEstimands(
constructor::Union{typeof(ATE), typeof(IATE)},
dataset, treatments, outcomes;
confounders=nothing,
outcome_extra_covariates=(),
freq_table=nothing,
positivity_constraint=nothing
) where names
return generateFactorialEstimandFromContrasts(
IATE,
treatments_levels,
outcome;
confounders=confounders,
outcome_extra_covariates=outcome_extra_covariates,
freq_table=freq_table,
positivity_constraint=positivity_constraint
positivity_constraint=nothing,
verbosity=1
)
end
Identifies `treatment_levels` from `dataset` and a factorialEstimand
for each outcome in `outcomes`.
"""
factorialIATE(dataset, treatments, outcome;
confounders=nothing,
outcome_extra_covariates=(),
positivity_constraint=nothing
)
Finds treatments levels from the dataset and generates a `ComposedEstimand` of IATE from them
(see [`factorialIATE(treatments_levels, outcome; confounders=nothing, outcome_extra_covariates=())`](@ref)).
"""
function factorialIATE(dataset, treatments, outcome;
function factorialEstimands(
constructor::Union{typeof(CM), typeof(ATE), typeof(IATE)},
dataset, treatments, outcomes;
confounders=nothing,
outcome_extra_covariates=(),
positivity_constraint=nothing
positivity_constraint=nothing,
verbosity=1
)
estimands = []
treatments_levels = unique_treatment_values(dataset, treatments)
freq_table = positivity_constraint !== nothing ? frequency_table(dataset, keys(treatments_levels)) : nothing
return factorialIATE(
treatments_levels,
outcome;
confounders=confounders,
outcome_extra_covariates=outcome_extra_covariates,
freq_table=freq_table,
positivity_constraint=positivity_constraint
)
for outcome in outcomes
Ψ = factorialEstimand(
constructor,
treatments_levels,
outcome;
confounders=confounders,
outcome_extra_covariates=outcome_extra_covariates,
freq_table=freq_table,
positivity_constraint=positivity_constraint,
verbosity=verbosity-1
)
if length.args) > 0
push!(estimands, Ψ)
else
verbosity > 0 && @warn(string(
"ATE for outcome, ", outcome,
" has no component passing the positivity constraint, skipped."
))
end
end
return estimands
end

joint_levels::StatisticalIATE) = Iterators.product(values.treatment_values)...)
Expand Down
Loading

0 comments on commit b0e3158

Please sign in to comment.