diff --git a/docs/source/example_notebooks/gcm_401k_analysis.ipynb b/docs/source/example_notebooks/gcm_401k_analysis.ipynb index 71fc884b08..98e169ff97 100644 --- a/docs/source/example_notebooks/gcm_401k_analysis.ipynb +++ b/docs/source/example_notebooks/gcm_401k_analysis.ipynb @@ -6,7 +6,7 @@ "source": [ "# Impact of 401(k) eligibility on net financial assets\n", "\n", - "In this case study, we will use real-world data from 401(k) analysis to explain how Causality library can be used to estimate average treatment effect (ATE) and conditional ATE (CATE)." + "In this case study, we will use real-world data from 401(k) analysis to explain how graphical causal models can be used to estimate average treatment effect (ATE) and conditional ATE (CATE)." ] }, { @@ -106,6 +106,9 @@ "edges.extend([(covariate, treatment_var) for covariate in covariates])\n", "edges.extend([(covariate, outcome_var) for covariate in covariates])\n", "\n", + "# To ensure that the treatment is considered as a categorical variable, we convert the values explicitly to strings.\n", + "df = df.astype({treatment_var: str})\n", + "\n", "causal_graph = nx.DiGraph(edges)" ] }, @@ -153,9 +156,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We observe that real-valued variables do not follow well-known parameteric distributions like Gaussian. Therefore, we fit empirical distributions whenever those variables do not have parents, which is also suitable for categorical variables. \n", + "We observe that real-valued variables do not follow well-known parametric distributions like Gaussian. Therefore, we fit empirical distributions whenever those variables do not have parents, which is also suitable for categorical variables. \n", "\n", - "Let's assign causal models to variables. For the treatment variable, we assign a classifier functional causal model (FCM) with a random forest classifier. For the outcome variable, we assign an additive noise model with a random forest regression as a function and empirical distribution for the noise. We assign empirical distributions to other variables as they do not have parents in the causal graph." + "Let’s assign the causal mechanisms to each node explicitly. For the treatment variable, we assign a classifier functional causal model (FCM) with a random forest classifier. For the outcome variable, we assign an additive noise model with random forest regression as a function and an empirical distribution for the noise. We assign empirical distributions to other variables as they do not have parents in the causal graph." ] }, { @@ -175,27 +178,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "To fit a classifier FCM, we cast the treatment column to string type." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df = df.astype({treatment_var: str})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "Instead of assigning the models manually, we can also automate this **if** we don't have prior knowledge or are not familiar with the statistical implications:\n", - " \n", - "> gcm.auto.assign_causal_mechanisms(causal_model, df)\n", - "
" + "> **If** we don't have prior knowledge or are not familiar with the statistical implications, we can also assign causal mechanisms automatically using a heuristic:\n", + "```\n", + "gcm.auto.assign_causal_mechanisms(causal_model, df)\n", + "```" ] }, { @@ -281,7 +267,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The average treatment effect of 401(k) eligibility on net financial assets is positive as indicated by the confidence interval $[4902.24, 8486.89]$. Now, let's plot CATEs of various income groups to get a clear picture." + "The average treatment effect of 401(k) eligibility on net financial assets is positive as indicated by the corresponding confidence interval. Now, let's plot CATEs of various income groups to get a clear picture." ] }, { @@ -327,7 +313,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.9.16" } }, "nbformat": 4, diff --git a/docs/source/example_notebooks/gcm_basic_example.ipynb b/docs/source/example_notebooks/gcm_basic_example.ipynb index 20e0248bfe..27e129aaa1 100644 --- a/docs/source/example_notebooks/gcm_basic_example.ipynb +++ b/docs/source/example_notebooks/gcm_basic_example.ipynb @@ -252,9 +252,7 @@ "id": "67cfaf23-1b90-4124-84cb-39ea67401a22", "metadata": {}, "source": [ - "
\n", - "Note, this evaluation take some significant time depending on the model complexities, graph size and amount of data. For a speed-up, consider changing the evaluation parameters.\n", - "
" + "> Note, this evaluation can take some significant time depending on the model complexities, graph size and amount of data. For a speed-up, consider changing the evaluation parameters." ] }, { @@ -264,9 +262,7 @@ "source": [ "## Step 3: Answering a causal query based on the SCM\n", "\n", - "The last step, answering a causal question, is our actual goal. E.g. we could ask the question:\n", - "\n", - "> What will happen to the variable Z if I intervene on Y?\n", + "The last step, answering a causal question, is our actual goal. E.g. we could ask the question \"What will happen to the variable Z if I intervene on Y?\".\n", "\n", "This can be done via the `interventional_samples` function. Here’s how:" ] @@ -305,9 +301,7 @@ "id": "d65e3319-58ea-4284-8810-c88b10d08448", "metadata": {}, "source": [ - "
\n", - "DoWhy offers a wide range of causal questions that can be answered with GCMs. See the user guide or other notebooks for more examples.\n", - "
" + "> DoWhy offers a wide range of causal questions that can be answered with GCMs. See the user guide or other notebooks for more examples." ] } ], diff --git a/docs/source/example_notebooks/gcm_draw_samples.ipynb b/docs/source/example_notebooks/gcm_draw_samples.ipynb index 6ee23ff563..9ae40ba5eb 100644 --- a/docs/source/example_notebooks/gcm_draw_samples.ipynb +++ b/docs/source/example_notebooks/gcm_draw_samples.ipynb @@ -138,9 +138,7 @@ "id": "f430827d-fc75-4c23-8d2d-3d98605dd927", "metadata": {}, "source": [ - "
\n", - "While the evaluation provides us insights toward the causal graph structure as well, we cannot confirm the graph structure, only reject it if we find inconsistencies between the dependencies of the observed structure and what the graph represents. In our case, we do not reject the DAG, but there are other equivalent DAGs that would not be rejected as well. To see this, consider the example above - X→Y→Z and X←Y←Z would generate the same observational distribution (since they encode the same conditionals), but only X→Y→Z would generate the correct interventional distribution (e.g., when intervening on Y).\n", - "
" + "> While the evaluation provides us insights toward the causal graph structure as well, we cannot confirm the graph structure, only reject it if we find inconsistencies between the dependencies of the observed structure and what the graph represents. In our case, we do not reject the DAG, but there are other equivalent DAGs that would not be rejected as well. To see this, consider the example above - X→Y→Z and X←Y←Z would generate the same observational distribution (since they encode the same conditionals), but only X→Y→Z would generate the correct interventional distribution (e.g., when intervening on Y)." ] } ], diff --git a/docs/source/example_notebooks/gcm_icc.ipynb b/docs/source/example_notebooks/gcm_icc.ipynb index d781d9adb3..a6357f003f 100644 --- a/docs/source/example_notebooks/gcm_icc.ipynb +++ b/docs/source/example_notebooks/gcm_icc.ipynb @@ -37,7 +37,7 @@ "id": "098c7ca0-9391-4033-b056-d09ac5365abf", "metadata": {}, "source": [ - "In the first example, we use the famous [MPG data set](https://archive.ics.uci.edu/dataset/9/auto+mpg), which contains different features that are used for the prediction of miles per gallon (mpg) of a car engine. The relationship between these features can be modeled as a graphical causal model. For this, we follow the causal graph defined in the [work by Wang et al.](https://ieeexplore.ieee.org/document/8585647) and remove all nodes that have no influence on MPG. This leaves us with the following graph:" + "In the first example, we use the famous [MPG data set](https://archive.ics.uci.edu/dataset/9/auto+mpg), which contains different features that are used for the prediction of miles per gallon (mpg) of a car engine. Let's say our task is to improve the design process where we need a good understanding of the influences of our variables on the mpg consumption. The relationship between these features can be modeled as a graphical causal model. For this, we follow the causal graph defined in the [work by Wang et al.](https://ieeexplore.ieee.org/document/8585647) and remove all nodes that have no influence on MPG. This leaves us with the following graph:" ] }, { @@ -120,12 +120,39 @@ "print(gcm.evaluate_causal_model(scm_mpg, auto_mpg_data, evaluate_invertibility_assumptions=False, evaluate_causal_structure=False))" ] }, + { + "cell_type": "markdown", + "id": "8b95b48a-a6ac-4d7c-84a6-718a73841aeb", + "metadata": {}, + "source": [ + "After defining our structural causal model, we can now obtain more insights into what factors influence fuel consumption. As a first insight, we can estimate the direct arrow strength of the connections weight -> mpg and horsepower -> mpg. Note that by default, the arrow strength method measures the influence with respect to the variance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6257a9e2-a1f4-4c16-b629-b5a13bbf32dc", + "metadata": {}, + "outputs": [], + "source": [ + "arrow_strengths_mpg = gcm.arrow_strength(scm_mpg, target_node='mpg')\n", + "gcm.util.plot(scm_mpg.graph, causal_strengths=arrow_strengths_mpg)" + ] + }, + { + "cell_type": "markdown", + "id": "42e62ed2-7e29-49ac-99cd-bca20d7565ca", + "metadata": {}, + "source": [ + "As we see here, the weight has a much higher influence on the variance in mpg than horsepower does." + ] + }, { "cell_type": "markdown", "id": "5258a976-dce8-4268-9fb9-2a03c03325a5", "metadata": {}, "source": [ - "After defining our structural causal model, we can now apply the ICC method to obtain more insights into what factors influence fuel consumption. This could help us improve the design process. Note that by default, we attribute the variance of the target node to the upstream nodes." + "While knowing how much the direct parents influence our node of interest provides some valuable insights, the weight and horsepower might only just inherit information from their common parent. To distinguish between the information inherited from the parent and their own contribution, we apply the ICC method:" ] }, { @@ -173,7 +200,7 @@ "id": "1606ebc4-7223-48c0-b92d-5fb13761d81b", "metadata": {}, "source": [ - "It turns out that the number of cylinders already explains a large fraction of the fuel consumption and the intermediate nodes like displacement, horsepower, and weight mostly inherit uncertainty from their parents. This is because, although weight and horsepower are the more direct predictors of mpg, they are mostly determined by displacement and cylinders. As we also see with the contribution of mpg itself, roughly 1/4 of the variance of mpg remains unexplained by all of the above factors, which may be partially due to model inaccuracies.\n", + "It turns out that the number of cylinders already explains a large fraction of the fuel consumption and the intermediate nodes like displacement, horsepower, and weight mostly inherit uncertainty from their parents. This is because, although weight and horsepower are the more direct predictors of mpg, they are mostly determined by displacement and cylinders. This gives some useful insights for potential optimizations. As we also see with the contribution of mpg itself, roughly 1/4 of the variance of mpg remains unexplained by all of the above factors, which may be partially due to model inaccuracies.\n", "\n", "While the model evaluation showed that there are some inaccuracies with respect to the KL divergence between the generated and observed distributions, we see that ICC still provides non-trivial results in the sense that the contributions differ significantly across nodes and that not everything is simply attributed to the target node itself." ] @@ -183,9 +210,7 @@ "id": "043ab28d-6732-4fa8-8c59-8a592f2058be", "metadata": {}, "source": [ - "
\n", - "Note that estimating the contribution to the variance of the target in ICC can be seen as a nonlinear version of ANOVA that incorporates the causal structure.\n", - "
" + "> Note that estimating the contribution to the variance of the target in ICC can be seen as a nonlinear version of ANOVA that incorporates the causal structure." ] }, { @@ -201,18 +226,15 @@ "id": "5fa50502-152a-4d77-985c-b4ae51426e96", "metadata": {}, "source": [ - "In the next example, we look at different recordings taken of the river flows ($m^3/s$) at a 15 minute frequency across 5 different measuring stations in England at Henthorn, New Jumbles Rock, Hodder Place, Whalley Weir and Samlesbury. The data is taken from the [UK Department for Environment Food & Rural Affairs website](https://environment.data.gov.uk/hydrology/explore). Here is a map of the rivers:" + "In the next example, we look at different recordings taken of the river flows ($m^3/s$) at a 15 minute frequency across 5 different measuring stations in England at Henthorn, New Jumbles Rock, Hodder Place, Whalley Weir and Samlesbury. Here, obtaining a better understanding of how the river flows behave can help to plan potential mitigation steps to avoid overflows. The data is taken from the [UK Department for Environment Food & Rural Affairs website](https://environment.data.gov.uk/hydrology/explore). Here is a map of the rivers:" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "c773d712-0806-4fc9-9739-62f2749406e2", + "cell_type": "markdown", + "id": "68bfc4ad-94e9-43ff-84bb-5fa1c82a06c4", "metadata": {}, - "outputs": [], "source": [ - "from IPython.display import Image\n", - "Image('river-map.jpg') " + "" ] }, { @@ -243,17 +265,15 @@ "id": "38ea910f-b7f3-4923-9fc9-b40bb3a7f2b6", "metadata": {}, "source": [ - "Here, we are interested in the causal influence of the upstream rivers on the Samlesbury river. For instance, to obtain a better understanding of how the river flows behave and to potentially plan mitigation steps to avoid overflows. Similar to the example before, we would expect these nodes to be heavily confounded by, e.g., the weather. That is, the true graph is more likely to be along the lines of:" + "In this setting, we are interested in the causal influence of the upstream rivers on the Samlesbury river. Similar to the example before, we would expect these nodes to be heavily confounded by, e.g., the weather. That is, the true graph is more likely to be along the lines of:" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "88cd1c6e-d8c7-48e6-bc37-7b5280f39d57", + "cell_type": "markdown", + "id": "c0a0441a-929f-40f7-a4ed-5601846959da", "metadata": {}, - "outputs": [], "source": [ - "Image('river-confounded.png') " + "" ] }, { @@ -261,7 +281,7 @@ "id": "b94dea14-9058-406d-8466-eb066815c095", "metadata": {}, "source": [ - "Nevertheless, we still expect the ICC algorithm to provide some insights into the contribution to the river flow of Samlesbury, even with the hidden confounder in place." + "Nevertheless, we still expect the ICC algorithm to provide some insights into the contribution to the river flow of Samlesbury, even with the hidden confounder in place:" ] }, { diff --git a/docs/source/example_notebooks/gcm_online_shop.ipynb b/docs/source/example_notebooks/gcm_online_shop.ipynb index ff46385f16..34362006d1 100644 --- a/docs/source/example_notebooks/gcm_online_shop.ipynb +++ b/docs/source/example_notebooks/gcm_online_shop.ipynb @@ -5,7 +5,7 @@ "id": "1c74ae7a-e77a-4b38-be41-7fb82e6930a5", "metadata": {}, "source": [ - "# Causal attribution and root-cause analysis of an online shop" + "# Causal Attributions and Root-Cause Analysis in an Online Shop" ] }, { @@ -274,7 +274,7 @@ "metadata": {}, "outputs": [], "source": [ - "print(gcm.evaluate_causal_model(scm, data_2021, compare_mechanism_baselines=True, evaluate_invertibility_assumptions=False, evaluate_causal_structure=False))" + "print(gcm.evaluate_causal_model(scm, data_2021, compare_mechanism_baselines=True, evaluate_invertibility_assumptions=False))" ] }, { @@ -282,7 +282,7 @@ "id": "b65c73b6-28af-4b57-b05f-c6c6245a714a", "metadata": {}, "source": [ - "The fitted causal mechanisms are fairly good representations of the data generation process, with some minor inaccuracies. However, this is to be expected given the small sample size and relatively small signal-to-noise ratio for many nodes. Most importantly, all the baseline mechanisms did not perform better, which is a good indicator that our model selection is appropriate. Note that even if the models have some inaccuracies, most of the GCM-based algorithms are generally robust against this." + "The fitted causal mechanisms are fairly good representations of the data generation process, with some minor inaccuracies. However, this is to be expected given the small sample size and relatively small signal-to-noise ratio for many nodes. Most importantly, all the baseline mechanisms did not perform better, which is a good indicator that our model selection is appropriate. Based on the evaluation, we also do not reject the given causal graph." ] }, { @@ -290,9 +290,7 @@ "id": "a2e5acb0-f40e-4478-bee8-db3ff58bea21", "metadata": {}, "source": [ - "
\n", - "The selection of baseline models change be configured as well. For more details, take a look at the corresponding evaluate_causal_model documentation.\n", - "
" + "> The selection of baseline models or the p-value for graph falsification can be configured as well. For more details, take a look at the corresponding evaluate_causal_model documentation." ] }, { @@ -301,6 +299,34 @@ "metadata": {}, "source": [ "## Step 3: Answer causal questions\n", + "### Generate new samples\n", + "\n", + "Since we learned about the data generation process, we can also generate new samples:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74ed17eb-45ec-444d-9364-2aa850911a05", + "metadata": {}, + "outputs": [], + "source": [ + "gcm.draw_samples(scm, num_samples=10)" + ] + }, + { + "cell_type": "markdown", + "id": "8dfc67f4-f89c-480d-8f6a-b0a74651ef3f", + "metadata": {}, + "source": [ + "We have drawn 10 samples from the joint distribution following the learned causal relationships." + ] + }, + { + "cell_type": "markdown", + "id": "9a10e5ff-c5aa-4386-b462-bd696a1a54ed", + "metadata": {}, + "source": [ "### What are the key factors influencing the variance in profit?" ] }, @@ -553,9 +579,7 @@ "id": "c2c82783-9a69-43c3-820c-048a6f5860bf", "metadata": {}, "source": [ - "
\n", - "Note that the results differ depending on the selected data, since they are sample specific. On other days, other factors could be relevant. Furthermore, note that the analysis (including the confidence intervals) always relies on the modeling assumptions made. In other words, if the models change or have a poor fit, one would also expect different results.\n", - "
" + "> Note that the results differ depending on the selected data, since they are sample specific. On other days, other factors could be relevant. Furthermore, note that the analysis (including the confidence intervals) always relies on the modeling assumptions made. In other words, if the models change or have a poor fit, one would also expect different results." ] }, { diff --git a/docs/source/example_notebooks/gcm_rca_microservice_architecture.ipynb b/docs/source/example_notebooks/gcm_rca_microservice_architecture.ipynb index 59df41010f..36d3451556 100644 --- a/docs/source/example_notebooks/gcm_rca_microservice_architecture.ipynb +++ b/docs/source/example_notebooks/gcm_rca_microservice_architecture.ipynb @@ -13,13 +13,10 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "from IPython.display import Image\n", - "Image('microservice-architecture-dependencies.png', width=500) " + "" ] }, { @@ -125,9 +122,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "
\n", - "Here, we are interested in the causal relationships between latencies of services rather than the order of calling the services.\n", - "
" + "> Here, we are interested in the causal relationships between latencies of services rather than the order of calling the services." ] }, { @@ -158,11 +153,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "
\n", - "Alternatively, we can also automate this **if** we don't have prior knowledge or are not familiar with the statistical implications:\n", - " \n", - "> gcm.auto.assign_causal_mechanisms(causal_model, normal_data)\n", - "
" + "> Alternatively, we can also automate this **if** we don't have prior knowledge or are not familiar with the statistical implications:\n", + "```\n", + "gcm.auto.assign_causal_mechanisms(causal_model, normal_data)\n", + "```" ] }, { @@ -193,9 +187,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "
\n", - "For more detailed insights, set compare_mechanism_baselines to True. However, this will take significantly longer.\n", - "
" + "> For more detailed insights, set compare_mechanism_baselines to True. However, this will take significantly longer." ] }, { @@ -214,7 +206,10 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -233,7 +228,10 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -260,7 +258,10 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -279,10 +280,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "
\n", - "By default, a quantile-based anomaly score is used that estimates the negative log-probability of a sample being\n", - "normal. This is, the higher the probabilty of an outlier, the larger the score. The library offers different kinds of outlier scoring functions, such as the z-score, where the mean is the expected value based on the causal model.
\n", - "\n", + "> By default, a quantile-based anomaly score is used that estimates the negative log-probability of a sample being\n", + "normal. This is, the higher the probabilty of an outlier, the larger the score. The library offers different kinds of outlier scoring functions, such as the z-score, where the mean is the expected value based on the causal model." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "Let us visualize the attributions along with their uncertainty in a bar plot." ] }, @@ -290,7 +295,10 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -518,13 +526,16 @@ "metadata": {}, "source": [ "This simulates the latency relationships under the assumption of having synchronized services and that there are no\n", - "hidden aspects that impact two nodes at the same time. Furthermore, we assume that the Caching Service has to call through to the Product DB only in 50% of the cases (i.e., we have a 50% cache miss rate). Also, we assume that the Product Service can make calls in parallel to its downstream services Shipping Cost Service, Caching Service, and Customer DB and join the threads when all three service have returned.\n", - "\n", - "
\n", - "We use truncated exponential and\n", + "hidden aspects that impact two nodes at the same time. Furthermore, we assume that the Caching Service has to call through to the Product DB only in 50% of the cases (i.e., we have a 50% cache miss rate). Also, we assume that the Product Service can make calls in parallel to its downstream services Shipping Cost Service, Caching Service, and Customer DB and join the threads when all three service have returned." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> We use truncated exponential and\n", "half-normal distributions,\n", - "since their shapes are similar to distributions observed in real services.\n", - "
" + "since their shapes are similar to distributions observed in real services." ] }, { diff --git a/docs/source/example_notebooks/gcm_supply_chain_dist_change.ipynb b/docs/source/example_notebooks/gcm_supply_chain_dist_change.ipynb index af9f454e79..0dda4a3358 100644 --- a/docs/source/example_notebooks/gcm_supply_chain_dist_change.ipynb +++ b/docs/source/example_notebooks/gcm_supply_chain_dist_change.ipynb @@ -147,7 +147,7 @@ "source": [ "### Causal Attribution Analysis\n", "\n", - "We consider the distribution-change-attribution method based on graphical causal models described in [Budhathoki et al., 2021](https://arxiv.org/abs/2102.13384), which is also implmented in *Causality*. In summary, given the underlying causal graph of variables, the attribution method attributes the change in the marginal distribution of a target variable (or its summary, such as its mean) to changes in data-generating processes (also called \"causal mechanisms\") of variables upstream in the causal graph. A causal mechanism of a variable is the conditional distribution of the variable given its *direct causes*. We can also think of a causal mechanism as an algorithm (or a compute program) in the system that takes the values of direct causes as input and produces the value of the effect as an output. To use the attribution method, we first require the causal graph of the variables, namely *demand*, *constraint*, *submitted*, *confirmed* and *received*." + "We consider the distribution-change-attribution method based on graphical causal models described in [Budhathoki et al., 2021](https://arxiv.org/abs/2102.13384), which is also implmented in DoWhy. In summary, given the underlying causal graph of variables, the attribution method attributes the change in the marginal distribution of a target variable (or its summary, such as its mean) to changes in data-generating processes (also called \"causal mechanisms\") of variables upstream in the causal graph. A causal mechanism of a variable is the conditional distribution of the variable given its *direct causes*. We can also think of a causal mechanism as an algorithm (or a compute program) in the system that takes the values of direct causes as input and produces the value of the effect as an output. To use the attribution method, we first require the causal graph of the variables, namely *demand*, *constraint*, *submitted*, *confirmed* and *received*." ] }, { @@ -279,11 +279,17 @@ " data_week2, \n", " 'received', \n", " num_samples=2000,\n", - " difference_estimation_func=lambda x1, x2 : np.mean(x2) - np.mean(x1))\n", - "\n", - "plt.bar(contributions.keys(), contributions.values())\n", - "plt.ylabel('Contribution')\n", - "plt.show()" + " difference_estimation_func=lambda x1, x2 : np.mean(x2) - np.mean(x1))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from dowhy.utils import bar_plot\n", + "bar_plot(contributions, ylabel='Contribution')" ] }, { @@ -304,17 +310,21 @@ " causal_model,\n", " data_week1, \n", " data_week2, \n", - " 'received', \n", + " 'received',\n", " num_samples=2000,\n", " difference_estimation_func=lambda x1, x2 : np.mean(x2) - np.mean(x1)), \n", " confidence_level=0.95, \n", - " num_bootstrap_resamples=10)\n", - "\n", - "yerr_plus = [uncertainty_contribs[node][1] - median_contribs[node] for node in median_contribs.keys()]\n", - "yerr_minus = [median_contribs[node] - uncertainty_contribs[node][0] for node in median_contribs.keys()]\n", - "plt.bar(median_contribs.keys(), median_contribs.values(), yerr=np.array([yerr_minus, yerr_plus]), ecolor='black')\n", - "plt.ylabel('Contribution')\n", - "plt.show()" + " num_bootstrap_resamples=5,\n", + " n_jobs=-1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "bar_plot(median_contribs, ylabel='Contribution', uncertainties=uncertainty_contribs)" ] }, { diff --git a/docs/source/getting_started/index.rst b/docs/source/getting_started/index.rst index 82921326f2..b971b50363 100644 --- a/docs/source/getting_started/index.rst +++ b/docs/source/getting_started/index.rst @@ -74,9 +74,9 @@ using the same API, refer to :doc:`../example_notebooks/dowhy-conditional-treatm Graphical causal model-based inference --------------------------------------- -For features like root cause analysis, structural analysis and similar, DoWhy uses graphical causal models. The -language of graphical causal models again offers a variety of causal questions that can be answered. DoWhy's API to -answer these causal questions follows a simple 3-step recipe as follows: +For features like root cause analysis, point-wise counterfactual inference, structural analysis and similar, DoWhy uses +graphical causal models. The language of graphical causal models again offers a variety of causal questions that can +be answered. DoWhy's API to answer these causal questions follows a simple 3-step recipe as follows: .. code:: python @@ -96,21 +96,19 @@ answer these causal questions follows a simple 3-step recipe as follows: # Step 2: Train our causal model with the data from above: gcm.fit(causal_model, data) - # Step 3: Perform a causal analysis. E.g. we have an: - anomalous_record = pd.DataFrame(dict(X=[.7], Y=[100.0], Z=[303.0])) + # Step 3: Perform a causal analysis. For instance, root cause analysis, where we observe + anomalous_sample = pd.DataFrame(dict(X=[0.1], Y=[6.2], Z=[19])) # Here, Y is the root cause. # ... and would like to answer the question: # "Which node is the root cause of the anomaly in Z?": - anomaly_attribution = gcm.attribute_anomalies(causal_model, "Z", anomalous_record) + anomaly_attribution = gcm.attribute_anomalies(causal_model, "Z", anomalous_sample) -If you want to learn more about this, we recommend starting with -:doc:`../user_guide/modeling_gcm/index` in +If you want to learn more about this and other GCM features, we recommend starting with :doc:`../user_guide/modeling_gcm/index` in the user guide or check out :doc:`../example_notebooks/gcm_basic_example`. - Further resources ^^^^^^^^^^^^^^^^^ -There's further resources available: +There are further resources available: - An introductory `tutorial on causal inference `_ - A comprehensive @@ -121,5 +119,6 @@ There's further resources available: `Foundations of causal inference and its impacts on machine learning `_ - The PDF book `Elements of Causal Inference `_ - Draft chapters of an upcoming book: `Causal reasoning: Fundamentals and machine learning applications `_ +- A blog post describing one of DoWhy's root cause analysis algorithms via graphical causal models: `New method identifies the root causes of statistical outliers `_ diff --git a/docs/source/index.rst b/docs/source/index.rst index 1f536f8d54..f4db3af13c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -20,10 +20,11 @@ DoWhy documentation **Related resources**: `Source Repository `__ | `Issues & Ideas `__ | -`Join the Community (Discord) `__ | `PyWhy Organization `__ | `DoWhy on PyPI `__ | +**Join our Community on Discord:** ``__ + Much like machine learning libraries have done for prediction, DoWhy is a Python library that aims to spark causal thinking and analysis. DoWhy provides a wide variety of algorithms for effect estimation, causal structure learning, diagnosis of causal structures, root cause analysis, interventions and counterfactuals. @@ -72,3 +73,43 @@ structure learning, diagnosis of causal structures, root cause analysis, interve ^^^ The API reference contains a detailed description of the functions, modules, and objects included in DoWhy. It assumes that you have an understanding of the key concepts. + + +Key differences compared to available causal inference software +---------------------------------------------------------------- +DoWhy brings four key differences compared to available software for causal inference: + +**Explicit identifying assumptions** + Assumptions are first-class citizens in DoWhy. + + Each analysis starts by building a causal model. The assumptions can be viewed graphically or in terms + of conditional independence statements. Further, in the case of GCMs, the data generation process of each node is + modeled explicitly. Wherever possible, DoWhy can also automatically test stated assumptions using observed data. + +**Separation between identification and estimation** + Identification is the causal problem. Estimation is simply a statistical problem. + + DoWhy respects this boundary and treats them separately. This focuses the causal + inference effort on identification and frees up estimation using any + available statistical estimator for a target estimand. In addition, multiple + estimation methods can be used for a single identified estimand and + vice-versa. The same goes for modeling causal mechanisms, where any third-party machine learning package can be used for + modeling the functional relationships. + +**Automated validation of assumptions** + What happens when key identifying assumptions may not be satisfied? + + The most critical, and often skipped, part of causal analysis is checking whether the made assumptions about the + causal relationships hold. DoWhy makes it easy to automatically run sensitivity and robustness checks on the + obtained estimate, to falsify the given causal graph, or to evaluate fitted causal mechanisms. + +**Default parameters for simple application of complex algorithms** + Selecting the right set of variables or models is a hard problem. + + DoWhy aims to select appropriate parameters by default while allowing users to fully customize + each function call and model specification. For instance, DoWhy automatically selects the most appropriate identification + method or offers functionalities to automatically assign appropriate causal mechanisms. + +Finally, DoWhy is easily extensible with a particular focus on supporting other libraries, such as EconML, CausalML, +scikit-learn and more. Algorithms are implemented in a modular way, encouraging users to contribute their own or to +simply plug in different customized models. \ No newline at end of file diff --git a/docs/source/user_guide/causal_tasks/estimating_causal_effects/direct_indirect_effects/index.rst b/docs/source/user_guide/causal_tasks/estimating_causal_effects/direct_indirect_effects/index.rst deleted file mode 100644 index 7dd38bcaf6..0000000000 --- a/docs/source/user_guide/causal_tasks/estimating_causal_effects/direct_indirect_effects/index.rst +++ /dev/null @@ -1,12 +0,0 @@ -Estimating direct and indirect effects -====================================== - -In addition to average *total* causal effect, DoWhy can be used to estimate direct and indirect effects. -More generally, these tasks form a part of mediation analysis. - -.. toctree:: - :maxdepth: 1 - - mediation_analysis - quantify_arrow_strength - diff --git a/docs/source/user_guide/causal_tasks/estimating_causal_effects/direct_indirect_effects/mediation_analysis.rst b/docs/source/user_guide/causal_tasks/estimating_causal_effects/direct_indirect_effects/mediation_analysis.rst deleted file mode 100644 index cf3697fadc..0000000000 --- a/docs/source/user_guide/causal_tasks/estimating_causal_effects/direct_indirect_effects/mediation_analysis.rst +++ /dev/null @@ -1,48 +0,0 @@ -Estimating natural direct and indirect effects -============================================== - -We use the estimand_type argument to specify that the target estimand should be for a natural direct effect or the natural indirect effect. For definitions, see `Interpretation and Identification of Causal Mediation `_ by Judea Pearl. - -**Natural direct effect**: Effect due to the path v0->y - -**Natural indirect effect**: Effect due to the path v0->FD0->y (mediated by FD0). - -Identification --------------- - ->>> # Natural direct effect (nde) ->>> identified_estimand_nde = model.identify_effect(estimand_type="nonparametric-nde", ->>> proceed_when_unidentifiable=True) ->>> print(identified_estimand_nde) - ->>> # Natural indirect effect (nie) ->>> identified_estimand_nie = model.identify_effect(estimand_type="nonparametric-nie", ->>> proceed_when_unidentifiable=True) ->>> print(identified_estimand_nie) - -Estimation ----------- - ->>> import dowhy.causal_estimators.linear_regression_estimator ->>> causal_estimate_nie = model.estimate_effect(identified_estimand_nie, ->>> method_name="mediation.two_stage_regression", ->>> confidence_intervals=False, ->>> test_significance=False, ->>> method_params = { ->>> 'first_stage_model': dowhy.causal_estimators.linear_regression_estimator.LinearRegressionEstimator, ->>> 'second_stage_model': dowhy.causal_estimators.linear_regression_estimator.LinearRegressionEstimator ->>> } ->>> ) ->>> print(causal_estimate_nie) - ->>> causal_estimate_nde = model.estimate_effect(identified_estimand_nde, ->>> method_name="mediation.two_stage_regression", ->>> confidence_intervals=False, ->>> test_significance=False, ->>> method_params = { ->>> 'first_stage_model': dowhy.causal_estimators.linear_regression_estimator.LinearRegressionEstimator, ->>> 'second_stage_model': dowhy.causal_estimators.linear_regression_estimator.LinearRegressionEstimator ->>> } ->>> ) ->>> print(causal_estimate_nde) - diff --git a/docs/source/user_guide/causal_tasks/estimating_causal_effects/effect_estimation_with_gcm.rst b/docs/source/user_guide/causal_tasks/estimating_causal_effects/effect_estimation_with_gcm.rst index d6e1aba6d1..358c4b9168 100644 --- a/docs/source/user_guide/causal_tasks/estimating_causal_effects/effect_estimation_with_gcm.rst +++ b/docs/source/user_guide/causal_tasks/estimating_causal_effects/effect_estimation_with_gcm.rst @@ -67,6 +67,12 @@ observational data instead of generating new ones: >>> observed_data=data) 1.4990885925844586 +Related example notebooks +^^^^^^^^^^^^^^^^^^^^^^^^^ + +- :doc:`../../../example_notebooks/gcm_basic_example` +- :doc:`../../../example_notebooks/gcm_401k_analysis` + Understanding the method ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/user_guide/causal_tasks/estimating_causal_effects/index.rst b/docs/source/user_guide/causal_tasks/estimating_causal_effects/index.rst index cc32c6d7ae..536bca3767 100644 --- a/docs/source/user_guide/causal_tasks/estimating_causal_effects/index.rst +++ b/docs/source/user_guide/causal_tasks/estimating_causal_effects/index.rst @@ -28,12 +28,11 @@ Using these verbs, DoWhy implements a causal effect estimation API that supports * Frontdoor * Instrumental variable * ID algorithm -* Mediation (direct and indirect effects) Once a causal effect is identified, we can choose an estimation method compatible with the identification strategy. For estimating the average causal effect, DoWhy supports the following methods. * Methods based on matching confounders' values: - * Distance-based matching + * Distance-based matching * Methods based on estimating the treatment assignment * Propensity-based Stratification @@ -52,9 +51,9 @@ Once a causal effect is identified, we can choose an estimation method compatibl * Two-stage linear regression -For estimating the conditional average causal effect, DoWhy supports calling EconML methods. For more details on EconML, check out their `documentation `_. If the data-generating process for the outcome Y can be approximated as a linear function, you may also use the linear regression method for CACE estimation. - +For estimating the conditional average causal effect, DoWhy supports calling EconML methods. For more details on EconML, check out their `documentation `_. If the data-generating process for the outcome Y can be approximated as a linear function, you may also use the linear regression method for CACE estimation. +For related notebooks, see :doc:`../../../example_notebooks/nb_index` .. toctree:: :maxdepth: 1 @@ -64,4 +63,3 @@ For estimating the conditional average causal effect, DoWhy supports calling Eco effect_estimation_with_natural_experiments/index conditional_effect_estimation/index effect_estimation_with_gcm - direct_indirect_effects/index diff --git a/docs/source/user_guide/causal_tasks/index.rst b/docs/source/user_guide/causal_tasks/index.rst index a49960b387..42afb71907 100644 --- a/docs/source/user_guide/causal_tasks/index.rst +++ b/docs/source/user_guide/causal_tasks/index.rst @@ -13,6 +13,7 @@ This chapter describes how to perform each of these tasks. :maxdepth: 2 estimating_causal_effects/index + quantify_causal_influence/index root_causing_and_explaining/index what_if/index causal_prediction/index diff --git a/docs/source/user_guide/causal_tasks/estimating_causal_effects/direct_indirect_effects/arrow_strength_example.png b/docs/source/user_guide/causal_tasks/quantify_causal_influence/arrow_strength_example.png similarity index 100% rename from docs/source/user_guide/causal_tasks/estimating_causal_effects/direct_indirect_effects/arrow_strength_example.png rename to docs/source/user_guide/causal_tasks/quantify_causal_influence/arrow_strength_example.png diff --git a/docs/source/user_guide/causal_tasks/root_causing_and_explaining/icc.rst b/docs/source/user_guide/causal_tasks/quantify_causal_influence/icc.rst similarity index 96% rename from docs/source/user_guide/causal_tasks/root_causing_and_explaining/icc.rst rename to docs/source/user_guide/causal_tasks/quantify_causal_influence/icc.rst index 4f0d8814f5..c47e1cf061 100644 --- a/docs/source/user_guide/causal_tasks/root_causing_and_explaining/icc.rst +++ b/docs/source/user_guide/causal_tasks/quantify_causal_influence/icc.rst @@ -39,7 +39,7 @@ To see how the method works, let us generate some data following the example abo Note the larger standard deviation of the 'noise' in :math:`X`. -Next, we will model cause-effect relationships as a structural causal model and fit it to the data. Here, we are using +Next, we will model cause-effect relationships as a structural causal model using DoWhy's GCM framework and fit it to the data. Here, we are using the auto module to automatically assign causal mechanisms: >>> causal_model = gcm.StructuralCausalModel(nx.DiGraph([('X', 'Y'), ('Y', 'Z')])) # X -> Y -> Z @@ -58,6 +58,13 @@ Note that, although we use a linear relationship here, the method can also handl :math:`Z`, including itself, to its variance (the default measure). These contributions sum up to the variance of :math:`Z`. As we see here, we observe that ~92% of the variance of :math:`Z` comes from :math:`X`. +Related example notebooks +^^^^^^^^^^^^^^^^^^^^^^^^^ + +- :doc:`../../../example_notebooks/gcm_icc` +- :doc:`../../../example_notebooks/gcm_online_shop` + + .. _understand-method-icc: Understanding the method @@ -119,7 +126,7 @@ Here, we explicitly defined the variance in the parameter ``attribution_func`` a in other quantities, such as absolute deviations. This can also be simply computed by replacing the ``attribution_func`` with a custom function: - >>> mean_absolute_deviation_estimator = lambda x: np.mean(abs(x)) + >>> mean_absolute_deviation_estimator = lambda x, y: np.mean(abs(x-y)) >>> node_to_contribution = gcm.intrinsic_causal_influence(causal_model, 'Z', >>> prediction_model_from_noises_to_target, >>> attribution_func=mean_absolute_deviation_estimator) diff --git a/docs/source/user_guide/causal_tasks/quantify_causal_influence/index.rst b/docs/source/user_guide/causal_tasks/quantify_causal_influence/index.rst new file mode 100644 index 0000000000..4ba5e99e0a --- /dev/null +++ b/docs/source/user_guide/causal_tasks/quantify_causal_influence/index.rst @@ -0,0 +1,31 @@ +Quantify Causal Influence +========================= + +In addition to estimating the average *total* causal effect, DoWhy can also be used for mediation analysis, for +estimating the direct arrow strength between two nodes and for estimating the intrinsic causal influence of nodes in +a causal graph. + +The main differences between these methods are: + +**Mediation Analysis:** Mediation analysis primarily aims to decompose the total effect of a treatment on an outcome +into direct and indirect effects through one or more mediator variables. The focus is on understanding how much of the +effect is transmitted directly from the treatment to the outcome and how much is mediated through other variables. + +**Direct Arrow Strength:** This method quantifies the causal influence of one variable on another by measuring the +change in the distribution when an edge in a graph is removed. It uses a specific measure for estimating the change, +such as the difference in variance or the relative entropy when removing an edge. This provides an understanding of how the +removal of a specific causal link affects the target variable and, by this, provides a single value representing the +strength of a specific causal link that is well defined for nonlinear relationships. + +**Intrinsic Causal Contribution:** This method focuses on estimating the intrinsic contribution of a node within a +graph, independent of the influences inherited from its ancestors. It involves representing each node as a function of +upstream noise terms and employs structure-preserving interventions to measure the influence of the noise terms of each +node. By this, this method separates the inherent information added by each node from that obtained from its ancestors. +The information added can be quantified by measures like (conditional) variance or entropy etc. + +.. toctree:: + :maxdepth: 1 + + mediation_analysis + quantify_arrow_strength + icc diff --git a/docs/source/user_guide/causal_tasks/quantify_causal_influence/mediation_analysis.rst b/docs/source/user_guide/causal_tasks/quantify_causal_influence/mediation_analysis.rst new file mode 100644 index 0000000000..76bda54bf5 --- /dev/null +++ b/docs/source/user_guide/causal_tasks/quantify_causal_influence/mediation_analysis.rst @@ -0,0 +1,53 @@ +Mediation Analysis: Estimating natural direct and indirect effects +================================================================== + +Mediation analysis can be used to quantify the extent to which a causal influence is exerted through a specific pathway. DoWhy supports the estimation of the *natural direct effect* and the *natural indirect effect*: + +**Natural direct effect**: Effect due to the path v0->y +**Natural indirect effect**: Effect due to the path v0->FD0->y (mediated by FD0). + +For more details, see `Interpretation and Identification of Causal Mediation `_ by Judea Pearl. + +Using DoWhy's effect estimation framework, we can perform a mediation analysis by adjusting the estimand_type argument accordingly: + +Identification +-------------- + +>>> # Natural direct effect (nde) +>>> identified_estimand_nde = model.identify_effect(estimand_type="nonparametric-nde", +>>> proceed_when_unidentifiable=True) +>>> print(identified_estimand_nde) + +>>> # Natural indirect effect (nie) +>>> identified_estimand_nie = model.identify_effect(estimand_type="nonparametric-nie", +>>> proceed_when_unidentifiable=True) +>>> print(identified_estimand_nie) + +Estimation +---------- + +>>> import dowhy.causal_estimators.linear_regression_estimator +>>> causal_estimate_nie = model.estimate_effect(identified_estimand_nie, +>>> method_name="mediation.two_stage_regression", +>>> confidence_intervals=False, +>>> test_significance=False, +>>> method_params = { +>>> 'first_stage_model': dowhy.causal_estimators.linear_regression_estimator.LinearRegressionEstimator, +>>> 'second_stage_model': dowhy.causal_estimators.linear_regression_estimator.LinearRegressionEstimator +>>> }) +>>> print(causal_estimate_nie) + +>>> causal_estimate_nde = model.estimate_effect(identified_estimand_nde, +>>> method_name="mediation.two_stage_regression", +>>> confidence_intervals=False, +>>> test_significance=False, +>>> method_params = { +>>> 'first_stage_model': dowhy.causal_estimators.linear_regression_estimator.LinearRegressionEstimator, +>>> 'second_stage_model': dowhy.causal_estimators.linear_regression_estimator.LinearRegressionEstimator +>>> }) +>>> print(causal_estimate_nde) + +Related example notebooks +^^^^^^^^^^^^^^^^^^^^^^^^^ + +- :doc:`../../../example_notebooks/dowhy_mediation_analysis` \ No newline at end of file diff --git a/docs/source/user_guide/causal_tasks/estimating_causal_effects/direct_indirect_effects/quantify_arrow_strength.rst b/docs/source/user_guide/causal_tasks/quantify_causal_influence/quantify_arrow_strength.rst similarity index 92% rename from docs/source/user_guide/causal_tasks/estimating_causal_effects/direct_indirect_effects/quantify_arrow_strength.rst rename to docs/source/user_guide/causal_tasks/quantify_causal_influence/quantify_arrow_strength.rst index 3c27bcbf44..09567d4e28 100644 --- a/docs/source/user_guide/causal_tasks/estimating_causal_effects/direct_indirect_effects/quantify_arrow_strength.rst +++ b/docs/source/user_guide/causal_tasks/quantify_causal_influence/quantify_arrow_strength.rst @@ -26,12 +26,10 @@ To see how to use the method, let us generate some data. >>> Y = 3*X + 4*Z + np.random.normal(loc=0, scale=1, size=1000) >>> data = pd.DataFrame(dict(X=X, Y=Y, Z=Z)) -Next, we will model cause-effect relationships as a probabilistic causal model and fit it to the data. +Next, we will model cause-effect relationships as a probabilistic causal model using DoWhy's GCM framework and fit it to the data. >>> causal_model = gcm.ProbabilisticCausalModel(nx.DiGraph([('Z', 'Y'), ('Z', 'X'), ('X', 'Y')])) ->>> causal_model.set_causal_mechanism('Z', gcm.EmpiricalDistribution()) ->>> causal_model.set_causal_mechanism('X', gcm.AdditiveNoiseModel(gcm.ml.create_linear_regressor())) ->>> causal_model.set_causal_mechanism('Y', gcm.AdditiveNoiseModel(gcm.ml.create_linear_regressor())) +>>> gcm.auto.assign_causal_mechanisms(causal_model, data) >>> gcm.fit(causal_model, data) Finally, we can estimate the strength of incoming arrows to a node of interest (e.g., :math:`Y`). @@ -49,9 +47,17 @@ than the direct influence from :math:`Z` to :math:`Y` (~14.73). Roughly speaking from :math:`X` to :math:`Y` increases the variance of :math:`Y` by ~41.32 units, whereas removing :math:`Z \to Y` increases the variance of :math:`Y` by ~14.73 units. -In the next section, we explain what "removing" an edge implies. +In the Section :ref:`Understanding the method `, we explain what "removing" an edge implies. In particular, we briefly explain the science behind our method for quantifying the strength of an arrow. +Related example notebooks +^^^^^^^^^^^^^^^^^^^^^^^^^ + +- :doc:`../../../example_notebooks/gcm_online_shop` +- :doc:`../../../example_notebooks/gcm_icc` + +.. _understand-method-arrow-strength: + Understanding the method ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/user_guide/causal_tasks/root_causing_and_explaining/anomaly_attribution.rst b/docs/source/user_guide/causal_tasks/root_causing_and_explaining/anomaly_attribution.rst index 726a65bd76..ca1193b089 100644 --- a/docs/source/user_guide/causal_tasks/root_causing_and_explaining/anomaly_attribution.rst +++ b/docs/source/user_guide/causal_tasks/root_causing_and_explaining/anomaly_attribution.rst @@ -53,10 +53,16 @@ There could also be multiple root causes. **Interpretation of results:** We estimated the contribution of the ancestors of :math:`W`, including :math:`W` itself, to the observed anomaly. While all nodes contribute to some extent, :math:`Y` is the standout. Note that :math:`Z` is also anomalous due to :math:`Y`, but it merely inherits the high value from :math:`Y`. The attribution method is able identify -this and distinguishes between the contributions of :math:`Y` and :math:`Z`. +this and distinguishes between the contributions of :math:`Y` and :math:`Z`. In case of a negative contribution, the +corresponding node even decreases the likelihood of an anomaly, i.e., reducing its apparent severity. For a detailed interpretation of the score, see the referenced paper. The following section also offers some intuition. +Related example notebooks +^^^^^^^^^^^^^^^^^^^^^^^^^ + +- :doc:`../../../example_notebooks/gcm_rca_microservice_architecture` +- :doc:`../../../example_notebooks/gcm_online_shop` Understanding the method ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/user_guide/causal_tasks/root_causing_and_explaining/distribution_change.rst b/docs/source/user_guide/causal_tasks/root_causing_and_explaining/distribution_change.rst index d2c03b6438..73159e5f63 100644 --- a/docs/source/user_guide/causal_tasks/root_causing_and_explaining/distribution_change.rst +++ b/docs/source/user_guide/causal_tasks/root_causing_and_explaining/distribution_change.rst @@ -61,10 +61,17 @@ reason is, that this function will call ``fit`` internally. To be precise, this make two copies of the causal graph and fit one graph to the first dataset and the second graph to the second dataset. +Related example notebooks +^^^^^^^^^^^^^^^^^^^^^^^^^ + +- :doc:`../../../example_notebooks/gcm_rca_microservice_architecture` +- :doc:`../../../example_notebooks/gcm_online_shop` +- :doc:`../../../example_notebooks/gcm_supply_chain_dist_change` + + Understanding the method ^^^^^^^^^^^^^^^^^^^^^^^^ - The idea behind this method is to *systematically* replace the causal mechanism learned based on the old dataset with the mechanism learned based on the new dataset. After each replacement, new samples are generated for the target node, where the data generation process is a mixture of old and new mechanisms. Our goal is to identify the mechanisms that diff --git a/docs/source/user_guide/causal_tasks/root_causing_and_explaining/index.rst b/docs/source/user_guide/causal_tasks/root_causing_and_explaining/index.rst index 9b6008843b..de081a9f24 100644 --- a/docs/source/user_guide/causal_tasks/root_causing_and_explaining/index.rst +++ b/docs/source/user_guide/causal_tasks/root_causing_and_explaining/index.rst @@ -1,11 +1,30 @@ -Explaining Observed Effects and Root-Cause Analysis -=================================================== +Root-Cause Analysis and Explanation +=================================== + +DoWhy's graphical causal model framework offers powerful tools for root cause analysis and explanation of observed +effects by making use of the decomposition of nodes in a causal graph into single causal mechanisms. + +**Attribute Anomalies:** Given anomalous observations (i.e., outliers), we can attribute them to the nodes that have +caused them. The idea here is to ask the counterfactual question "If node x had behaved differently, would we +still have observed the anomaly?". The change in our target node quantifies how much a node contributes to the +observed anomaly. To ensure a fair attribution, this needs to be estimated systematically for different combinations +of changes in nodes. + +**Distribution Change Attribution:** Given two data sets where the distribution has changed, we attribute the changes to +the nodes in the graph that have caused the changes. Here, we first identify the data generating mechanisms of nodes that +have changed and then attribute changes in distribution (e.g., with respect to a change in the variance or in KL divergence) +to these mechanisms. + +**Feature Relevance:** Here, we address the question of how relevant a feature is for a model. Popular packages like +SHAP address this question by defining a certain set function and estimating the Shapley value for each input feature. +In our case, we do something similar, but also incorporate the probabilistic nature of a causal mechanism. That is, +we also incorporate the noise that influences a variable. By this, we can compute the relevance of inputs, but can also +incorporate the unexplainable part represented by the noise. Furthermore, the Shapley value estimator in DoWhy offers a +flexible way to define a customized set function. .. toctree:: :maxdepth: 1 anomaly_attribution distribution_change - icc - unit_change feature_relevance diff --git a/docs/source/user_guide/causal_tasks/root_causing_and_explaining/unit_change.rst b/docs/source/user_guide/causal_tasks/root_causing_and_explaining/unit_change.rst deleted file mode 100644 index f511caa963..0000000000 --- a/docs/source/user_guide/causal_tasks/root_causing_and_explaining/unit_change.rst +++ /dev/null @@ -1,3 +0,0 @@ -Unit Change Attribution -======================== -TBD \ No newline at end of file diff --git a/docs/source/user_guide/causal_tasks/what_if/counterfactuals.rst b/docs/source/user_guide/causal_tasks/what_if/counterfactuals.rst index e1e5b30ff1..03e0f2231e 100644 --- a/docs/source/user_guide/causal_tasks/what_if/counterfactuals.rst +++ b/docs/source/user_guide/causal_tasks/what_if/counterfactuals.rst @@ -24,9 +24,9 @@ To see how we can use this method, let's generate some data: >>> import networkx as nx, numpy as np, pandas as pd >>> from dowhy import gcm ->>> X = np.random.uniform(low=0, high=10, size=1000) ->>> Y = -2*X + np.random.normal(loc=0, scale=5, size=1000) ->>> Z = 3*Y + 80 + np.random.normal(loc=0, scale=5, size=1000) +>>> X = np.random.uniform(low=0, high=10, size=2000) +>>> Y = -2*X + np.random.normal(loc=0, scale=5, size=2000) +>>> Z = 3*Y + 80 + np.random.normal(loc=0, scale=5, size=2000) >>> training_data = pd.DataFrame(data=dict(X=X, Y=Y, Z=Z)) If we think of the cholesterol example, one could think of :math:`Y` here as some kind of body measurement that affects @@ -36,9 +36,7 @@ Next, we'll model cause-effect relationships and fit the models to the data. Est invertible SCM that can be represented by additive noise models: >>> causal_model = gcm.InvertibleStructuralCausalModel(nx.DiGraph([('X', 'Y'), ('Y', 'Z')])) # X -> Y -> Z ->>> causal_model.set_causal_mechanism('X', gcm.EmpiricalDistribution()) ->>> causal_model.set_causal_mechanism('Y', gcm.AdditiveNoiseModel(gcm.ml.create_linear_regressor())) ->>> causal_model.set_causal_mechanism('Z', gcm.AdditiveNoiseModel(gcm.ml.create_linear_regressor())) +>>> gcm.auto.assign_causal_mechanisms(causal_model, training_data) >>> gcm.fit(causal_model, training_data) @@ -78,6 +76,11 @@ perfectly. Specifically, the learned coefficients for :math:`Y` and :math:`Z` ar as suggested by the data generation process. As usual in machine learning, more data or fine-tuning models can help to improve accuracy. +Related example notebooks +^^^^^^^^^^^^^^^^^^^^^^^^^ + +- :doc:`../../../example_notebooks/gcm_counterfactual_medical_dry_eyes` + .. _understand-method-counter: Understanding the method diff --git a/docs/source/user_guide/causal_tasks/what_if/index.rst b/docs/source/user_guide/causal_tasks/what_if/index.rst index 414944bed5..0a87ced154 100644 --- a/docs/source/user_guide/causal_tasks/what_if/index.rst +++ b/docs/source/user_guide/causal_tasks/what_if/index.rst @@ -1,6 +1,9 @@ Asking and Answering What-If Questions ====================================== +Using graphical causal models, it's possible to tackle both rung 2 (interventions) and rung 3 (counterfactuals) queries +as defined in Pearl's ladder of causation. + .. toctree:: :maxdepth: 1 diff --git a/docs/source/user_guide/causal_tasks/what_if/interventions.rst b/docs/source/user_guide/causal_tasks/what_if/interventions.rst index a1bf1086f2..132b53fcb7 100644 --- a/docs/source/user_guide/causal_tasks/what_if/interventions.rst +++ b/docs/source/user_guide/causal_tasks/what_if/interventions.rst @@ -23,9 +23,7 @@ Next, we'll model cause-effect relationships as a probabilistic causal model and >>> from dowhy import gcm >>> causal_model = gcm.ProbabilisticCausalModel(nx.DiGraph([('X', 'Y'), ('Y', 'Z')])) # X -> Y -> Z ->>> causal_model.set_causal_mechanism('X', gcm.EmpiricalDistribution()) ->>> causal_model.set_causal_mechanism('Y', gcm.AdditiveNoiseModel(gcm.ml.create_linear_regressor())) ->>> causal_model.set_causal_mechanism('Z', gcm.AdditiveNoiseModel(gcm.ml.create_linear_regressor())) +>>> gcm.auto.assign_causal_mechanisms(causal_model, training_data) >>> gcm.fit(causal_model, training_data) @@ -55,3 +53,10 @@ shift interventions where we shift the random variable X by some value: 2 1.340949 1.910316 5.882468 3 1.837919 4.360685 12.565738 4 3.791410 8.361918 25.477725 + +Related example notebooks +^^^^^^^^^^^^^^^^^^^^^^^^^ + +- :doc:`../../../example_notebooks/gcm_basic_example` +- :doc:`../../../example_notebooks/gcm_401k_analysis` +- :doc:`../../../example_notebooks/gcm_rca_microservice_architecture` \ No newline at end of file diff --git a/docs/source/user_guide/intro.rst b/docs/source/user_guide/intro.rst index c6c3907758..95049301e7 100644 --- a/docs/source/user_guide/intro.rst +++ b/docs/source/user_guide/intro.rst @@ -9,18 +9,16 @@ DoWhy supports estimation of the average causal effect for backdoor, frontdoor, Supported causal tasks ---------------------- -DoWhy's API is organized around the different *causal tasks* that it enables a user to perform. We categorize tasks into :doc:`causal_tasks/estimating_causal_effects/index`, :doc:`causal_tasks/causal_prediction/index`, -:doc:`causal_tasks/root_causing_and_explaining/index`, and :doc:`causal_tasks/what_if/index`. +DoWhy's API is organized around the different *causal tasks* that it enables a user to perform. We categorize tasks into :doc:`causal_tasks/estimating_causal_effects/index`, :doc:`causal_tasks/quantify_causal_influence/index`, +:doc:`causal_tasks/root_causing_and_explaining/index`, :doc:`causal_tasks/what_if/index`, and :doc:`causal_tasks/causal_prediction/index`. These tasks give answers to questions, such as "If I change the color of my button to red, how much will this change users’ purchase decisions?", or "Which service in my distributed system has caused the frontend to be slower than usual?". To perform tasks, DoWhy leverages two powerful frameworks, namely graphical causal models (GCM) and potential outcomes (PO), depending on the task at hand. What’s common to most tasks is that they require a *causal graph*, which is modeled after the -problem domain. For that reason, this user guide starts with :doc:`modeling_causal_relations/index`. Readers interested in -:doc:`causal_tasks/root_causing_and_explaining/unit_change` or :doc:`causal_tasks/root_causing_and_explaining/feature_relevance`, -which do not necessarily require a causal graph, can skip right to those sections. +problem domain. For that reason, this user guide starts with :doc:`modeling_causal_relations/index`. -To aid the navigation within this user guide and the library further, the following flow chart can be used: +See the following chart for a quick overview of some DoWhy's features covered in this user-guide: .. image:: navigation.png :alt: Visual navigation map to aid the user in navigating the user guide @@ -30,11 +28,9 @@ Testing validity of a causal analysis ------------------------------------- Since causal tasks concern an interventional data distribution that is often not observed, we need special ways to evaluate the validity of a causal estimate. Methods like cross-validation from predictive machine learning do not work, unless we have access to samples from the interventional distribution. Therefore, for each causal task, a important part of the analysis is to test whether the obtained answer is valid. In DoWhy, we call this process *refutation*, which involves refuting or challenging the assumptions made by a causal analysis. Refutations are performed at two stages: after modeling the causal graph, and after completing the analysis for a task. -In the first stage, graph refutations test whether the assumptions encoded in a given causal graph are valid. This is an important set of refutations since all downstream analysis depends on the graph. These refutations are typically task-agnostic and we recommend running them to improve the quality of the assumed graph. DoWhy's functionality for refuting a causal graph is described in :doc:`modeling_causal_relations/refuting_causal_graph/index`. The second kind of refutations, estimate refutations, are conducted after the task analysis returns a causal estimate. These refutations test whether the analysis follows best practices, provides the correct answer under special test data, and how robust the final estimate is to violation of assumptions. Estimate refutations can help improve the robustness of an analysis or help choose between multiple candidate models in the analysis. We discuss estimate refutations in a separate chapter, :doc:`refuting_causal_estimates/index`. +In the first stage, graph refutations test whether the assumptions encoded in a given causal graph are valid. This is an important set of refutations since all downstream analysis depends on the graph. These refutations are typically task-agnostic and we recommend running them to improve the quality of the assumed graph. DoWhy's functionality for refuting a causal graph is described in :doc:`modeling_causal_relations/refuting_causal_graph/index` (also see `Falsification of User-Given Directed Acyclic Graphs <../example_notebooks/gcm_falsify_dag.html>`_ for an alternative approach). For effect estimation problems, the second kind of refutations, estimate refutations, are conducted after the task analysis returns a causal estimate. These refutations test whether the analysis follows best practices, provides the correct answer under special test data, and how robust the final estimate is to violation of assumptions. Estimate refutations can help improve the robustness of an analysis or help choose between multiple candidate models in the analysis. We discuss estimate refutations in a separate chapter, :doc:`refuting_causal_estimates/index`. -For an alternative approach of validating a given causal graph, see `Falsification of User-Given Directed Acyclic Graphs <../example_notebooks/gcm_falsify_dag.html>`_. - -For evaluating a graphical causal model, see :doc:`modeling_gcm/model_evaluation`. +In the case of graphical causal models, one can additionally falsify certain assumptions about the underlying causal mechanisms and evaluate their performance on test data. For more details see :doc:`modeling_gcm/model_evaluation`. Who this user guide is for -------------------------- @@ -48,6 +44,6 @@ and for implementing and evaluating causal inference methods on benchmark datase `Infant Health and Development Program (IHDP) `_ dataset, `Infant Mortality (Twins) `_ dataset, and the `Lalonde Jobs `_ dataset. -For an introductory example of root-cause analysis, check out the `Root Cause Analysis in a Microservice Architecture notebook `_. -For a full list of example notebooks, see `Example notebooks `_. +For an introductory example of using graphical causal models for root cause analysis, check out the `Root Cause Analysis in a Microservice Architecture notebook `_ or the `Causal Attributions and Root-Cause Analysis in an Online Shop notebook `_. +For a full list of example notebooks, see :doc:`../example_notebooks/nb_index`. diff --git a/docs/source/user_guide/modeling_causal_relations/refuting_causal_graph/refuting_invertible_model.rst b/docs/source/user_guide/modeling_causal_relations/refuting_causal_graph/refuting_invertible_model.rst deleted file mode 100644 index 68ef71c32f..0000000000 --- a/docs/source/user_guide/modeling_causal_relations/refuting_causal_graph/refuting_invertible_model.rst +++ /dev/null @@ -1,4 +0,0 @@ -Refuting Invertible Model (GCM only) -==================================== - -TBD diff --git a/docs/source/user_guide/modeling_gcm/customizing_model_assignment.rst b/docs/source/user_guide/modeling_gcm/customizing_model_assignment.rst index 65b98a80d5..926cb4d053 100644 --- a/docs/source/user_guide/modeling_gcm/customizing_model_assignment.rst +++ b/docs/source/user_guide/modeling_gcm/customizing_model_assignment.rst @@ -23,8 +23,8 @@ Non-root nodes such as :math:`Y` are modelled using a *conditional* stochastic m defines corresponding interfaces for both, namely :class:`~dowhy.gcm.causal_mechanisms.StochasticModel` and :class:`~dowhy.gcm.causal_mechanisms.ConditionalStochasticModel`. -The gcm package also provides ready-to-use implementations, such as :class:`~dowhy.gcm.stochastic_models -.ScipyDistribution` or :class:`~dowhy.gcm.stochastic_models.BayesianGaussianMixtureDistribution` for +The gcm package also provides ready-to-use implementations, such as :class:`~dowhy.gcm.stochastic_models.ScipyDistribution` +or :class:`~dowhy.gcm.stochastic_models.BayesianGaussianMixtureDistribution` for :class:`~dowhy.gcm.causal_mechanisms.StochasticModel`, and :class:`~dowhy.gcm.causal_mechanisms.AdditiveNoiseModel` for :class:`~dowhy.gcm.causal_mechanisms.ConditionalStochasticModel`. @@ -44,9 +44,9 @@ For the non-root node Y, let's use an additive noise model (ANM), represented by structural assignment of the form: :math:`Y := f(X) + N`. Here, f is a deterministic prediction function, whereas N is a noise term. Let's put all of this together: ->>> causal_model.set_causal_mechanism('Y', gcm.AdditiveNoiseModel( ->>> prediction_model=gcm.ml.create_linear_regressor(), ->>> noise_model=gcm.ScipyDistribution(norm))) +>>> causal_model.set_causal_mechanism('Y', +>>> gcm.AdditiveNoiseModel(prediction_model=gcm.ml.create_linear_regressor(), +>>> noise_model=gcm.ScipyDistribution(norm))) The rather interesting part here is the ``prediction_model``, which corresponds to our function :math:`f` above. This prediction model must satisfy the contract defined by @@ -74,6 +74,15 @@ Finally, we can learn the parameters of those causal models from the training da ``causal_model`` is now ready to be used for various types of causal queries as explained in :doc:`../causal_tasks/index`. +.. note:: + + As mentioned above, DoWhy has a wrapper class that supports scikit learn models out of the box. For instance + + >>> from sklearn.ensemble import RandomForestRegressor + >>> causal_model.set_causal_mechanism('Y', gcm.AdditiveNoiseModel(gcm.ml.SklearnRegressionModel(RandomForestRegressor))) + + would use a RandomForestRegressor instead of a LinearRegressor from the sklearn package. + Using ground truth models ------------------------- @@ -102,7 +111,6 @@ implements the :class:`~dowhy.gcm.ml.PredictionModel` interface: Now we can use this in our ANMs instead: >>> causal_model.set_causal_mechanism('Y', gcm.AdditiveNoiseModel(MyCustomModel(2))) ->>> >>> gcm.fit(causal_model, data) .. note:: diff --git a/docs/source/user_guide/modeling_gcm/draw_samples.rst b/docs/source/user_guide/modeling_gcm/draw_samples.rst index 4f0e2521ce..3a6f048b21 100644 --- a/docs/source/user_guide/modeling_gcm/draw_samples.rst +++ b/docs/source/user_guide/modeling_gcm/draw_samples.rst @@ -24,8 +24,8 @@ Lets take a look at the following example: 3 -1.540165 -3.196612 -9.028178 4 1.218309 3.867429 12.394407 -Similar as in the introduction, we generate data for the simple linear DAG X→Y→Z. Lets define the GCM and fit it to the -data: +We now learned the generative models of the variables, based on the defined causal graph and the additive noise model assumption. +To generate new samples from this model, we can now simply call: >>> import networkx as nx >>> import dowhy.gcm as gcm @@ -46,17 +46,48 @@ To generate new samples from this model, we can now simply call: 3 -0.817718 -1.125724 -3.013189 4 0.520793 -0.081344 0.987426 -If our modeling assumptions are correct, the generated data should now resemble the observed data distribution, i.e. the -generated samples correspond to the joint distribution we defined for our example data at the beginning. A quick -way to make sure of this is to estimate the KL-divergence between observed and generated distribution: +If our modeling assumptions are correct, the generated data should now resemble the observed data distribution, i.e. +the generated samples correspond to the joint distribution we defined for our example data at the beginning. One way +to make sure of this is to estimate the KL-divergence between observed and generated distribution. For this, we can +make use of the evaluation module: + +>>> print(gcm.evaluate_causal_model(causal_model, data, evaluate_causal_mechanisms=False, evaluate_invertibility_assumptions=False)) + +.. image:: graph_evaluation.png + :alt: Causal Graph Falsification + +.. code-block:: + + Evaluated and the overall average KL divergence between generated and observed distribution and graph structure. The results are as follows: + + ==== Evaluation of Generated Distribution ==== + The overall average KL divergence between the generated and observed distribution is 0.014769479715506385 + The estimated KL divergence indicates an overall very good representation of the data distribution. + + ==== Evaluation of the Causal Graph Structure ==== + +-------------------------------------------------------------------------------------------------------+ + | Falsificaton Summary | + +-------------------------------------------------------------------------------------------------------+ + | The given DAG is not informative because 2 / 6 of the permutations lie in the Markov | + | equivalence class of the given DAG (p-value: 0.33). | + | The given DAG violates 0/1 LMCs and is better than 66.7% of the permuted DAGs (p-value: 0.33). | + | Based on the provided significance level (0.2) and because the DAG is not informative, | + | we do not reject the DAG. | + +-------------------------------------------------------------------------------------------------------+ + + ==== NOTE ==== + Always double check the made model assumptions with respect to the graph structure and choice of causal mechanisms. + All these evaluations give some insight into the goodness of the causal model, but should not be overinterpreted, since some causal relationships can be intrinsically hard to model. Furthermore, many algorithms are fairly robust against misspecifications or poor performances of causal mechanisms. + +This confirms that the generated distribution is close to the observed one. ->>> gcm.divergence.auto_estimate_kl_divergence(data.to_numpy(), generated_data.to_numpy()) -0 +.. note:: -Here, we expect the divergence to be (very) small. + While the evaluation provides us insights toward the causal graph structure as well, we cannot confirm the + graph structure, only reject it if we find inconsistencies between the dependencies of the observed structure and what + the graph represents. In our case, we do not reject the DAG, but there are other equivalent DAGs that would not be + rejected as well. To see this, consider the example above - X→Y→Z and X←Y←Z would generate the same observational + distribution (since they encode the same conditionals), but only X→Y→Z would generate the correct interventional + distribution (e.g., when intervening on Y). -**Note**: We **cannot** validate the correctness of a causal graph this way, -since any graph from a Markov equivalence class would be sufficient to generate data that is consistent with the observed one, -but only one particular graph would generate the correct interventional and counterfactual distributions. This is, seeing the example above, -X→Y→Z and X←Y←Z would generate the same observational distribution (since they encode the same conditionals), but only X→Y→Z would generate the -correct interventional distribution (e.g. when intervening on Y). \ No newline at end of file +The next section provides more details about the evaluation method. \ No newline at end of file diff --git a/docs/source/user_guide/modeling_gcm/graphical_causal_model_types.rst b/docs/source/user_guide/modeling_gcm/graphical_causal_model_types.rst index fc8690210d..43f7a0f2b5 100644 --- a/docs/source/user_guide/modeling_gcm/graphical_causal_model_types.rst +++ b/docs/source/user_guide/modeling_gcm/graphical_causal_model_types.rst @@ -41,5 +41,25 @@ additive noise model (ANM) of the form :math:`X_i = f_i(PA_i, N_i) = f'_i(PA_i) standard regression models. The noise then becomes simply the residual. For causal questions like treatment effect estimation, modeling rung 2 is sufficient. On the other hand, to determine -root causes by asking "Would the target have been an outlier if node X behaved differently?", rung 3 is required. Note -that DoWhy generally uses invertible FCMs, such as ANMs, when models are assigned automatically. \ No newline at end of file +root causes by asking "Would the target have been an outlier if node X behaved differently?", rung 3 is required. The +following provides an overview of available types of causal mechanisms that are supported out-of-the box: + +Available Causal Mechanisms +--------------------------- + +To support causal mechanisms that can be used for any of the types of causal models above, we have simple interfaces +that a causal mechanism needs to implement. For instance, to use a causal mechanism in a PCM, one only needs to +implement a method that expects samples from the parent nodes and returns samples from the conditional distribution. +Having these lightweight interfaces allows easily using your own and customized types of causal mechanisms for different +use cases. However, DoWhy also has different types of mechanisms, specifically functional causal models, implemented +natively supporting different types of data: + +- `Additive Noise Models `_ (continuous data) of the form :math:`X_i = f_i(PA_i) + N_i`, where :math:`f_i` can be any kind of regression function (e.g., from scikit-learn) and the noise :math:`N_i` is unobserved noise. When fitting an ANM, this then boils down to fitting the :math:`f_i` model (e.g., via least squares) and fitting :math:`N_i` based on the residuals :math:`N_i = X_i - f_i(PA_i)`. As mentioned throughout the user guide, ANMs are the most commonly used types of causal models due to their simplicity and ability to answer counterfactual questions. +- `Post-nonlinear models `_ (continuous data) of the form :math:`X_i = g_i(f_i(PA_i) + N_i)`, where :math:`g_i` is assumed to be invertible. These are a generalization of ANMs, allowing more complex relationships between :math:`f_i` and :math:`PA_i`. +- `Discrete Additive Noise Models `_ (discrete data), which have a similar definition as ANMs but are restricted to discrete values. +- `Classifier-based Functional Causal Models `_ (categorical data) of the form :math:`X_i = f_i(PA_i, N_i)`, which cannot be used for rung 3 queries, since :math:`f_i` is typically not invertible here with respect to :math:`N_i`, but can be used for algorithms relying only on interventional queries (rung 2). Here, :math:`f_i` can be based on any classification model (e.g., from scikit-learn) and :math:`N_i` is by definition a uniform distribution on [0, 1] used to sample from the conditional class probabilities. + +In all mechanisms, causal sufficiency is assumed, i.e., :math:`N_i` is assumed to be independent of :math:`PA_i`. More +details and justification of these types of causal mechanisms can be found in the correspondingly linked papers. Note +that when using the auto assignment function, DoWhy tries to use invertible FCMs, such as ANMs, due to their flexibility +in addressing rung 3 queries. For categorical data, make sure to represent them as strings. \ No newline at end of file diff --git a/docs/source/user_guide/modeling_gcm/index.rst b/docs/source/user_guide/modeling_gcm/index.rst index 293660ee6c..810739b600 100644 --- a/docs/source/user_guide/modeling_gcm/index.rst +++ b/docs/source/user_guide/modeling_gcm/index.rst @@ -42,7 +42,8 @@ to answer causal questions. To introduce this data-generating process, we use an SCM that’s built on top of our causal graph: >>> from dowhy import gcm ->>> causal_model = gcm.StructuralCausalModel(causal_graph) +>>> import networkx as nx +>>> causal_model = gcm.StructuralCausalModel(nx.DiGraph([("X", "Y"), ("Y", "Z")])) At this point we would normally load our dataset. For this introduction, we generate some synthetic data instead. The API takes data in form of Pandas DataFrames: @@ -99,13 +100,24 @@ The causal model is now ready to be used for :doc:`../causal_tasks/index`. Evaluating a fitted SCM ----------------------- -For evaluating the fitted model, see :doc:`modeling_gcm/model_evaluation`. +For evaluating the fitted model, see :doc:`model_evaluation`. -Other related GCM topics ------------------------- +Related example notebooks +^^^^^^^^^^^^^^^^^^^^^^^^^ + +- :doc:`../../example_notebooks/gcm_basic_example` +- :doc:`../../example_notebooks/gcm_draw_samples` +- :doc:`../../example_notebooks/gcm_rca_microservice_architecture` +- :doc:`../../example_notebooks/gcm_online_shop` +- :doc:`../../example_notebooks/gcm_401k_analysis` +- :doc:`../../example_notebooks/gcm_counterfactual_medical_dry_eyes` +- :doc:`../../example_notebooks/gcm_supply_chain_dist_change` +- :doc:`../../example_notebooks/gcm_icc` + + +Other topics +------------ -- :doc:`A basic example <../../example_notebooks/gcm_basic_example>` -- `A blog post illustrating the usage of DoWhy's GCM features `_ .. toctree:: :maxdepth: 1 diff --git a/docs/source/user_guide/modeling_gcm/model_evaluation.rst b/docs/source/user_guide/modeling_gcm/model_evaluation.rst index 58c742c1d1..060aabc477 100644 --- a/docs/source/user_guide/modeling_gcm/model_evaluation.rst +++ b/docs/source/user_guide/modeling_gcm/model_evaluation.rst @@ -111,7 +111,7 @@ the chain structure example X→Y→Z again: For more insights toward the quality of the fitted graphical causal model, consider using the evaluate_causal_model function after fitting the causal mechanisms. In this scenario, an empirical distribution is assigned to the root node X, while additive noise models are used for -nodes Y and Z. In both of these cases, a linear regression model demonstrated the best performance in terms +nodes Y and Z (see :doc:`graphical_causal_model_types` for more details about the causal mechanism types). In both of these cases, a linear regression model demonstrated the best performance in terms of minimizing the mean squared error. A list of evaluated models and their performance is also available. Since we used the default parameter for the auto assignment, only a small model zoo is evaluated. However, we can also adjust the assigment quality to extend it to more models. diff --git a/docs/source/user_guide/navigation.png b/docs/source/user_guide/navigation.png index 6beb94e0b3..f7bfa7091f 100644 Binary files a/docs/source/user_guide/navigation.png and b/docs/source/user_guide/navigation.png differ diff --git a/docs/source/user_guide/navigation.pptx b/docs/source/user_guide/navigation.pptx index f7f5e21bf1..4679139711 100644 Binary files a/docs/source/user_guide/navigation.pptx and b/docs/source/user_guide/navigation.pptx differ diff --git a/docs/source/user_guide/navigation_alternative.png b/docs/source/user_guide/navigation_alternative.png deleted file mode 100644 index ad1eb3b45d..0000000000 Binary files a/docs/source/user_guide/navigation_alternative.png and /dev/null differ diff --git a/docs/source/user_guide/navigation_alternative.pptx b/docs/source/user_guide/navigation_alternative.pptx deleted file mode 100644 index af312cba96..0000000000 Binary files a/docs/source/user_guide/navigation_alternative.pptx and /dev/null differ diff --git a/dowhy/gcm/influence.py b/dowhy/gcm/influence.py index 710231e1ed..b8988e5e8a 100644 --- a/dowhy/gcm/influence.py +++ b/dowhy/gcm/influence.py @@ -244,8 +244,7 @@ def intrinsic_causal_influence( that generated samples follow the fitted models. In contrast, the 'approx' method involves selecting and training a suitable model based on data sampled from the graph. This might lead to deviations from the outcomes of the fitted models, but is faster and can be more - robust in certain settings. A more detailed treatment on why we need this parameter is - also provided in :ref:`icc`. + robust in certain settings. :param attribution_func: Optional attribution function to measure the statistical property of the target node. This function expects two inputs; predictions after the randomization of certain features (i.e. samples from noise nodes) and a baseline where no features were randomized. The baseline @@ -353,8 +352,7 @@ def intrinsic_causal_influence_sample( that generated samples follow the fitted models. In contrast, the 'approx' method involves selecting and training a suitable model based on data sampled from the graph. This might lead to deviations from the outcomes of the fitted models, but is faster and can be more - robust in certain settings. A more detailed treatment on why we need this parameter is - also provided in :ref:`icc`. + robust in certain settings. :param subset_scoring_func: Set function for estimating the quantity of interest based. This function expects two inputs; the outcome of the model for some samples if certain features are permuted and the outcome of the model for the same samples when no features were permuted. By default, diff --git a/dowhy/gcm/model_evaluation.py b/dowhy/gcm/model_evaluation.py index 1a1d62612f..83eedb9c18 100644 --- a/dowhy/gcm/model_evaluation.py +++ b/dowhy/gcm/model_evaluation.py @@ -69,6 +69,7 @@ def __init__( conditional_independence_test_falsify: Callable[[np.ndarray, np.ndarray, np.ndarray], float] = partial( kernel_based, use_bootstrap=False, max_num_samples_run=500 ), + falsify_graph_significance_level: float = 0.2, n_jobs: Optional[int] = None, ): """Parameters for the causal model evaluation method. See the parameter description for more details. @@ -94,6 +95,10 @@ def __init__( :param conditional_independence_test_falsify: A method for testing the independence between two variables given a third one used for falsifying the given graph structure. Note that the variables can be multivariate. + :param falsify_graph_significance_level: Significance level for rejecting the given graph based on the + permutation tests. The default of 0.2 here is higher than the usual + 0.05. Consider reducing it to be more strict about falsifying the + graph. :param n_jobs: Number of parallel jobs. Whenever the evaluation method supports parallelization, this parameter is used. """ @@ -132,6 +137,7 @@ def __init__( self.max_num_permutations_falsify = max_num_permutations_falsify self.independence_test_falsify = independence_test_falsify self.conditional_independence_test_falsify = conditional_independence_test_falsify + self.falsify_graph_significance_level = falsify_graph_significance_level self.n_jobs = n_jobs @@ -400,6 +406,7 @@ def evaluate_causal_model( n_permutations=config.max_num_permutations_falsify, independence_test=config.independence_test_falsify, conditional_independence_test=config.conditional_independence_test_falsify, + significance_level=config.falsify_graph_significance_level, n_jobs=config.n_jobs, ) @@ -775,7 +782,7 @@ def _get_crps_interpretation_string(crps: float) -> str: return "The estimated CRPS indicates a good model performance." elif 0.35 <= crps < 0.65: return ( - "The estimated CRPS indicates a fair model performance. " + "The estimated CRPS indicates only a fair model performance. " "Note, however, that a high CRPS could also result from a small signal to noise ratio." ) elif 0.65 <= crps < 0.9: