{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Calculating the Probabilities of Necessary and Sufficient Causation - Tian and Pearl (2000)\n", "\n", "Consider the causal effect of a voucher on customer conversion. We can distinguish between the following types of causation:\n", "\n", "* **Necessary**: If the customer doesn't get the voucher, they will not convert\n", "* **Sufficient**: If the customer gets the voucher, they will convert\n", "* **Necessary and sufficient**: The customer will convert if and only if they receive the voucher\n", "\n", "In general, we would like many intervetions to be of the last type. If the voucher is not necessary for a given customer, we might be wasting money by targeting them; if the voucher is not sufficient, we may not fulfil the goal of the campaign, which is to cause customers to convert.\n", "\n", "[Tian and Pearl (2000)](https://ftp.cs.ucla.edu/pub/stat_ser/r271-A.pdf) provided a way to combine experimental and observational data to derive bounds for the probability of each of the above types of causation. In this notebook, we replicate the example from their paper. " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "\n", "from causalml.optimize import get_pns_bounds" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Tian and Pearl (2000, p. 306)](https://ftp.cs.ucla.edu/pub/stat_ser/r271-A.pdf) imagine a setup where we have both experimental and observational data about the efficacy of a certain drug. The experimental data looks as follows:\n", "\n", "| | Treatment | Control |\n", "|-----------|-----------|---------|\n", "| Deaths | 16 | 14 |\n", "| Survivals | 984 | 986 |\n", "\n", "Therefore, based on the experiment, it looks like there isn't much of a difference in the rate of deaths in the treatment and control groups. However, in addition to the experimental data, we also have the following data that is from an observational study, i.e. a study in which we simply observe the outcomes for those who choose to use the drug vs. those who don't:\n", "\n", "| | Treatment | Control |\n", "|-----------|-----------|---------|\n", "| Deaths | 2 | 28 |\n", "| Survivals | 998 | 972 |\n", "\n", "Because people self-select to use the drug, the data shown in the table is very likely confounded. However, Tian and Pearl argue that the above two datasets can be combined to obtain information that is not visible by looking at either of the datasets independently, namely the probabilities of necessary and sufficient causation (PNS). More specifically, it is possible to derive bounds for PNS by combining the two data sources. To see how, let's generate the datasets:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "num_samples = 2000\n", "half = int(num_samples / 2)\n", "treatment = np.tile([0, 1], half)\n", "recovery = np.zeros(num_samples)\n", "\n", "df_rct = pd.DataFrame({'treatment': treatment, 'death': recovery})\n", "df_obs = pd.DataFrame({'treatment': treatment, 'death': recovery})" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# Set the label to `1' for 16 treatment and 14 control observations\n", "df_rct.loc[df_rct.loc[df_rct['treatment'] == 1].sample(n=16).index, 'death'] = 1\n", "df_rct.loc[df_rct.loc[df_rct['treatment'] == 0].sample(n=14).index, 'death'] = 1" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "treatment\n", "0 14.0\n", "1 16.0\n", "Name: death, dtype: float64" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_rct.groupby('treatment')['death'].sum()" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# Set the label to `1' for 2 treatment and 28 control observations\n", "df_obs.loc[df_obs.loc[df_obs['treatment'] == 1].sample(n=2).index, 'death'] = 1\n", "df_obs.loc[df_obs.loc[df_obs['treatment'] == 0].sample(n=28).index, 'death'] = 1" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "treatment\n", "0 28.0\n", "1 2.0\n", "Name: death, dtype: float64" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_obs.groupby('treatment')['death'].sum()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "WIth these data, we can now use the `get_pns_bounds()' function to calculate the relevant bounds. Let's do it for each of the three types of bound:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "pns_lb, pns_ub = get_pns_bounds(df_rct, df_obs, 'treatment', 'death', type='PNS')" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "pn_lb, pn_ub = get_pns_bounds(df_rct, df_obs, 'treatment', 'death', type='PN')" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "ps_lb, ps_ub = get_pns_bounds(df_rct, df_obs, 'treatment', 'death', type='PS')" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Bounds for the probability of necessary causation: [1.0, 1]\n", "Bounds for the probability of sufficient causation: [0.002, 0.031]\n", "Bounds for the probability of necessary and sufficient causation: [0.002, 0.016]\n", "\n" ] } ], "source": [ "print(f'''\n", "Bounds for the probability of necessary causation: [{round(pn_lb, 3)}, {round(pn_ub, 3)}]\n", "Bounds for the probability of sufficient causation: [{round(ps_lb, 3)}, {round(ps_ub, 3)}]\n", "Bounds for the probability of necessary and sufficient causation: [{round(pns_lb, 3)}, {round(pns_ub, 3)}]\n", "''')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, by combining experimental and observational data, we arrive at the conclusion that the participants who died and took the drug would have definitely survived without taking the drug. Those who survived and did not take the drug would have had between 0.2% and 3.1% risk of dying had they taken the drug. This illustrates how combining experimental and observational data can lead to additional insights compared to analysing either data source separately." ] } ], "metadata": { "interpreter": { "hash": "1b5c1e8782fc5f664c4fe135feb4dd5f062247c917b91ce86cc8a320dfc2f525" }, "kernelspec": { "display_name": "Python 3.10.1 64-bit ('acme': conda)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.5" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }