Skip to content

Commit 3d51429

Browse files
committed
json logging
1 parent 221a3cc commit 3d51429

File tree

3 files changed

+68
-45
lines changed

3 files changed

+68
-45
lines changed

causal_testing/json_front/json_class.py

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -129,40 +129,19 @@ def run_json_tests(self, effects: dict, estimators: dict, f_flag: bool = False,
129129
if "skip" in test and test["skip"]:
130130
continue
131131
test["estimator"] = estimators[test["estimator"]]
132-
if "mutations" in test:
132+
# If we have specified concrete control and treatment value
133+
if "mutations" not in test:
134+
failed, msg = self._run_concrete_metamorphic_test()
135+
# If we have a variable to mutate
136+
else:
133137
if test["estimate_type"] == "coefficient":
134138
failed, msg = self._run_coefficient_test(test=test, f_flag=f_flag, effects=effects)
135139
else:
136-
failed, msg = self._run_ate_test(test=test, f_flag=f_flag, effects=effects, mutates=mutates)
137-
self._append_to_file(msg, logging.INFO)
138-
else:
139-
outcome_variable = next(
140-
iter(test["expected_effect"])
141-
) # Take first key from dictionary of expected effect
142-
base_test_case = BaseTestCase(
143-
treatment_variable=self.variables["inputs"][test["treatment_variable"]],
144-
outcome_variable=self.variables["outputs"][outcome_variable],
145-
)
146-
147-
causal_test_case = CausalTestCase(
148-
base_test_case=base_test_case,
149-
expected_causal_effect=effects[test["expected_effect"][outcome_variable]],
150-
control_value=test["control_value"],
151-
treatment_value=test["treatment_value"],
152-
estimate_type=test["estimate_type"],
153-
)
154-
failed, _ = self._execute_test_case(causal_test_case=causal_test_case, test=test, f_flag=f_flag)
155-
156-
msg = (
157-
f"Executing concrete test: {test['name']} \n"
158-
+ f"treatment variable: {test['treatment_variable']} \n"
159-
+ f"outcome_variable = {outcome_variable} \n"
160-
+ f"control value = {test['control_value']}, treatment value = {test['treatment_value']} \n"
161-
+ f"Result: {'FAILED' if failed else 'Passed'}"
162-
)
163-
self._append_to_file(msg, logging.INFO)
140+
failed, msg = self._run_metamorphic_tests(
141+
test=test, f_flag=f_flag, effects=effects, mutates=mutates
142+
)
164143
test["failed"] = failed
165-
# print(msg)
144+
test["result"] = msg
166145
return self.test_plan["tests"]
167146

168147
def _run_coefficient_test(self, test: dict, f_flag: bool, effects: dict):
@@ -194,9 +173,36 @@ def _run_coefficient_test(self, test: dict, f_flag: bool, effects: dict):
194173
+ "==============\n"
195174
+ f" Result: {'FAILED' if failed else 'Passed'}"
196175
)
176+
self._append_to_file(msg, logging.INFO)
177+
return failed, result
178+
179+
def _run_concrete_metamorphic_test(self, test: dict, f_flag: bool, effects: dict, mutates: dict):
180+
outcome_variable = next(iter(test["expected_effect"])) # Take first key from dictionary of expected effect
181+
base_test_case = BaseTestCase(
182+
treatment_variable=self.variables["inputs"][test["treatment_variable"]],
183+
outcome_variable=self.variables["outputs"][outcome_variable],
184+
)
185+
186+
causal_test_case = CausalTestCase(
187+
base_test_case=base_test_case,
188+
expected_causal_effect=effects[test["expected_effect"][outcome_variable]],
189+
control_value=test["control_value"],
190+
treatment_value=test["treatment_value"],
191+
estimate_type=test["estimate_type"],
192+
)
193+
failed, msg = self._execute_test_case(causal_test_case=causal_test_case, test=test, f_flag=f_flag)
194+
195+
msg = (
196+
f"Executing concrete test: {test['name']} \n"
197+
+ f"treatment variable: {test['treatment_variable']} \n"
198+
+ f"outcome_variable = {outcome_variable} \n"
199+
+ f"control value = {test['control_value']}, treatment value = {test['treatment_value']} \n"
200+
+ f"Result: {'FAILED' if failed else 'Passed'}"
201+
)
202+
self._append_to_file(msg, logging.INFO)
197203
return failed, msg
198204

199-
def _run_ate_test(self, test: dict, f_flag: bool, effects: dict, mutates: dict):
205+
def _run_metamorphic_tests(self, test: dict, f_flag: bool, effects: dict, mutates: dict):
200206
"""Builds structures and runs test case for tests with an estimate_type of 'ate'.
201207
202208
:param test: Single JSON test definition stored in a mapping (dict)
@@ -228,6 +234,7 @@ def _run_ate_test(self, test: dict, f_flag: bool, effects: dict, mutates: dict):
228234
+ f" Number of concrete tests for test case: {str(len(concrete_tests))} \n"
229235
+ f" {failures}/{len(concrete_tests)} failed for {test['name']}"
230236
)
237+
self._append_to_file(msg, logging.INFO)
231238
return failures, msg
232239

233240
def _execute_tests(self, concrete_tests, test, f_flag):
@@ -269,10 +276,11 @@ def _execute_test_case(
269276
test_passes = causal_test_case.expected_causal_effect.apply(causal_test_result)
270277

271278
if "coverage" in test and test["coverage"]:
272-
adequacy = DataAdequacy(causal_test_case, causal_test_engine, estimation_model)
273-
effect_estimate, ci_low, ci_high, outcomes = adequacy.measure_adequacy(100)
274-
self._append_to_file(f"KURTOSIS: {effect_estimate.mean()}", logging.INFO)
275-
self._append_to_file(f"PASSING: {sum(outcomes)}/{len(outcomes)}", logging.INFO)
279+
adequacy_metric = DataAdequacy(causal_test_case, causal_test_engine, estimation_model)
280+
adequacy_metric.measure_adequacy()
281+
# self._append_to_file(f"KURTOSIS: {effect_estimate.mean()}", logging.INFO)
282+
# self._append_to_file(f"PASSING: {sum(outcomes)}/{len(outcomes)}", logging.INFO)
283+
causal_test_result.adequacy = adequacy_metric
276284

277285
if causal_test_result.ci_low() is not None and causal_test_result.ci_high() is not None:
278286
result_string = (

causal_testing/testing/causal_test_adequacy.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,19 @@ def measure_adequacy(self):
3434

3535

3636
class DataAdequacy:
37-
def __init__(self, test_case: CausalTestCase, test_engine: CausalTestEngine, estimator: Estimator):
37+
def __init__(
38+
self, test_case: CausalTestCase, test_engine: CausalTestEngine, estimator: Estimator, bootstrap_size: int = 100
39+
):
3840
self.test_case = test_case
3941
self.test_engine = test_engine
4042
self.estimator = estimator
43+
self.kurtosis = None
44+
self.outcomes = None
45+
self.bootstrap_size = bootstrap_size
4146

42-
def measure_adequacy(self, bootstrap_size: int = 100):
47+
def measure_adequacy(self):
4348
results = []
44-
for i in range(bootstrap_size):
49+
for i in range(self.bootstrap_size):
4550
estimator = deepcopy(self.estimator)
4651
estimator.df = estimator.df.sample(len(estimator.df), replace=True, random_state=i)
4752
try:
@@ -66,6 +71,8 @@ def convert_to_df(field):
6671
results[field] = convert_to_df(field)
6772

6873
effect_estimate = pd.concat(results["effect_estimate"].tolist(), axis=1).transpose().reset_index(drop=True)
69-
ci_low = pd.concat(results["ci_low"].tolist(), axis=1).transpose()
70-
ci_high = pd.concat(results["ci_high"].tolist(), axis=1).transpose()
71-
return effect_estimate.kurtosis(), ci_low.kurtosis(), ci_high.kurtosis(), outcomes
74+
self.kurtosis = effect_estimate.kurtosis()
75+
self.outcomes = sum(outcomes)
76+
77+
def to_dict(self):
78+
return {"kurtosis": self.kurtosis.to_dict(), "bootstrap_size": self.bootstrap_size, "passing": self.outcomes}

causal_testing/testing/causal_test_result.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ def __init__(
2828
test_value: TestValue,
2929
confidence_intervals: [float, float] = None,
3030
effect_modifier_configuration: {Variable: Any} = None,
31+
adequacy=None,
3132
):
3233
self.estimator = estimator
34+
self.adequacy = adequacy
3335
if estimator.adjustment_set:
3436
self.adjustment_set = estimator.adjustment_set
3537
else:
@@ -66,23 +68,29 @@ def push(s, inc=" "):
6668
ci_str = " " + push(pd.DataFrame(self.confidence_intervals).transpose().to_string(header=False))
6769
confidence_str += f"Confidence intervals:{ci_str}\n"
6870
confidence_str += f"Alpha:{self.estimator.alpha}\n"
69-
return base_str + confidence_str
71+
adequacy_str = ""
72+
if self.adequacy:
73+
adequacy_str = str(self.adequacy)
74+
return base_str + confidence_str + adequacy_str
7075

71-
def to_dict(self):
76+
def to_dict(self, json=False):
7277
"""Return result contents as a dictionary
7378
:return: Dictionary containing contents of causal_test_result
7479
"""
75-
return {
80+
base_dict = {
7681
"treatment": self.estimator.treatment,
7782
"control_value": self.estimator.control_value,
7883
"treatment_value": self.estimator.treatment_value,
7984
"outcome": self.estimator.outcome,
80-
"adjustment_set": self.adjustment_set,
85+
"adjustment_set": list(self.adjustment_set) if json else self.adjustment_set,
8186
"effect_measure": self.test_value.type,
8287
"effect_estimate": self.test_value.value,
8388
"ci_low": self.ci_low(),
8489
"ci_high": self.ci_high(),
8590
}
91+
if self.adequacy:
92+
base_dict["adequacy"] = self.adequacy.to_dict()
93+
return base_dict
8694

8795
def ci_low(self):
8896
"""Return the lower bracket of the confidence intervals."""

0 commit comments

Comments
 (0)