Skip to content

Commit

Permalink
Merge pull request #101 from TARGENE/linearly_indep_estimands
Browse files Browse the repository at this point in the history
Linearly indep estimands
  • Loading branch information
olivierlabayle authored Jan 25, 2024
2 parents a465cbc + d80bba4 commit c1e044c
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 75 deletions.
4 changes: 2 additions & 2 deletions 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 Expand Up @@ -55,7 +55,7 @@ PrettyTables = "2.2"
TableOperations = "1.2"
Tables = "1.6"
YAML = "0.4.9"
Zygote = "0.6"
Zygote = "0.6.69"
SplitApplyCombine = "1.2.2"
julia = "1.6, 1.7, 1"

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

2 comments on commit c1e044c

@olivierlabayle
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register

Release notes:

  • Limit the generation of factorial ATEs and IATEs to linearly independent ones
  • Rename the generating methods

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/99548

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.14.0 -m "<description of version>" c1e044c337df3b3757b5f1c44b168c0063ef5a03
git push origin v0.14.0

Please sign in to comment.