Skip to content

Commit

Permalink
update factorial estimands
Browse files Browse the repository at this point in the history
  • Loading branch information
olivierlabayle committed Jan 25, 2024
1 parent a465cbc commit 562491c
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 74 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.13.1"
version = "0.14.0"

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

- generating all ``ATEs``
- Factorial Treatments

It is possible to generate all possible ATEs from a set of treatment values or from a dataset. For that purpose, use the `generateATEs` 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 `factorialATE` function.

## The Interaction Average Treatment Effect

Expand All @@ -143,7 +143,7 @@ IATE_{0 \rightarrow 1, 0 \rightarrow 1}(P) = \mathbb{E}[Y|do(T_1=1, T_2=1)] - \m
- Statistical Estimand (via backdoor adjustment):

```math
IATE_{0 \rightarrow 1, 0 \rightarrow 1}(P) = \mathbb{E}_{\textbf{W}}[\mathbb{E}[Y|T_1=1, T_2=1, \textbf{W}]] - \mathbb{E}[Y|T_1=1, T_2=0, \textbf{W}] \\
IATE_{0 \rightarrow 1, 0 \rightarrow 1}(P) = \mathbb{E}_{\textbf{W}}[\mathbb{E}[Y|T_1=1, T_2=1, \textbf{W}] - \mathbb{E}[Y|T_1=1, T_2=0, \textbf{W}] \\
- \mathbb{E}[Y|T_1=0, T_2=1, \textbf{W}] + \mathbb{E}[Y|T_1=0, T_2=0, \textbf{W}]]
```

Expand Down Expand Up @@ -180,6 +180,10 @@ 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.

## Composed Estimands

