Skip to content

Commit 499ff29

Browse files
committed
Removed CausalSpecification
1 parent baa9b0e commit 499ff29

13 files changed

Lines changed: 57 additions & 133 deletions

File tree

causal_testing/main.py

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from causal_testing.estimation.linear_regression_estimator import LinearRegressionEstimator
1616
from causal_testing.estimation.logistic_regression_estimator import LogisticRegressionEstimator
1717
from causal_testing.specification.causal_dag import CausalDAG
18-
from causal_testing.specification.causal_specification import CausalSpecification
1918
from causal_testing.specification.scenario import Scenario
2019
from causal_testing.specification.variable import Input, Output
2120
from causal_testing.testing.base_test_case import BaseTestCase
@@ -106,7 +105,6 @@ def __init__(self, paths: CausalTestingPaths, ignore_cycles: bool = False, query
106105
self.data: Optional[pd.DataFrame] = None
107106
self.variables: Dict[str, Any] = {"inputs": {}, "outputs": {}, "metas": {}}
108107
self.scenario: Optional[Scenario] = None
109-
self.causal_specification: Optional[CausalSpecification] = None
110108
self.test_cases: Optional[List[CausalTestCase]] = None
111109

112110
def setup(self) -> None:
@@ -130,8 +128,11 @@ def setup(self) -> None:
130128
# Create variables from DAG
131129
self.create_variables()
132130

133-
# Create scenario and specification
134-
self.create_scenario_and_specification()
131+
# Create scenario
132+
self.scenario = Scenario(
133+
list(self.variables["inputs"].values()) + list(self.variables["outputs"].values()),
134+
{self.query} if self.query else None,
135+
)
135136

136137
logger.info("Setup completed successfully")
137138

@@ -187,15 +188,6 @@ def create_variables(self) -> None:
187188
if self.dag.in_degree(node_name) > 0:
188189
self.variables["outputs"][node_name] = Output(name=node_name, datatype=dtype)
189190

190-
def create_scenario_and_specification(self) -> None:
191-
"""Create scenario and causal specification objects from loaded data."""
192-
# Create scenario
193-
all_variables = list(self.variables["inputs"].values()) + list(self.variables["outputs"].values())
194-
self.scenario = Scenario(variables=all_variables)
195-
196-
# Create causal specification
197-
self.causal_specification = CausalSpecification(scenario=self.scenario, causal_dag=self.dag)
198-
199191
def load_tests(self) -> None:
200192
"""
201193
Load and prepare test configurations from file.
@@ -315,7 +307,7 @@ def create_causal_test(self, test: dict, base_test: BaseTestCase) -> CausalTestC
315307
control_value=test.get("control_value"),
316308
adjustment_set=test.get(
317309
"adjustment_set",
318-
self.causal_specification.causal_dag.identification(base_test, self.scenario.hidden_variables()),
310+
self.dag.identification(base_test, self.scenario.hidden_variables()),
319311
),
320312
df=filtered_df,
321313
effect_modifiers=None,

causal_testing/specification/causal_dag.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -493,8 +493,7 @@ def identification(self, base_test_case: BaseTestCase, avoid_variables: set[Vari
493493
494494
:param base_test_case: A base test case instance containing the outcome_variable and the
495495
treatment_variable required for identification.
496-
:param scenario: The modelling scenario relating to the tests
497-
496+
:param avoid_variables: Variables not to be adjusted for (e.g. hidden variables).
498497
:return: The smallest set of variables which can be adjusted for to obtain a causal
499498
estimate as opposed to a purely associational estimate.
500499
"""

causal_testing/specification/causal_specification.py

Lines changed: 0 additions & 22 deletions
This file was deleted.

causal_testing/surrogate/causal_surrogate_assisted.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
from abc import ABC, abstractmethod
44
from dataclasses import dataclass
55
from typing import Callable
6+
import logging
67

78
import pandas as pd
89

910
from causal_testing.estimation.cubic_spline_estimator import CubicSplineRegressionEstimator
10-
from causal_testing.specification.causal_specification import CausalSpecification
11+
from causal_testing.specification.scenario import Scenario
12+
from causal_testing.specification.causal_dag import CausalDAG
1113
from causal_testing.testing.base_test_case import BaseTestCase
1214

15+
logger = logging.getLogger(__name__)
16+
1317

1418
@dataclass
1519
class SimulationResult:
@@ -30,13 +34,11 @@ class SearchAlgorithm(ABC): # pylint: disable=too-few-public-methods
3034
space to be searched"""
3135

3236
@abstractmethod
33-
def search(
34-
self, surrogate_models: list[CubicSplineRegressionEstimator], specification: CausalSpecification
35-
) -> list:
37+
def search(self, surrogate_models: list[CubicSplineRegressionEstimator], scenario: Scenario) -> list:
3638
"""Function which implements a search routine which searches for the optimal fitness value for the specified
3739
scenario
3840
:param surrogate_models: The surrogate models to be searched
39-
:param specification: The Causal Specification (combination of Scenario and Causal Dag)"""
41+
:param scenario: The modelling scenario"""
4042

4143

4244
class Simulator(ABC):
@@ -64,11 +66,13 @@ class CausalSurrogateAssistedTestCase:
6466

6567
def __init__(
6668
self,
67-
specification: CausalSpecification,
69+
scenario: Scenario,
70+
causal_dag: CausalDAG,
6871
search_algorithm: SearchAlgorithm,
6972
simulator: Simulator,
7073
):
71-
self.specification = specification
74+
self.scenario = scenario
75+
self.causal_dag = causal_dag
7276
self.search_algorithm = search_algorithm
7377
self.simulator = simulator
7478

@@ -87,8 +91,8 @@ def execute(
8791
:return: tuple containing SimulationResult or str, execution number and dataframe"""
8892

8993
for i in range(max_executions):
90-
surrogate_models = self.generate_surrogates(self.specification, df)
91-
candidate_test_case, _, surrogate_model = self.search_algorithm.search(surrogate_models, self.specification)
94+
surrogate_models = self.generate_surrogates(df)
95+
candidate_test_case, _, surrogate_model = self.search_algorithm.search(surrogate_models, self.scenario)
9296

9397
self.simulator.startup()
9498
test_result = self.simulator.run_with_config(candidate_test_case)
@@ -101,7 +105,7 @@ def execute(
101105
else:
102106
df = pd.concat([df, test_result_df], ignore_index=True)
103107
if test_result.fault:
104-
print(
108+
logger.info(
105109
f"Fault found between {surrogate_model.base_test_case.treatment_variable.name} causing "
106110
f"{surrogate_model.base_test_case.outcome_variable.name}. Contradiction with "
107111
f"expected {surrogate_model.expected_relationship}."
@@ -113,28 +117,25 @@ def execute(
113117
)
114118
return test_result, i + 1, df
115119

116-
print("No fault found")
120+
logger.info("No fault found")
117121
return "No fault found", i + 1, df
118122

119-
def generate_surrogates(
120-
self, specification: CausalSpecification, df: pd.DataFrame
121-
) -> list[CubicSplineRegressionEstimator]:
122-
"""Generate a surrogate model for each edge of the dag that specifies it is included in the DAG metadata.
123-
:param specification: The Causal Specification (combination of Scenario and Causal Dag)
123+
def generate_surrogates(self, df: pd.DataFrame) -> list[CubicSplineRegressionEstimator]:
124+
"""Generate a surrogate model for each edge of the DAG that specifies it is included in the DAG metadata.
124125
:param df: An dataframe which contains data relevant to the specified scenario
125126
:return: A list of surrogate models
126127
"""
127128
surrogate_models = []
128129

129-
for u, v in specification.causal_dag.edges:
130-
edge_metadata = specification.causal_dag.adj[u][v]
130+
for u, v in self.causal_dag.edges:
131+
edge_metadata = self.causal_dag.adj[u][v]
131132
if "included" in edge_metadata:
132-
from_var = specification.scenario.variables.get(u)
133-
to_var = specification.scenario.variables.get(v)
133+
from_var = self.scenario.variables.get(u)
134+
to_var = self.scenario.variables.get(v)
134135
base_test_case = BaseTestCase(from_var, to_var)
135136

136-
minimal_adjustment_set = specification.causal_dag.identification(
137-
base_test_case, specification.scenario.hidden_variables()
137+
minimal_adjustment_set = self.causal_dag.identification(
138+
base_test_case, self.scenario.hidden_variables()
138139
)
139140

140141
surrogate = CubicSplineRegressionEstimator(

causal_testing/surrogate/surrogate_search_algorithms.py

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from pygad import GA
88

99
from causal_testing.estimation.cubic_spline_estimator import CubicSplineRegressionEstimator
10-
from causal_testing.specification.causal_specification import CausalSpecification
1110
from causal_testing.surrogate.causal_surrogate_assisted import SearchAlgorithm
11+
from causal_testing.specification.scenario import Scenario
1212

1313

1414
class GeneticSearchAlgorithm(SearchAlgorithm):
@@ -27,9 +27,7 @@ def __init__(self, delta=0.05, config: dict = None) -> None:
2727
}
2828

2929
# pylint: disable=too-many-locals
30-
def search(
31-
self, surrogate_models: list[CubicSplineRegressionEstimator], specification: CausalSpecification
32-
) -> list:
30+
def search(self, surrogate_models: list[CubicSplineRegressionEstimator], scenario: Scenario) -> list:
3331
solutions = []
3432

3533
for surrogate_model in surrogate_models:
@@ -53,7 +51,7 @@ def fitness_function(ga, solution, idx): # pylint: disable=unused-argument
5351
)
5452
return contradiction_function(ate[0])
5553

56-
gene_types, gene_space = self.create_gene_types(surrogate_model, specification)
54+
gene_types, gene_space = self.create_gene_types(surrogate_model, scenario)
5755

5856
num_genes = 1 + len(surrogate_model.adjustment_set)
5957

@@ -71,8 +69,7 @@ def fitness_function(ga, solution, idx): # pylint: disable=unused-argument
7169
for k, v in self.config.items():
7270
if k == "gene_space":
7371
raise ValueError(
74-
"Gene space should not be set through config. This is generated from the causal "
75-
"specification"
72+
"Gene space should not be set through config. This is generated from the modelling scenario"
7673
)
7774
setattr(ga, k, v)
7875

@@ -88,24 +85,22 @@ def fitness_function(ga, solution, idx): # pylint: disable=unused-argument
8885
return max(solutions, key=itemgetter(1)) # This can be done better with fitness normalisation between edges
8986

9087
@staticmethod
91-
def create_gene_types(
92-
surrogate_model: CubicSplineRegressionEstimator, specification: CausalSpecification
93-
) -> tuple[list, list]:
94-
"""Generate the gene_types and gene_space for a given fitness function and specification
88+
def create_gene_types(surrogate_model: CubicSplineRegressionEstimator, scenario: Scenario) -> tuple[list, list]:
89+
"""Generate the gene_types and gene_space for a given fitness function and scenario
9590
:param surrogate_model: Instance of a CubicSplineRegressionEstimator
96-
:param specification: The Causal Specification (combination of Scenario and Causal Dag)"""
91+
:param scenario: The modelling scenario"""
9792

9893
var_space = {}
9994
var_space[surrogate_model.base_test_case.treatment_variable.name] = {}
10095
for adj in surrogate_model.adjustment_set:
10196
var_space[adj] = {}
10297

103-
for relationship in list(specification.scenario.constraints):
98+
for relationship in list(scenario.constraints):
10499
print(relationship)
105100
rel_split = str(relationship).split(" ")
106101

107102
if rel_split[0] in var_space:
108-
datatype = specification.scenario.variables.get(rel_split[0]).datatype
103+
datatype = scenario.variables.get(rel_split[0]).datatype
109104
if rel_split[1] == ">=":
110105
var_space[rel_split[0]]["low"] = datatype(rel_split[2])
111106
elif rel_split[1] == "<=":
@@ -119,9 +114,7 @@ def create_gene_types(
119114
gene_space.append(var_space[adj])
120115

121116
gene_types = []
122-
gene_types.append(
123-
specification.scenario.variables.get(surrogate_model.base_test_case.treatment_variable.name).datatype
124-
)
117+
gene_types.append(scenario.variables.get(surrogate_model.base_test_case.treatment_variable.name).datatype)
125118
for adj in surrogate_model.adjustment_set:
126-
gene_types.append(specification.scenario.variables.get(adj).datatype)
119+
gene_types.append(scenario.variables.get(adj).datatype)
127120
return gene_types, gene_space

causal_testing/testing/metamorphic_relation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import networkx as nx
1414

15-
from causal_testing.specification.causal_specification import CausalDAG, Node
15+
from causal_testing.specification.causal_dag import CausalDAG
1616
from causal_testing.testing.base_test_case import BaseTestCase
1717

1818
logger = logging.getLogger(__name__)
@@ -23,7 +23,7 @@ class MetamorphicRelation:
2323
"""Class representing a metamorphic relation."""
2424

2525
base_test_case: BaseTestCase
26-
adjustment_vars: Iterable[Node]
26+
adjustment_vars: Iterable[str]
2727

2828
def __eq__(self, other):
2929
same_type = self.__class__ == other.__class__

examples/covasim_/vaccinating_elderly/example_vaccine.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
import logging
33
import pandas as pd
44
from causal_testing.specification.causal_dag import CausalDAG
5-
from causal_testing.specification.scenario import Scenario
65
from causal_testing.specification.variable import Input, Output
7-
from causal_testing.specification.causal_specification import CausalSpecification
86
from causal_testing.testing.causal_test_case import CausalTestCase
97
from causal_testing.testing.causal_effect import Positive, Negative, NoEffect
108
from causal_testing.estimation.linear_regression_estimator import LinearRegressionEstimator
@@ -28,32 +26,12 @@ def setup_test_case(verbose: bool = False):
2826
causal_dag = CausalDAG(f"{ROOT}/dag.dot")
2927

3028
# 2. Create variables
31-
pop_size = Input("pop_size", int)
32-
pop_infected = Input("pop_infected", int)
33-
n_days = Input("n_days", int)
3429
vaccine = Input("vaccine", int)
3530
cum_infections = Output("cum_infections", int)
3631
cum_vaccinations = Output("cum_vaccinations", int)
3732
cum_vaccinated = Output("cum_vaccinated", int)
3833
max_doses = Output("max_doses", int)
3934

40-
# 3. Create scenario by applying constraints over a subset of the input variables
41-
scenario = Scenario(
42-
variables={
43-
pop_size,
44-
pop_infected,
45-
n_days,
46-
cum_infections,
47-
vaccine,
48-
cum_vaccinated,
49-
cum_vaccinations,
50-
max_doses,
51-
},
52-
)
53-
54-
# 4. Construct a causal specification from the scenario and causal DAG
55-
causal_specification = CausalSpecification(scenario, causal_dag)
56-
5735
# 5. Read the previously simulated data
5836
obs_df = pd.read_csv("simulated_data.csv")
5937

examples/lr91/example_max_conductances.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from causal_testing.specification.causal_dag import CausalDAG
55
from causal_testing.specification.scenario import Scenario
66
from causal_testing.specification.variable import Input, Output
7-
from causal_testing.specification.causal_specification import CausalSpecification
87
from causal_testing.testing.causal_test_case import CausalTestCase
98
from causal_testing.testing.causal_effect import Positive, Negative, NoEffect
109
from causal_testing.estimation.linear_regression_estimator import LinearRegressionEstimator

examples/poisson-line-process/example_pure_python.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from causal_testing.specification.causal_dag import CausalDAG
77
from causal_testing.specification.scenario import Scenario
88
from causal_testing.specification.variable import Input, Output
9-
from causal_testing.specification.causal_specification import CausalSpecification
109
from causal_testing.testing.causal_test_case import CausalTestCase
1110
from causal_testing.testing.causal_effect import ExactValue, Positive
1211
from causal_testing.estimation.linear_regression_estimator import LinearRegressionEstimator
@@ -79,9 +78,6 @@ def estimate_risk_ratio(self) -> float:
7978
}
8079
)
8180

82-
# 4. Construct a causal specification from the scenario and causal DAG
83-
causal_specification = CausalSpecification(scenario, causal_dag)
84-
8581
observational_data_path = f"{ROOT}/data/random/data_random_1000.csv"
8682

8783

@@ -99,7 +95,7 @@ def test_poisson_intensity_num_shapes(save=False):
9995
base_test_case=base_test_case,
10096
treatment_value=treatment_value,
10197
control_value=control_value,
102-
adjustment_set=causal_specification.causal_dag.identification(base_test_case),
98+
adjustment_set=causal_dag.identification(base_test_case),
10399
df=pd.read_csv(f"{ROOT}/data/smt_100/data_smt_wh{wh}_100.csv", index_col=0).astype(float),
104100
effect_modifiers=None,
105101
alpha=0.05,
@@ -114,7 +110,7 @@ def test_poisson_intensity_num_shapes(save=False):
114110
base_test_case=base_test_case,
115111
treatment_value=treatment_value,
116112
control_value=control_value,
117-
adjustment_set=causal_specification.causal_dag.identification(base_test_case),
113+
adjustment_set=causal_dag.identification(base_test_case),
118114
df=observational_df,
119115
effect_modifiers=None,
120116
formula="num_shapes_unit ~ I(intensity ** 2) + intensity - 1",
@@ -158,7 +154,7 @@ def test_poisson_width_num_shapes(save=False):
158154
base_test_case=base_test_case,
159155
treatment_value=w + 1.0,
160156
control_value=float(w),
161-
adjustment_set=causal_specification.causal_dag.identification(base_test_case),
157+
adjustment_set=causal_dag.identification(base_test_case),
162158
df=df,
163159
effect_modifiers={"intensity": i},
164160
formula="num_shapes_unit ~ width + I(intensity ** 2)+I(width ** -1)+intensity-1",

0 commit comments

Comments
 (0)