Skip to content

Commit

Permalink
Address PR comments
Browse files Browse the repository at this point in the history
Signed-off-by: Andres Morales <andresmor@microsoft.com>
  • Loading branch information
andresmor-ms committed Sep 20, 2022
1 parent 6d1a464 commit 5494d1d
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 96 deletions.
Expand Up @@ -62,7 +62,7 @@
"outputs": [],
"source": [
"from dowhy.causal_graph import CausalGraph\n",
"from dowhy.causal_identifier import BackdoorIdentifier, BackdoorAdjustmentMethod, CausalIdentifierEstimandType"
"from dowhy.causal_identifier import DefaultIdentifier, BackdoorAdjustment, CausalIdentifierEstimandType"
]
},
{
Expand Down Expand Up @@ -190,9 +190,9 @@
}
],
"source": [
"ident_eff = BackdoorIdentifier(\n",
"ident_eff = DefaultIdentifier(\n",
" estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE,\n",
" backdoor_adjustment=BackdoorAdjustmentMethod.BACKDOOR_EFFICIENT,\n",
" backdoor_adjustment=BackdoorAdjustment.BACKDOOR_EFFICIENT,\n",
")\n",
"print(\n",
" ident_eff.identify_effect(\n",
Expand Down Expand Up @@ -221,9 +221,9 @@
"metadata": {},
"outputs": [],
"source": [
"ident_minimal_eff = BackdoorIdentifier(\n",
"ident_minimal_eff = DefaultIdentifier(\n",
" estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE,\n",
" backdoor_adjustment=BackdoorAdjustmentMethod.BACKDOOR_MIN_EFFICIENT,\n",
" backdoor_adjustment=BackdoorAdjustment.BACKDOOR_MIN_EFFICIENT,\n",
")\n",
"print(\n",
" ident_minimal_eff.identify_effect(\n",
Expand All @@ -245,9 +245,9 @@
"metadata": {},
"outputs": [],
"source": [
"ident_mincost_eff = BackdoorIdentifier(\n",
"ident_mincost_eff = DefaultIdentifier(\n",
" estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE,\n",
" backdoor_adjustment=BackdoorAdjustmentMethod.BACKDOOR_MINCOST_EFFICIENT,\n",
" backdoor_adjustment=BackdoorAdjustment.BACKDOOR_MINCOST_EFFICIENT,\n",
")\n",
"print(\n",
" ident_mincost_eff.identify_effect(\n",
Expand Down Expand Up @@ -324,9 +324,9 @@
"metadata": {},
"outputs": [],
"source": [
"ident_eff = BackdoorIdentifier(\n",
"ident_eff = DefaultIdentifier(\n",
" estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE,\n",
" backdoor_adjustment=BackdoorAdjustmentMethod.BACKDOOR_EFFICIENT,\n",
" backdoor_adjustment=BackdoorAdjustment.BACKDOOR_EFFICIENT,\n",
")\n",
"try:\n",
" results_eff = ident_eff.identify_effect(graph=G, treatment_name=treatment_name, outcome_name=outcome_name)\n",
Expand All @@ -340,9 +340,9 @@
"metadata": {},
"outputs": [],
"source": [
"ident_eff = BackdoorIdentifier(\n",
"ident_eff = DefaultIdentifier(\n",
" estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE,\n",
" backdoor_adjustment=BackdoorAdjustmentMethod.BACKDOOR_MIN_EFFICIENT,\n",
" backdoor_adjustment=BackdoorAdjustment.BACKDOOR_MIN_EFFICIENT,\n",
")\n",
"print(\n",
" ident_minimal_eff.identify_effect(\n",
Expand All @@ -359,9 +359,9 @@
"metadata": {},
"outputs": [],
"source": [
"ident_eff = BackdoorIdentifier(\n",
"ident_eff = DefaultIdentifier(\n",
" estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE,\n",
" backdoor_adjustment=BackdoorAdjustmentMethod.BACKDOOR_MINCOST_EFFICIENT,\n",
" backdoor_adjustment=BackdoorAdjustment.BACKDOOR_MINCOST_EFFICIENT,\n",
")\n",
"print(\n",
" ident_mincost_eff.identify_effect(\n",
Expand Down Expand Up @@ -414,9 +414,9 @@
"metadata": {},
"outputs": [],
"source": [
"ident_eff = BackdoorIdentifier(\n",
"ident_eff = DefaultIdentifier(\n",
" estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE,\n",
" backdoor_adjustment=BackdoorAdjustmentMethod.BACKDOOR_EFFICIENT,\n",
" backdoor_adjustment=BackdoorAdjustment.BACKDOOR_EFFICIENT,\n",
")\n",
"try:\n",
" results_eff = ident_eff.identify_effect(\n",
Expand Down Expand Up @@ -509,9 +509,9 @@
},
"outputs": [],
"source": [
"ident_eff = BackdoorIdentifier(\n",
"ident_eff = DefaultIdentifier(\n",
" estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE,\n",
" backdoor_adjustment=BackdoorAdjustmentMethod.BACKDOOR_MINCOST_EFFICIENT,\n",
" backdoor_adjustment=BackdoorAdjustment.BACKDOOR_MINCOST_EFFICIENT,\n",
" costs=costs,\n",
")\n",
"print(\n",
Expand All @@ -534,9 +534,9 @@
"metadata": {},
"outputs": [],
"source": [
"ident_eff = BackdoorIdentifier(\n",
"ident_eff = DefaultIdentifier(\n",
" estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE,\n",
" backdoor_adjustment=BackdoorAdjustmentMethod.BACKDOOR_MIN_EFFICIENT,\n",
" backdoor_adjustment=BackdoorAdjustment.BACKDOOR_MIN_EFFICIENT,\n",
")\n",
"print(\n",
" ident_minimal_eff.identify_effect(\n",
Expand Down
10 changes: 5 additions & 5 deletions docs/source/example_notebooks/functional_api.ipynb
Expand Up @@ -9,8 +9,8 @@
"from dowhy import CausalModel\n",
"from dowhy.causal_identifier import (\n",
" identify_effect,\n",
" BackdoorIdentifier,\n",
" BackdoorAdjustmentMethod,\n",
" DefaultIdentifier,\n",
" BackdoorAdjustment,\n",
" IDIdentifier,\n",
" CausalIdentifierEstimandType,\n",
")"
Expand Down Expand Up @@ -68,7 +68,7 @@
" graph=graph,\n",
" treatment=treatment_name,\n",
" outcome=outcome_name,\n",
" method=BackdoorIdentifier(estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE),\n",
" method=DefaultIdentifier(estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE),\n",
")\n",
"print(identified_estimand)"
]
Expand Down Expand Up @@ -100,8 +100,8 @@
"source": [
"# Another way of executing the identify effect by directly calling the object\n",
"\n",
"identifier = BackdoorIdentifier(\n",
" estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE, backdoor_adjustment=BackdoorAdjustmentMethod.BACKDOOR_DEFAULT\n",
"identifier = DefaultIdentifier(\n",
" estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE, backdoor_adjustment=BackdoorAdjustment.BACKDOOR_DEFAULT\n",
")\n",
"\n",
"identified_estimand = identifier.identify_effect(\n",
Expand Down
6 changes: 3 additions & 3 deletions dowhy/causal_identifier/__init__.py
@@ -1,10 +1,10 @@
from dowhy.causal_identifier.backdoor_identifier import BackdoorAdjustmentMethod, BackdoorIdentifier
from dowhy.causal_identifier.default_identifier import BackdoorAdjustment, DefaultIdentifier
from dowhy.causal_identifier.id_identifier import IDIdentifier
from dowhy.causal_identifier.identify_effect import CausalIdentifierEstimandType, IdentifiedEstimand, identify_effect

__all__ = [
"BackdoorIdentifier",
"BackdoorAdjustmentMethod",
"DefaultIdentifier",
"BackdoorAdjustment",
"CausalIdentifierEstimandType",
"IdentifiedEstimand",
"IDIdentifier",
Expand Down
Expand Up @@ -12,7 +12,7 @@
from dowhy.utils.api import parse_state


class BackdoorAdjustmentMethod(Enum):
class BackdoorAdjustment(Enum):

# Backdoor method names
BACKDOOR_DEFAULT = "default"
Expand All @@ -24,7 +24,7 @@ class BackdoorAdjustmentMethod(Enum):
BACKDOOR_MINCOST_EFFICIENT = "efficient-mincost-adjustment"


class BackdoorIdentifier:
class DefaultIdentifier:
"""Class that implements different identification methods.
Currently supports backdoor and instrumental variable identification methods. The identification is based on the causal graph provided.
Expand All @@ -34,25 +34,25 @@ class BackdoorIdentifier:
MAX_BACKDOOR_ITERATIONS = 100000

METHOD_NAMES = {
BackdoorAdjustmentMethod.BACKDOOR_DEFAULT,
BackdoorAdjustmentMethod.BACKDOOR_EXHAUSTIVE,
BackdoorAdjustmentMethod.BACKDOOR_MIN,
BackdoorAdjustmentMethod.BACKDOOR_MAX,
BackdoorAdjustmentMethod.BACKDOOR_EFFICIENT,
BackdoorAdjustmentMethod.BACKDOOR_MIN_EFFICIENT,
BackdoorAdjustmentMethod.BACKDOOR_MINCOST_EFFICIENT,
BackdoorAdjustment.BACKDOOR_DEFAULT,
BackdoorAdjustment.BACKDOOR_EXHAUSTIVE,
BackdoorAdjustment.BACKDOOR_MIN,
BackdoorAdjustment.BACKDOOR_MAX,
BackdoorAdjustment.BACKDOOR_EFFICIENT,
BackdoorAdjustment.BACKDOOR_MIN_EFFICIENT,
BackdoorAdjustment.BACKDOOR_MINCOST_EFFICIENT,
}
EFFICIENT_METHODS = {
BackdoorAdjustmentMethod.BACKDOOR_EFFICIENT,
BackdoorAdjustmentMethod.BACKDOOR_MIN_EFFICIENT,
BackdoorAdjustmentMethod.BACKDOOR_MINCOST_EFFICIENT,
BackdoorAdjustment.BACKDOOR_EFFICIENT,
BackdoorAdjustment.BACKDOOR_MIN_EFFICIENT,
BackdoorAdjustment.BACKDOOR_MINCOST_EFFICIENT,
}
DEFAULT_BACKDOOR_METHOD = BackdoorAdjustmentMethod.BACKDOOR_DEFAULT
DEFAULT_BACKDOOR_METHOD = BackdoorAdjustment.BACKDOOR_DEFAULT

def __init__(
self,
estimand_type: CausalIdentifierEstimandType,
backdoor_adjustment: BackdoorAdjustmentMethod = BackdoorAdjustmentMethod.BACKDOOR_DEFAULT,
backdoor_adjustment: BackdoorAdjustment = BackdoorAdjustment.BACKDOOR_DEFAULT,
proceed_when_unidentifiable: bool = False,
optimize_backdoor: bool = False,
costs: Optional[List] = None,
Expand Down Expand Up @@ -144,7 +144,7 @@ def identify_ate_effect(
mediation_second_stage_confounders = None
### 1. BACKDOOR IDENTIFICATION
# Pick algorithm to compute backdoor sets according to method chosen
if self.backdoor_adjustment not in BackdoorIdentifier.EFFICIENT_METHODS:
if self.backdoor_adjustment not in DefaultIdentifier.EFFICIENT_METHODS:
# First, checking if there are any valid backdoor adjustment sets
if self.optimize_backdoor == False:
backdoor_sets = self.identify_backdoor(graph, treatment_name, outcome_name)
Expand All @@ -153,7 +153,7 @@ def identify_ate_effect(

path = Backdoor(graph._graph, treatment_name, outcome_name)
backdoor_sets = path.get_backdoor_vars()
elif self.backdoor_adjustment in BackdoorIdentifier.EFFICIENT_METHODS:
elif self.backdoor_adjustment in DefaultIdentifier.EFFICIENT_METHODS:
backdoor_sets = self.identify_efficient_backdoor(graph, conditional_node_names=conditional_node_names)
estimands_dict, backdoor_variables_dict = self.build_backdoor_estimands_dict(
graph, treatment_name, outcome_name, backdoor_sets, estimands_dict
Expand Down Expand Up @@ -404,8 +404,8 @@ def identify_backdoor(
raise ValueError(f"d-separation algorithm {dseparation_algo} is not supported")
backdoor_adjustment = (
self.backdoor_adjustment
if self.backdoor_adjustment != BackdoorAdjustmentMethod.BACKDOOR_DEFAULT
else BackdoorIdentifier.DEFAULT_BACKDOOR_METHOD
if self.backdoor_adjustment != BackdoorAdjustment.BACKDOOR_DEFAULT
else DefaultIdentifier.DEFAULT_BACKDOOR_METHOD
)

# First, checking if empty set is a valid backdoor set
Expand All @@ -421,7 +421,7 @@ def identify_backdoor(
if check["is_dseparated"]:
backdoor_sets.append({"backdoor_set": empty_set})
# If the method is `minimal-adjustment`, return the empty set right away.
if backdoor_adjustment == BackdoorAdjustmentMethod.BACKDOOR_MIN:
if backdoor_adjustment == BackdoorAdjustment.BACKDOOR_MIN:
return backdoor_sets

# Second, checking for all other sets of variables. If include_unobserved is false, then only observed variables are eligible.
Expand All @@ -443,7 +443,7 @@ def identify_backdoor(
dsep_outcome_var = graph.check_dseparation(outcome_name, parse_state(var), set())
if not dsep_outcome_var or not dsep_treat_var:
filt_eligible_variables.add(var)
if backdoor_adjustment in BackdoorIdentifier.METHOD_NAMES:
if backdoor_adjustment in DefaultIdentifier.METHOD_NAMES:
backdoor_sets, found_valid_adjustment_set = self.find_valid_adjustment_sets(
graph,
treatment_name,
Expand All @@ -454,9 +454,9 @@ def identify_backdoor(
backdoor_sets,
filt_eligible_variables,
backdoor_adjustment=backdoor_adjustment,
max_iterations=BackdoorIdentifier.MAX_BACKDOOR_ITERATIONS,
max_iterations=DefaultIdentifier.MAX_BACKDOOR_ITERATIONS,
)
if backdoor_adjustment == BackdoorAdjustmentMethod.BACKDOOR_DEFAULT and found_valid_adjustment_set:
if backdoor_adjustment == BackdoorAdjustment.BACKDOOR_DEFAULT and found_valid_adjustment_set:
# repeat the above search with BACKDOOR_MIN
backdoor_sets, _ = self.find_valid_adjustment_sets(
graph,
Expand All @@ -467,12 +467,12 @@ def identify_backdoor(
dseparation_algo,
backdoor_sets,
filt_eligible_variables,
backdoor_adjustment=BackdoorAdjustmentMethod.BACKDOOR_MIN,
max_iterations=BackdoorIdentifier.MAX_BACKDOOR_ITERATIONS,
backdoor_adjustment=BackdoorAdjustment.BACKDOOR_MIN,
max_iterations=DefaultIdentifier.MAX_BACKDOOR_ITERATIONS,
)
else:
raise ValueError(
f"Identifier method {backdoor_adjustment} not supported. Try one of the following: {BackdoorIdentifier.METHOD_NAMES}"
f"Identifier method {backdoor_adjustment} not supported. Try one of the following: {DefaultIdentifier.METHOD_NAMES}"
)
return backdoor_sets

Expand Down Expand Up @@ -523,13 +523,13 @@ def identify_efficient_backdoor(self, graph: CausalGraph, conditional_node_names
conditional_node_names=conditional_node_names,
costs=self.costs,
)
if self.backdoor_adjustment == BackdoorAdjustmentMethod.BACKDOOR_EFFICIENT:
if self.backdoor_adjustment == BackdoorAdjustment.BACKDOOR_EFFICIENT:
backdoor_set = efficient_bd.optimal_adj_set()
backdoor_sets = [{"backdoor_set": tuple(backdoor_set)}]
elif self.backdoor_adjustment == BackdoorAdjustmentMethod.BACKDOOR_MIN_EFFICIENT:
elif self.backdoor_adjustment == BackdoorAdjustment.BACKDOOR_MIN_EFFICIENT:
backdoor_set = efficient_bd.optimal_minimal_adj_set()
backdoor_sets = [{"backdoor_set": tuple(backdoor_set)}]
elif self.backdoor_adjustment == BackdoorAdjustmentMethod.BACKDOOR_MINCOST_EFFICIENT:
elif self.backdoor_adjustment == BackdoorAdjustment.BACKDOOR_MINCOST_EFFICIENT:
backdoor_set = efficient_bd.optimal_mincost_adj_set()
backdoor_sets = [{"backdoor_set": tuple(backdoor_set)}]
return backdoor_sets
Expand All @@ -553,7 +553,7 @@ def find_valid_adjustment_sets(
# If `minimal-adjustment` method is specified, start the search from the set with minimum size. Otherwise, start from the largest.
set_sizes = (
range(1, len(filt_eligible_variables) + 1, 1)
if backdoor_adjustment == BackdoorAdjustmentMethod.BACKDOOR_MIN
if backdoor_adjustment == BackdoorAdjustment.BACKDOOR_MIN
else range(len(filt_eligible_variables), 0, -1)
)
for size_candidate_set in set_sizes:
Expand All @@ -573,28 +573,24 @@ def find_valid_adjustment_sets(
backdoor_sets.append({"backdoor_set": candidate_set})
found_valid_adjustment_set = True
num_iterations += 1
if (
backdoor_adjustment == BackdoorAdjustmentMethod.BACKDOOR_EXHAUSTIVE
and num_iterations > max_iterations
):
if backdoor_adjustment == BackdoorAdjustment.BACKDOOR_EXHAUSTIVE and num_iterations > max_iterations:
self.logger.warning(f"Max number of iterations {max_iterations} reached.")
break
# If the backdoor method is `maximal-adjustment` or `minimal-adjustment`, return the first found adjustment set.
if (
backdoor_adjustment
in {
BackdoorAdjustmentMethod.BACKDOOR_DEFAULT,
BackdoorAdjustmentMethod.BACKDOOR_MAX,
BackdoorAdjustmentMethod.BACKDOOR_MIN,
BackdoorAdjustment.BACKDOOR_DEFAULT,
BackdoorAdjustment.BACKDOOR_MAX,
BackdoorAdjustment.BACKDOOR_MIN,
}
and found_valid_adjustment_set
):
break
# If all variables are observed, and the biggest eligible set
# does not satisfy backdoor, then none of its subsets will.
if (
backdoor_adjustment
in {BackdoorAdjustmentMethod.BACKDOOR_DEFAULT, BackdoorAdjustmentMethod.BACKDOOR_MAX}
backdoor_adjustment in {BackdoorAdjustment.BACKDOOR_DEFAULT, BackdoorAdjustment.BACKDOOR_MAX}
and all_nodes_observed
):
break
Expand Down
11 changes: 10 additions & 1 deletion dowhy/causal_identifier/id_identifier.py
@@ -1,3 +1,5 @@
from typing import List, Optional, Union

import networkx as nx

from dowhy.causal_graph import CausalGraph
Expand Down Expand Up @@ -99,7 +101,14 @@ def __init__(self, estimand_type=CausalIdentifierEstimandType.NONPARAMETRIC_ATE)
if estimand_type != CausalIdentifierEstimandType.NONPARAMETRIC_ATE:
raise Exception("The estimand type should be 'non-parametric ate' for the ID method type.")

def identify_effect(self, graph: CausalGraph, treatment_name, outcome_name, node_names=None, **kwargs):
def identify_effect(
self,
graph: CausalGraph,
treatment_name: Union[str, List[str]],
outcome_name: Union[str, List[str]],
node_names: Optional[Union[str, List[str]]] = None,
**kwargs,
):
"""
Implementation of the ID algorithm.
Link - https://ftp.cs.ucla.edu/pub/stat_ser/shpitser-thesis.pdf
Expand Down

0 comments on commit 5494d1d

Please sign in to comment.