From 34668346e463da1897b1e37c8257c3e1f2b5e3e5 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 31 Jul 2026 15:07:34 +0100 Subject: [PATCH 1/3] Fixed docs build --- causal_testing/discovery/nsga_discovery.py | 2 +- .../estimation/abstract_regression_estimator.py | 10 +++++++--- causal_testing/testing/causal_test_case.py | 10 ++++++---- docs/source/modules/estimators.rst | 15 +-------------- 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/causal_testing/discovery/nsga_discovery.py b/causal_testing/discovery/nsga_discovery.py index 3cb99222..646d20fb 100644 --- a/causal_testing/discovery/nsga_discovery.py +++ b/causal_testing/discovery/nsga_discovery.py @@ -39,7 +39,7 @@ def binary_string_to_causal_dag(self, individual: np.array) -> CausalDAG: Converts a binary string representation of a causal DAG to a CausalDAG object. :param individual: Bitstring of the same length as `possible_edges` such that 1 at position `i` represents - possible_edges[i] being an edge in the graph and 0 represents it not being. + possible_edges[i] being an edge in the graph and 0 represents it not being. :returns: The converted CausalDAG instance. """ causal_dag = CausalDAG() diff --git a/causal_testing/estimation/abstract_regression_estimator.py b/causal_testing/estimation/abstract_regression_estimator.py index cee528b0..fbc58759 100644 --- a/causal_testing/estimation/abstract_regression_estimator.py +++ b/causal_testing/estimation/abstract_regression_estimator.py @@ -64,6 +64,7 @@ def __init__( def _get_adjusted_variables(self, tree: ast.AST) -> set[str]: """ Recursively return variables in an AST. + :returns: Set of all variables not used as part of a function. """ if isinstance(tree, ast.Name) and tree.id != self.treatment_variable: @@ -108,7 +109,9 @@ def _setup_covariates(self, df: pd.DataFrame) -> pd.Series: Parse the formula and set up the covariates from the design matrix so we can use them in the statsmodels array API. This allows us to only parse the formula once, rather than using the formula API, which parses it every time the regression model is fit, which can be a lot if using causal test adequacy. + :param df: The data to use. + :returns: The data and the covariate columns. """ _, covariate_data = dmatrices(self.formula, df, return_type="dataframe") @@ -154,9 +157,10 @@ def treatment_columns(self, model: RegressionResultsWrapper) -> list[str]: This is a workaround for statsmodels mangling the names of categorical variables to include the values. :param model: The fitted model from which to extract the variable names. + :returns: A list of the feature names in the model that represent the treatment. Normally this will just be - [treatment_name], but for categorical treatments, you'll have - [treatment_name[value_1], treatment_name[value_2]]. + [treatment_name], but for categorical treatments, you'll have + [treatment_name[value_1], treatment_name[value_2]]. """ return [ param @@ -170,7 +174,7 @@ def _predict(self, df) -> pd.DataFrame: :param df: The data to use. :param: adjustment_config: The values of the adjustment variables to use. - :return: The estimated outcome under control and treatment, with confidence intervals in the form of a + :returns: The estimated outcome under control and treatment, with confidence intervals in the form of a dataframe with columns "predicted", "se", "ci_lower", and "ci_upper". """ model = self.fit_model(df) diff --git a/causal_testing/testing/causal_test_case.py b/causal_testing/testing/causal_test_case.py index fa6e4c90..ec1de7b8 100644 --- a/causal_testing/testing/causal_test_case.py +++ b/causal_testing/testing/causal_test_case.py @@ -20,6 +20,7 @@ class CausalTestCase: variables, a CausalTestCase stores the values of these variables. Also the outcome variable and value are specified. The goal of a CausalTestCase is to test whether the intervention made to the control via the treatment causes the model-under-test to produce the expected change. + :param base_test_case: A BaseTestCase object consisting of a treatment variable, outcome variable and effect :param expected_causal_effect: The expected causal effect (Positive, Negative, No Effect). :param effect_measure: A string which denotes the type of estimate to return. @@ -70,10 +71,11 @@ def measure_adequacy( ) -> DataAdequacy: """ Calculate the adequacy measurement, and populate the data_adequacy field. + :param df: The original dataset to use. :param bootstrap_size: The number of bootstrap samples to use. (Defaults to 100) :param group_by: For IPCWEstimator - the "id" column to ensure that entire individuals are sampled rather than - random rows. + random rows. """ results = [] outcomes = [] @@ -130,8 +132,7 @@ def execute_test( :param suppress_estimation_errors: Set to True to suppress estimation errors. (Defaults to False) :param bootstrap_size: The number of bootstrap samples to use. (Defaults to 100) :param group_by: For IPCWEstimator - the "id" column to ensure that entire individuals are sampled rather than - random rows. - :return causal_test_result: A CausalTestResult for the executed causal test case. + random rows. """ if not self.skip: try: @@ -161,7 +162,8 @@ def estimate_effect(self, df: pd.DataFrame) -> CausalTestResult: Execute a causal test case and return the causal test result. :param df: The data to use. - :return causal_test_result: A CausalTestResult for the executed causal test case. + + :returns: A CausalTestResult for the executed causal test case. """ if self.query: df = df.query(self.query) diff --git a/docs/source/modules/estimators.rst b/docs/source/modules/estimators.rst index 20c1a26e..cf286e9b 100644 --- a/docs/source/modules/estimators.rst +++ b/docs/source/modules/estimators.rst @@ -31,7 +31,7 @@ LogisticRegressionEstimator :noindex: MultinomialRegressionEstimator -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **Recommended use:** For categorical outcomes (e.g. colurs: Red, Green, Blue). @@ -42,19 +42,6 @@ MultinomialRegressionEstimator :show-inheritance: :noindex: -CubicSplineRegressionEstimator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Recommended use:** For continuous outcomes with non-linear relationships or changes in behaviour. -Useful when the relationship between treatment and outcome cannot be captured by a linear model. - -.. autoclass:: causal_testing.estimation.cubic_spline_estimator.CubicSplineRegressionEstimator - :members: - :undoc-members: - :show-inheritance: - :noindex: - - InstrumentalVariableEstimator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From dc28758bdd079a04769c29b9647cac64143a797e Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 31 Jul 2026 15:24:37 +0100 Subject: [PATCH 2/3] Added PR to build docs --- .github/workflows/ci-tests-drafts.yaml | 6 +++++- .github/workflows/ci-tests.yaml | 6 +++++- pyproject.toml | 2 -- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-tests-drafts.yaml b/.github/workflows/ci-tests-drafts.yaml index 7f1dfe80..339f907f 100644 --- a/.github/workflows/ci-tests-drafts.yaml +++ b/.github/workflows/ci-tests-drafts.yaml @@ -25,7 +25,7 @@ jobs: python --version python -m pip install --upgrade pip pip install -e . - pip install -e .[test] + pip install -e .[dev] pip install pytest pytest-cov - name: Register Jupyter Kernel run: | @@ -38,3 +38,7 @@ jobs: with: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} + - name: "Build docs" + working-directory: "docs" + run: | + make html diff --git a/.github/workflows/ci-tests.yaml b/.github/workflows/ci-tests.yaml index d74f335f..1f2b16a9 100644 --- a/.github/workflows/ci-tests.yaml +++ b/.github/workflows/ci-tests.yaml @@ -30,7 +30,7 @@ jobs: python --version python -m pip install --upgrade pip pip install -e . - pip install -e .[test] + pip install -e .[dev] pip install pytest pytest-cov - name: Register Jupyter Kernel run: | @@ -43,3 +43,7 @@ jobs: with: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} + - name: "Build docs" + working-directory: "docs" + run: | + make html diff --git a/pyproject.toml b/pyproject.toml index f773ba49..c33ced14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,8 +51,6 @@ dev = [ "pandoc", "pre-commit", "tox", -] -test = [ "nbclient", "nbformat", "ipykernel", From 75e1183da4ad5294bb7761ee4bb9208e4dcf2b84 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Mon, 3 Aug 2026 16:26:11 +0100 Subject: [PATCH 3/3] Updated schematic --- README.md | 4 +- .../source/_static/images}/.gitignore | 0 docs/source/_static/images/schematic-dark.png | Bin 0 -> 16519 bytes docs/source/_static/images/schematic.png | Bin 0 -> 15297 bytes docs/source/_static/images/schematic.tex | 127 +++++++++++++++ docs/source/background.rst | 2 +- docs/source/conf.py | 2 +- docs/source/index.rst | 5 +- images/schematic-dark.png | Bin 23854 -> 0 bytes images/schematic.png | Bin 22163 -> 0 bytes images/schematic.tex | 154 ------------------ paper/paper.md | 2 +- 12 files changed, 136 insertions(+), 160 deletions(-) rename {images => docs/source/_static/images}/.gitignore (100%) create mode 100644 docs/source/_static/images/schematic-dark.png create mode 100644 docs/source/_static/images/schematic.png create mode 100644 docs/source/_static/images/schematic.tex delete mode 100644 images/schematic-dark.png delete mode 100644 images/schematic.png delete mode 100644 images/schematic.tex diff --git a/README.md b/README.md index 30f0d675..8bea825e 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ the inputs and outputs of the system under test, supported by mathematical found enable causal inference. Each causal test case targets the causal effect of a specific intervention on the system under test--that is, a deliberate modification to the input configuration expected to produce a corresponding change in one or more outputs. -![Causal Testing Workflow](images/schematic-dark.png#gh-dark-mode-only) -![Causal Testing Workflow](images/schematic.png#gh-light-mode-only) +![Causal Testing Workflow](docs/source/_static/images/schematic-dark.png#gh-dark-mode-only) +![Causal Testing Workflow](docs/source/_static/images/schematic.png#gh-light-mode-only) ## Installation diff --git a/images/.gitignore b/docs/source/_static/images/.gitignore similarity index 100% rename from images/.gitignore rename to docs/source/_static/images/.gitignore diff --git a/docs/source/_static/images/schematic-dark.png b/docs/source/_static/images/schematic-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ee1f1065d39e8558cdab85c6a9c3c7a221572163 GIT binary patch literal 16519 zcmb7rXH-*7)NUX&K|oZB(iG{v_aY)C2}MA9i-aQ5A%IeY4e1g>Zz>6h^xh$gQUzXv zG$Tck66w9eJ?Q&>-=DkIU3amPHJqF?XV2_Cd-i_zGtqYpb?IrZ(tXHx1Ip7=?L&6(OkT^KtU(SOe!ORnzWmew=UYuy-tb>dOB?L-Z#u{a z&$DN2srsf{iUwAEe_!JC_M_=XChcKChwi2lpUIyk<_}5&j7~nl?QN{)l>4vqR=nWD zUc043?|c`2U8h(lCR%p6OYP`Mf`+zuhOM)tpXe)EX7mj8>6596%x6946olBQn;bk2 z5tnI}$orc-Be;oyn-psrU<+%agC|HP4?9Y<{zXIM31%JjG?)a72caSb?4)^hH-;|L zQcfSowFyygO_8^{dwG|JWvhd({1$!$=HMVlX!qLhJ`6H)^*+sY+uj9tX!&&sr5oD= zdZBWJ>D~sNp8dUQEldWkob%DM@&|z!@11?f-f9%O0T-!}5Cd)M-_+cUVsv5K{aql? zH4x;s=G}+imUDtyY|U!BA`bblpX0lrcm3(jTPeAENuZ0GTu_ON(-3OX9`@}k_1sr9 zg4Z4sC~!0;as=(9$ygNao7Z}D7nGkG?zL&S#B&J9_`A*xe9qZ83|HT5+^uQdwQ|o1 z8yR=@>|sO1Np=4Je2MA3b9Drcnqde$}sYV-;Q z84h);>mR>dnFi*K7xP<~!4QHDUatIuAv8NDSa__~&fNp1q2`h&kw71ZMkZpH`6dKN z1`t=!jCD;nJLH=V0kx;sz|^3cy|5H!fY}OwXn;CdIkJohrD%qvtssaIO!+S1y}YyO z9(J#=dH^4BQWRawaVaL43fdK56sp&VI%H~6&d^vM#8LvKBZO_Fkqd+gl{ z#Gw_u=kpE}{~%-eH9pYy81wmLk`M+`thxPYkfQ9fMLH#Fzl_(U>`DN0 z#j>0nwQ1foDqbAz)7d?&ZoHs0B0kp6R-q!v%x3Q6kuVRkgXiS`L2GWvT;jh_6T4RC8a(i^AIjzm_`?O6QSL;6q^_FU7`0aDJZ}_4;W$ zY98f=cYRQ~>o;gpq#7J@5|`tJi1XF9Jq_i53a&JyocH3GPk8qaX;N~)LDfI>z8J}T z`l$!ETU_+Ab7bNNP3v>%Ilhls68$&D0FfjaupFDaK#Q}5S<+-Lc-TX{&#(@GEf-o zDzMh+np)LU76^BT2G@P}zatP5(frM|Nb|pURpC@{Tl{M;um-$#Rz45uAR^GKxhBD^ zY$Bv|dv!$<&4WR`19N2SsR_X-CHA5Tq-nSUhh^t!{lr%)OtR>${WYs0E>FgyC)7%- zDn!~umsOK8)i?By)I@PxhZhH}D(B(W4$F3g=W?nr8`ZnL0_%~oDPeUF=Zrek&U+F_~^AuUR2oM{i2UpS^s;64Ht1PQx6T4eIcaj{> zgPwz6AOVrlly+|pB9wp-!sVDkLkciEcf~n=9-`&r{S(kNMscnNQn>+2S(Qbz9e|$D z4Dsf_Lht5j3qNF-d?|aK|29MMm&uTj)R@^XL^(h#{h)uAdgPd95&UcSj!rV=04AXBv-|h{#@u0(IJw*78>iME;6Q|M0L~)Z58N|=gE~b

*;kQKi*6^rzkd4DP77t%JeWm1dG0Zj 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/images/schematic.png b/docs/source/_static/images/schematic.png new file mode 100644 index 0000000000000000000000000000000000000000..01110fded67dc263b1c7d97454d83d9d0536b8d3 GIT binary patch literal 15297 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 JwGZuI{U2`@U4#Gt literal 0 HcmV?d00001 diff --git a/docs/source/_static/images/schematic.tex b/docs/source/_static/images/schematic.tex new file mode 100644 index 00000000..78808899 --- /dev/null +++ b/docs/source/_static/images/schematic.tex @@ -0,0 +1,127 @@ +\documentclass{standalone} + +\usepackage{tikz} +\usetikzlibrary{arrows,positioning,shapes,calc,fit,overlay-beamer-styles, backgrounds} +\usepackage{dsfont,pifont} +\newcommand*{\expe}{\mathds{E}} +\usepackage{amsmath} +\usepackage{booktabs} +\usepackage{fontawesome7} + +\usepackage[default]{FiraSans} +\usepackage[mathrm=sym]{unicode-math} +\setmathfont{Fira Math} + +\newcommand{\indep}{\perp \!\!\! \perp} + +\begin{document} +\tikzset{ + node/.style={circle, draw, minimum size=3ex, inner sep=0.2}, + edge/.style={->,> = latex'}, +} + +\newcommand{\cmark}{\ding{51}}% +\newcommand{\xmark}{\ding{55}}% + +\begin{tikzpicture}[background rectangle/.style={fill=none}, show background rectangle, color=black] + + % Test Case + \begin{scope}[name prefix=test-, local bounding box=test-case] + \node[draw=none, rectangle, anchor=north] (title) at (0, 0) {Causal Test Cases}; + \node[anchor=north,align=center] (tuple) at (title.south) {$I \to_{?} Y_3$\hspace{5mm}$X_2 \indep_? X_2$}; + \node[draw, rectangle] [fit=(title) (tuple)] {}; + \end{scope} + + % ci + \begin{scope}[name prefix=ci-, local bounding box=ci, shift={($(test-test-case.east) + (1, 0)$)}] + \node[draw=none, rectangle, anchor=south west] (title) {Causal Inference}; + \node[draw=none, rectangle, anchor=north, align=center] (brain) at (title.south) {\faIcon{hexagon-nodes-bolt}}; + + \coordinate (top) at ({(0, 0)} |- test-title.north); + \coordinate (bot) at ({(0, 0)} |- test-tuple.south); + + \node[draw, rectangle] [fit=(title) (brain) (top) (bot)] {}; + \end{scope} + + % Estimate + \begin{scope}[name prefix=estimate-, local bounding box=estimate, shift={($(ci-ci.east)+(1, 0)$)}] + \node[draw=none, rectangle, anchor=south west] (title) {Causal Estimate}; + \node[anchor=north] (table) at (title.south) {\faIcon{chart-line}}; + \coordinate (top) at ({(0, 0)} |- test-title.north); + \coordinate (bot) at ({(0, 0)} |- test-tuple.south); + \node[draw, rectangle] [fit=(title) (table) (top) (bot)] {}; + \end{scope} + + % Oracle + \begin{scope}[name prefix=oracle-, local bounding box=test-oracle, shift={($(estimate-estimate.east) + (1, 0)$)}] + \node[draw=none, rectangle, anchor=south west] (title) {Test Oracle}; + \node[draw=none, rectangle, anchor=north] (scale) at (title.south) {\faIcon{scale-balanced}}; + + \coordinate (top) at ({(0, 0)} |- test-title.north); + \coordinate (bot) at ({(0, 0)} |- test-tuple.south); + \node[draw, rectangle] [fit=(title) (scale) (top) (bot)] {}; + \end{scope} + + % Outcome + \begin{scope}[name prefix=outcome-, local bounding box=test-outcome, shift={($(oracle-test-oracle.east) + (1, 0)$)}] + \node[draw=none, rectangle, anchor=south west] (title) at (0,0) {Test Outcomes}; + \node[draw=none, anchor=north] (ok) at (title.south) {\cmark ~ \xmark}; + + \coordinate (top) at ({(0, 0)} |- test-title.north); + \coordinate (bot) at ({(0, 0)} |- test-tuple.south); + \node[draw, rectangle] (test-outcome) [fit=(outcome-title) (outcome-ok) (top) (bot)] {}; + \end{scope} + + + % Causal DAG + \begin{scope}[name prefix=dag-, shift={(0, 2)}] + \node[node] (x1) at (-1, 0) {$X_1$}; + \node[node] (x2) at (-1, 1.4) {$X_2$}; + \node[node] (i) at (0, 0.7) {$I$}; + \node[node] (y1) at (1,0) {$Y_{1}$}; + \node[node] (y2) at (1,0.7) {$Y_2$}; + \node[node] (y3) at (1,1.4) {$Y_3$}; + + \draw[edge] (x1) to (i); + \draw[edge] (x2) to (i); + \draw[edge] (i) to (y1); + \draw[edge] (i) to (y2); + \draw[edge] (i) to (y3); + \draw[edge] (x1) to (y1); + \draw[edge] (x2) to (y3); + \node[draw=none, rectangle] (nodes) [fit=(x1) (x2) (y1) (y2) (y3) (i)] {}; + \node[draw=none, rectangle, anchor=south] (title) at (nodes.north) {Causal DAG}; + \end{scope} + % DAG outline + \node[draw, rectangle] (dag) [fit=(dag-nodes) (dag-title) (dag-title)] {}; + + % Data + \begin{scope}[name prefix=data-, local bounding box=test-data, shift={($(dag) + (5, 1.12)$)}] + \node[draw=none, rectangle] (title) {Test Data}; + \node[anchor=north] (table) at (title.south) { + \begin{tabular}{rrrrrr} + \toprule + $X_1$ & $X_2$ & $I$ & $Y_1$ & $Y_2$ & $Y_3$ \\ + \midrule + 1.2 & ``UK'' & 0.3 & 7.8 & 4 & True \\ + 3.2 & ``UK'' & 0.1 & 7.6 & 8 & False \\ + \multicolumn{6}{c}{$\vdots$} \\ + \bottomrule + \end{tabular} + }; + \node[draw, rectangle] [fit=(title) (table)] {}; + \end{scope} + + + %Information flow + \draw[edge, dashed] (dag.290) -- (ci-ci.160); + \draw[edge, dashed] (dag) -- (test-test-case.north); + \draw[edge, dashed] (test-test-case) -- (ci-ci); + + \draw[edge, dashed] (data-test-data.south) -- (data-test-data |- ci-ci.north); + \draw[edge, dashed] (ci-ci) -- (estimate-estimate); + + \draw[edge, dashed] (estimate-estimate) -- (oracle-test-oracle.west |- estimate-estimate); + \draw[edge, dashed] (oracle-test-oracle.east |- outcome-test-outcome) -- (outcome-test-outcome); +\end{tikzpicture} +\end{document} diff --git a/docs/source/background.rst b/docs/source/background.rst index 33f5104e..0deabcca 100644 --- a/docs/source/background.rst +++ b/docs/source/background.rst @@ -71,7 +71,7 @@ Background .. container:: zoom-container - .. figure:: ../../images/schematic.png + .. figure:: _static/images/CITCOM-logo.png :class: zoomable-image :alt: Schematic diagram of the Causal Testing Framework :align: center diff --git a/docs/source/conf.py b/docs/source/conf.py index 7f81d7d1..69f04cee 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -78,7 +78,7 @@ html_theme = "sphinx_rtd_theme" # Static files such as CSS or images -html_static_path = ["_static", os.path.abspath("../../images")] +html_static_path = ["_static"] # Custom CSS html_css_files = ["css/custom.css"] diff --git a/docs/source/index.rst b/docs/source/index.rst index e0536a98..7283fe13 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -7,7 +7,10 @@ Welcome to the Causal Testing Framework Motivation ---------- -A common problem in computer science is to develop robust and reliable software systems that can perform correctly under various input configurations and maintain consistency across complex, physical scenarios. However, software systems, and more specifically computational models, can be difficult to test: they may contain hundreds of parameters, making testing all possible inputs computationally infeasible; some models may be inherently non-deterministic, producing different outputs for the same inputs due to randomness; or there may exist hidden causal relationships between input-output pairs, causing errors that only appear under specific combinations of input configurations. +From predicting the weather to simulating disease transmission, scientific software plays an increasingly pivotal role in developing scientific understanding that informs our everyday lives. +However, they are also some of the most difficult software systems to properly test. +They have large, complex input spaces, are computationally expensive to run, often rely on stochastic black-box components, and are applied in exploratory contexts where the expected outcomes are not known. +From a practical standpoint, the time and effort that can be dedicated to testing is often limited, especially in an academic context, making it especially important to maximise the efficiency of the limited number of test runs we are able to perform. The Framework ------------- diff --git a/images/schematic-dark.png b/images/schematic-dark.png deleted file mode 100644 index 1be684230cf5f5cbcf211189b49692bf6691531e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23854 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 SrU(YoWkGz6Zt=k1m>dv diff --git a/images/schematic.png b/images/schematic.png deleted file mode 100644 index c1445da402deb28a579b70599aea28840389663c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22163 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,> = latex'}, -} - -\newcommand{\cmark}{\ding{51}}% -\newcommand{\xmark}{\ding{55}}% - -\begin{tikzpicture}[background rectangle/.style={fill=none}, show background rectangle, color=black] - - % Test Case - \begin{scope}[name prefix=test-, local bounding box=test-case] - \node[draw=none, rectangle, anchor=north] (title) at (0, 0) {Causal Test Case}; - \node[anchor=north] (tuple) at (title.south) {$(X=i, \Delta=\text{increase}, Y=y_1)$}; - \node[draw, rectangle] [fit=(title) (tuple)] {}; - \end{scope} - - % Estimand - \begin{scope}[name prefix=estimand-, local bounding box=estimand, anchor=south, shift={($(test-test-case.east |- test-tuple.south) + (1, 0)$)}] - \node[anchor=south west] (eqn) at (0,0) { - $\Delta Y=\expe{[I=0 | X_1]} - \expe{[I=1 | X_1]} $ - }; - \node[draw=none, rectangle, anchor=south] (title) at (eqn.north) {Statistical Estimand}; - \node[draw, rectangle] [fit=(estimand-title) (estimand-eqn)] {}; - \end{scope} - - % Estimate - \begin{scope}[name prefix=estimate-, local bounding box=estimate, shift={($(estimand-estimand.east)+(1, 0)$)}] - \node[draw=none, rectangle, anchor=south west] (title) at (0, 0) {Causal Estimate}; - \node[anchor=north] (table) at (title.south) { - $\Delta Y=5$ - }; - \coordinate (top) at ({(0, 0)} |- test-title.north); - \coordinate (bot) at ({(0, 0)} |- estimand-eqn.south); - \node[draw, rectangle] [fit=(title) (table) (top) (bot)] {}; - \end{scope} - - % Oracle - \begin{scope}[name prefix=oracle-, local bounding box=test-oracle, shift={($(estimate-estimate.east) + (1.54, -0.4)$)}] - \begin{scope}[shift={(0,0)}, local bounding box=brain, scale=1.2] - \begin{scope}[shift={(-7.6932,3.5256)}, local bounding box=brain] - \path[draw,line width=0.025cm] (8.162, -2.8955) circle (0.066cm); - \path[draw,line width=0.025cm] (8.0485, -3.2243) circle (0.066cm); - \path[draw,line width=0.025cm] (8.0346, -3.5296) circle (0.066cm); - \path[draw,line width=0.025cm] (8.2166, -3.757) circle (0.066cm); - \path[draw,line width=0.025cm] (7.6556, -3.7827) circle (0.066cm); - \path[draw,line width=0.025cm] (7.6315, -3.5091) circle (0.066cm); - \path[draw,line width=0.025cm] (7.4451, -3.2224) circle (0.066cm); - \path[draw,line width=0.025cm] (7.6247, -2.9461) circle (0.066cm); - - \path[draw,line width=0.025cm,miter limit=4.0] (7.6932, -2.6331) -- (7.3637, -2.8234) -- (7.3637, -3.0567) -- (7.1749, -3.1656) -- (7.1749, -3.5256) -- (7.3341, -3.6175) -- (7.3341, -3.8517) -- (7.6883, -4.0562) -- (7.868, -3.9669) -- (8.0478, -4.0562) -- (8.4019, -3.8517) -- (8.4019, -3.6175) -- (8.5611, -3.5256) -- (8.5611, -3.1656) -- (8.3724, -3.0567) -- (8.3724, -2.8234) -- (8.0429, -2.6331) -- (7.868, -2.7341) -- cycle; - \path[draw,line width=0.025cm,miter limit=4.0] (7.868, -3.9669) -- (7.868, -2.7341); - \path[draw,line width=0.025cm] (7.5588, -2.9461) -- (7.3637, -2.9461); - \path[draw,line width=0.025cm] (7.4451, -3.1565) -- (7.4451, -2.9461); - \path[draw,line width=0.025cm] (7.6316, -3.4431) -- (7.6316, -3.2116) -- (7.868, -3.2116); - \path[draw,line width=0.025cm] (7.5897, -3.7827) -- (7.4177, -3.7827) -- (7.4177, -3.523) -- (7.1749, -3.523); - \path[draw,line width=0.025cm] (8.162, -2.9614) -- (8.162, -3.0534) -- (7.868, -3.0534); - \path[draw,line width=0.025cm] (8.0485, -3.1584) -- (8.0485, -3.0534); - \path[draw,line width=0.025cm] (8.1005, -3.5296) -- (8.313, -3.5296) -- (8.313, -3.3442) -- (8.5611, -3.3442); - \path[draw,line width=0.025cm] (8.1507, -3.757) -- (8.0477, -3.757) -- (8.0477, -4.0561); - \end{scope} - \end{scope} - \node[draw=none, rectangle, anchor=south] (title) at (brain.north) {Test Oracle}; - - \node[draw, rectangle] [fit=(title) (brain)] {}; - \end{scope} - - % Outcome - \begin{scope}[name prefix=outcome-, local bounding box=test-outcome, shift={($(oracle-brain.east |- estimate-estimate.east) + (1, 0)$)}] - \node[draw=none, rectangle, anchor=south west] (title) at (0,0) {Test Outcomes}; - \node[draw=none, anchor=north] (ok) at (title.south) {\cmark ~ \xmark}; - - \coordinate (top) at ({(0, 0)} |- test-title.north); - \coordinate (bot) at ({(0, 0)} |- estimand-eqn.south); - \node[draw, rectangle] (test-outcome) [fit=(outcome-title) (outcome-ok) (top) (bot)] {}; - \end{scope} - - - % Causal DAG - \begin{scope}[name prefix=dag-, shift={($(estimand-estimand.north) + (0, 2)$)}] - \node[node] (x1) at (-1, 0) {$X_1$}; - \node[node] (x2) at (-1, 1.4) {$X_2$}; - \node[node] (i) at (0, 0.7) {$I$}; - \node[node] (y1) at (1,0) {$Y_{1}$}; - \node[node] (y2) at (1,0.7) {$Y_2$}; - \node[node] (y3) at (1,1.4) {$Y_3$}; - - \draw[edge] (x1) to (i); - \draw[edge] (x2) to (i); - \draw[edge] (i) to (y1); - \draw[edge] (i) to (y2); - \draw[edge] (i) to (y3); - \draw[edge] (x1) to (y1); - \draw[edge] (x2) to (y3); - \node[draw=none, rectangle] (nodes) [fit=(x1) (x2) (y1) (y2) (y3) (i)] {}; - \node[draw=none, rectangle, anchor=south] (title) at (nodes.north) {Causal DAG}; - \end{scope} - - % Scenario - \begin{scope}[name prefix=scenario-, shift={($(estimand-estimand.south) + (0, -2)$)}] - \node[draw=none, rectangle] (title) at (0, 0) {Modelling Scenario}; - \node[anchor=north] (constraints) at (title.south) {$\{ x_1 < 5, x_2 = \text{``UK''} \}$}; - \end{scope} - \node[draw, rectangle] (scenario) [fit=(scenario-title) (scenario-constraints)] {}; - - % Data - \begin{scope}[name prefix=data-, local bounding box=test-data] - \node[draw=none, rectangle] (title) at (estimate-estimate |- dag-title) {Test Data}; - \node[anchor=north] (table) at (title.south) { - \begin{tabular}{rrrrrr} - \toprule - $X_1$ & $X_2$ & $I$ & $Y_1$ & $Y_2$ & $Y_3$ \\ - \midrule - 1.2 & ``UK'' & 0.3 & 7.8 & 4 & 100 \\ - 3.2 & ``UK'' & 0.1 & 7.6 & 8 & 95 \\ - \multicolumn{6}{c}{$\vdots$} \\ - \bottomrule - \end{tabular} - }; - \node[draw, rectangle] [fit=(title) (table)] {}; - \end{scope} - - % DAG outline - \node[draw, rectangle] (dag) [fit=(dag-nodes) (dag-title) (dag-title |- data-table.south)] {}; - - %Information flow - \draw[edge, dashed] (dag) -- (estimand-estimand.north); - \draw[edge, dashed] (test-test-case) -- (estimand-estimand); - - \draw[edge, dashed] (scenario.north) -- (estimand-estimand); - \draw[edge, dashed] (scenario.north) -- ([yshift=7.3mm]scenario.north) -- ([yshift=7.3mm]scenario.north -| test-test-case) -- (test-test-case); - \draw[edge, dashed] (scenario.north) -- ([yshift=7.3mm]scenario.north) -- ([yshift=7.3mm]scenario.north -| estimate-estimate) -- (estimate-estimate); - - \draw[edge, dashed] (data-test-data.south) -- (estimate-estimate.north); - \draw[edge, dashed] (estimand-estimand) -- (estimate-estimate); - - \draw[edge, dashed] (estimate-estimate) -- (oracle-test-oracle.west |- estimate-estimate); - \draw[edge, dashed] (oracle-test-oracle.east |- outcome-test-outcome) -- (outcome-test-outcome); -\end{tikzpicture} -\end{document} diff --git a/paper/paper.md b/paper/paper.md index 7781ec4f..c78bdfeb 100644 --- a/paper/paper.md +++ b/paper/paper.md @@ -69,7 +69,7 @@ The user may also refine tests to validate the nature of a particular relationsh Next, the user supplies a set of runtime data in the form of a table with each column representing a variable and rows containing the value of each variable for a particular run of the software. Finally, the CTF automatically validates the causal properties by using the causal DAG to identify a statistical estimand [@pearl2009causality] (essentially a set of features in the data which must be controlled for), calculate a causal effect estimate from the supplied data, and validating this against the expected causal relationship. -![Causal Testing workflow.\label{fig:schematic}](../images/schematic.png) +![Causal Testing workflow.\label{fig:schematic}](../docs/_static/images/schematic.png) ## Test Adequacy Because the properties being tested are completely separate from the data used to validate them, traditional coverage-based metrics are not appropriate here.