As a result of Julia's automatic differentiation facilities, given a set of predefined estimands ``(\Psi_1, ..., \Psi_k)``, we can automatically compute an estimator for $f(\Psi_1, ..., \Psi_k)$. This is done via the `ComposedEstimand` type.
Expand Down
2 changes: 1 addition & 1 deletion src/TMLE.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ using SplitApplyCombine
export SCM, StaticSCM, add_equations!, add_equation!, parents, vertices
export CM, ATE, IATE
export AVAILABLE_ESTIMANDS
export generateATEs, generateIATEs
export factorialATE, factorialIATE
export TMLEE, OSE, NAIVE
export ComposedEstimand
export var, estimate, OneSampleTTest, OneSampleZTest, OneSampleHotellingT2Test,pvalue, confint, emptyIC
Expand Down
59 changes: 28 additions & 31 deletions src/counterfactual_mean_based/estimands.jl
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,11 @@ unique_non_missing(dataset, colname) = unique(skipmissing(Tables.getcolumn(datas

unique_treatment_values(dataset, colnames) =(;(colname => unique_non_missing(dataset, colname) for colname in colnames)...)

get_treatments_contrasts(treatments_unique_values) = [collect(Combinatorics.combinations(treatments_unique_values[T], 2)) for T in keys(treatments_unique_values)]

function generateComposedEstimandFromContrasts(
get_transitive_treatments_contrasts(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;
Expand All @@ -242,7 +244,7 @@ function generateComposedEstimandFromContrasts(
freq_table=nothing,
positivity_constraint=nothing
) where names
treatments_contrasts = get_treatments_contrasts(treatments_levels)
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]
Expand All @@ -262,18 +264,14 @@ end
GENERATE_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 contrasts are defined by (0 → 1, 1 → 2, 0 → 2), there are thus
3 x 3 = 9 joint contrasts to be generated:
For each treatment variable, the marginal transitive contrasts are defined by (0 → 1, 1 → 2). Note that (0 → 2) or (1 → 0) need not
be considered because they are linearly dependent on the other contrasts. Then, the cartesian product of treatment contrasts is taken,
resulting in a 2 x 2 = 4 dimensional joint estimand:
- (T₁: 0 → 1, T₂: 0 → 1)
- (T₁: 0 → 1, T₂: 1 → 2)
- (T₁: 0 → 1, T₂: 0 → 2)
- (T₁: 1 → 2, T₂: 0 → 1)
- (T₁: 1 → 2, T₂: 1 → 2)
- (T₁: 1 → 2, T₂: 0 → 2)
- (T₁: 0 → 2, T₂: 0 → 1)
- (T₁: 0 → 2, T₂: 1 → 2)
- (T₁: 0 → 2, T₂: 0 → 2)
# Return
Expand All @@ -287,11 +285,10 @@ 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`
"""

"""
generateATEs(
factorialATE(
treatments_levels::NamedTuple{names}, outcome;
confounders=nothing,
outcome_extra_covariates=(),
Expand All @@ -306,23 +303,23 @@ Generate a `ComposedEstimand` of ATEs from the `treatments_levels`. $GENERATE_DO
To generate a causal composed estimand with 3 components:
```@example
generateATEs((T₁ = (0, 1), T₂=(0, 1, 2)), :Y₁)
factorialATE((T₁ = (0, 1), T₂=(0, 1, 2)), :Y₁)
```
To generate a statistical composed estimand with 9 components:
```@example
generateATEs((T₁ = (0, 1, 2), T₂=(0, 1, 2)), :Y₁, confounders=[:W₁, :W₂])
factorialATE((T₁ = (0, 1, 2), T₂=(0, 1, 2)), :Y₁, confounders=[:W₁, :W₂])
```
"""
function generateATEs(
function factorialATE(
treatments_levels::NamedTuple{names}, outcome;
confounders=nothing,
outcome_extra_covariates=(),
freq_table=nothing,
positivity_constraint=nothing
) where names
return generateComposedEstimandFromContrasts(
return generateFactorialEstimandFromContrasts(
ATE,
treatments_levels,
outcome;
Expand All @@ -334,22 +331,22 @@ function generateATEs(
end

"""
generateATEs(dataset, treatments, outcome;
factorialATE(dataset, treatments, outcome;
confounders=nothing,
outcome_extra_covariates=(),
positivity_constraint=nothing
)
Find all unique values for each treatment variable in the dataset and generate all possible ATEs from these values.
"""
function generateATEs(dataset, treatments, outcome;
function factorialATE(dataset, treatments, outcome;
confounders=nothing,
outcome_extra_covariates=(),
positivity_constraint=nothing
)
treatments_levels = unique_treatment_values(dataset, treatments)
freq_table = positivity_constraint !== nothing ? frequency_table(dataset, keys(treatments_levels)) : nothing
return generateATEs(
return factorialATE(
treatments_levels,
outcome;
confounders=confounders,
Expand All @@ -360,38 +357,38 @@ function generateATEs(dataset, treatments, outcome;
end

"""
generateIATEs(
factorialIATE(
treatments_levels::NamedTuple{names}, outcome;
confounders=nothing,
outcome_extra_covariates=(),
freq_table=nothing,
positivity_constraint=nothing
) where names
Generates a `ComposedEstimand` of Average Interation Effects from `treatments_levels`. $GENERATE_DOCSTRING
Generates a `ComposedEstimand` of IATE from `treatments_levels`. $GENERATE_DOCSTRING
# Example:
To generate a causal composed estimand with 3 components:
```@example
generateIATEs((T₁ = (0, 1), T₂=(0, 1, 2)), :Y₁)
factorialIATE((T₁ = (0, 1), T₂=(0, 1, 2)), :Y₁)
```
To generate a statistical composed estimand with 9 components:
```@example
generateIATEs((T₁ = (0, 1, 2), T₂=(0, 1, 2)), :Y₁, confounders=[:W₁, :W₂])
factorialIATE((T₁ = (0, 1, 2), T₂=(0, 1, 2)), :Y₁, confounders=[:W₁, :W₂])
```
"""
function generateIATEs(
function factorialIATE(
treatments_levels::NamedTuple{names}, outcome;
confounders=nothing,
outcome_extra_covariates=(),
freq_table=nothing,
positivity_constraint=nothing
) where names
return generateComposedEstimandFromContrasts(
return generateFactorialEstimandFromContrasts(
IATE,
treatments_levels,
outcome;
Expand All @@ -403,23 +400,23 @@ function generateIATEs(
end

"""
generateIATEs(dataset, treatments, outcome;
factorialIATE(dataset, treatments, outcome;
confounders=nothing,
outcome_extra_covariates=(),
positivity_constraint=nothing
)
Finds treatments levels from the dataset and generates a `ComposedEstimand` of Average Interation Effects from them
(see [`generateIATEs(treatments_levels, outcome; confounders=nothing, outcome_extra_covariates=())`](@ref)).
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 generateIATEs(dataset, treatments, outcome;
function factorialIATE(dataset, treatments, outcome;
confounders=nothing,
outcome_extra_covariates=(),
positivity_constraint=nothing
)
treatments_levels = unique_treatment_values(dataset, treatments)
freq_table = positivity_constraint !== nothing ? frequency_table(dataset, keys(treatments_levels)) : nothing
return generateIATEs(
return factorialIATE(
treatments_levels,
outcome;
confounders=confounders,
Expand All @@ -434,4 +431,4 @@ joint_levels(Ψ::StatisticalIATE) = Iterators.product(values(Ψ.treatment_values
joint_levels::StatisticalATE) =
(Tuple.treatment_values[T][c] for T keys.treatment_values)) for c in (:case, :control))

joint_levels::StatisticalCM) = (values.treatment_values),)
joint_levels::StatisticalCM) = (values.treatment_values),)
8 changes: 8 additions & 0 deletions src/estimands.jl
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,11 @@ nuisance_functions_iterator(Ψ::ComposedEstimand) =

identify(method::AdjustmentMethod, Ψ::ComposedEstimand, scm) =
ComposedEstimand.f, Tuple(identify(method, arg, scm) for arg Ψ.args))

function string_repr(estimand::ComposedEstimand)
string(
"Composed Estimand applying function `", estimand.f, "` to: \n",
"-----------------\n- ",
join((string_repr(arg) for arg in estimand.args), "\n- ")
)
end
Loading

0 comments on commit 562491c

Please sign in to comment.