├── .gitignore ├── 1.2 - Simpsons Paradox.ipynb ├── 1.3 - Probability and Statistics.ipynb ├── 1.4 - Graphs.ipynb ├── 1.5 - Structural Causal Models.ipynb ├── 2.2 - Chains and Forks.ipynb ├── 2.3 - Colliders.ipynb ├── 2.4 - d-separation.ipynb ├── 2.5 - Model Testing and Causal Search.ipynb ├── 3.1 - Interventions.ipynb ├── 3.2 - The Adjustment Formula.ipynb ├── 3.3 - Backdoor Criterion.ipynb ├── 3.4 - Front-Door Criterion.ipynb ├── 3.5 - Conditional Interventions and Covariate-Specific Effects.ipynb ├── 3.6 - Inverse Probability Weighing.ipynb ├── 3.7 - Mediation.ipynb ├── CausalModel.py ├── LICENSE ├── README.md ├── d4sci.mplstyle ├── dags ├── Causality.Fig.1.2.dot ├── Primer.Fig.1.10.dot ├── Primer.Fig.1.6.dot ├── Primer.Fig.1.7a.dot ├── Primer.Fig.1.8.dot ├── Primer.Fig.1.9.dot ├── Primer.Fig.2.1.dot ├── Primer.Fig.2.2.dot ├── Primer.Fig.2.3.dot ├── Primer.Fig.2.5.dot ├── Primer.Fig.2.6.dot ├── Primer.Fig.2.7.dot ├── Primer.Fig.2.8.dot ├── Primer.Fig.2.9.dot ├── Primer.Fig.3.1.dot ├── Primer.Fig.3.10b.dot ├── Primer.Fig.3.11.dot ├── Primer.Fig.3.12.dot ├── Primer.Fig.3.2.dot ├── Primer.Fig.3.3.dot ├── Primer.Fig.3.4.dot ├── Primer.Fig.3.5.dot ├── Primer.Fig.3.6.dot ├── Primer.Fig.3.7.dot ├── Primer.Fig.3.8.dot └── Primer.SCM.1.5.3.dot ├── data ├── D4Sci_logo_ball.png ├── D4Sci_logo_full.png ├── book2.jpeg ├── causality.jpeg ├── iris.csv └── newsletter.png └── environment.yml /.gitignore: -------------------------------------------------------------------------------- 1 | figures/ 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | -------------------------------------------------------------------------------- /1.3 - Probability and Statistics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "
\n", 8 | "
\"Data
\n", 9 | "

Causal Inference In Statistics - A Primer

\n", 10 | "

1.3 Probability and Statistics

\n", 11 | "

Bruno Gonçalves
\n", 12 | " www.data4sci.com
\n", 13 | " @bgoncalves, @data4sci

\n", 14 | "

\n", 15 | "

\n", 16 | "
" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 1, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "from collections import Counter\n", 26 | "from pprint import pprint\n", 27 | "\n", 28 | "import pandas as pd\n", 29 | "import numpy as np\n", 30 | "import matplotlib.pyplot as plt \n", 31 | "\n", 32 | "import watermark\n", 33 | "\n", 34 | "%load_ext watermark\n", 35 | "%matplotlib inline" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "We start by print out the versions of the libraries we're using for future reference" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 2, 48 | "metadata": {}, 49 | "outputs": [ 50 | { 51 | "name": "stdout", 52 | "output_type": "stream", 53 | "text": [ 54 | "watermark 2.0.2\n", 55 | "json 2.0.9\n", 56 | "numpy 1.18.1\n", 57 | "autopep8 1.5\n", 58 | "pandas 1.0.1\n", 59 | "Fri Jul 24 2020 \n", 60 | "\n", 61 | "CPython 3.7.3\n", 62 | "IPython 6.2.1\n", 63 | "\n", 64 | "compiler : Clang 4.0.1 (tags/RELEASE_401/final)\n", 65 | "system : Darwin\n", 66 | "release : 19.5.0\n", 67 | "machine : x86_64\n", 68 | "processor : i386\n", 69 | "CPU cores : 8\n", 70 | "interpreter: 64bit\n", 71 | "Git hash : 7bb21d92754ca48aa8a201cf2e70b750085be46b\n" 72 | ] 73 | } 74 | ], 75 | "source": [ 76 | "%watermark -n -v -m -g -iv" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "Load default figure style" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 3, 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "plt.style.use('./d4sci.mplstyle')" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "metadata": {}, 98 | "source": [ 99 | "## Probability" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": 4, 105 | "metadata": {}, 106 | "outputs": [], 107 | "source": [ 108 | "data = pd.DataFrame({\n", 109 | " 'Gender': ['Male', 'Male', 'Male', 'Male', 'Female', 'Female', 'Female', 'Female'],\n", 110 | " 'Education': ['No High School', 'High School', 'College', 'Grad School', 'No High School', 'High School', 'College', 'Grad School'],\n", 111 | " 'Occurrence': [112, 231, 595, 242, 136, 189, 763, 172]\n", 112 | "})" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 5, 118 | "metadata": {}, 119 | "outputs": [], 120 | "source": [ 121 | "data.set_index(['Gender', 'Education'], inplace=True)" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 6, 127 | "metadata": {}, 128 | "outputs": [ 129 | { 130 | "data": { 131 | "text/plain": [ 132 | "Occurrence 2440\n", 133 | "dtype: int64" 134 | ] 135 | }, 136 | "execution_count": 6, 137 | "metadata": {}, 138 | "output_type": "execute_result" 139 | } 140 | ], 141 | "source": [ 142 | "data.sum()" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": 7, 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "P = data/data.sum()" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": 8, 157 | "metadata": {}, 158 | "outputs": [ 159 | { 160 | "data": { 161 | "text/html": [ 162 | "
\n", 163 | "\n", 176 | "\n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | "
Occurrence
GenderEducation
MaleNo High School0.045902
High School0.094672
College0.243852
Grad School0.099180
FemaleNo High School0.055738
High School0.077459
College0.312705
Grad School0.070492
\n", 226 | "
" 227 | ], 228 | "text/plain": [ 229 | " Occurrence\n", 230 | "Gender Education \n", 231 | "Male No High School 0.045902\n", 232 | " High School 0.094672\n", 233 | " College 0.243852\n", 234 | " Grad School 0.099180\n", 235 | "Female No High School 0.055738\n", 236 | " High School 0.077459\n", 237 | " College 0.312705\n", 238 | " Grad School 0.070492" 239 | ] 240 | }, 241 | "execution_count": 8, 242 | "metadata": {}, 243 | "output_type": "execute_result" 244 | } 245 | ], 246 | "source": [ 247 | "P" 248 | ] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": 9, 253 | "metadata": {}, 254 | "outputs": [ 255 | { 256 | "data": { 257 | "text/plain": [ 258 | "Occurrence 0.172131\n", 259 | "dtype: float64" 260 | ] 261 | }, 262 | "execution_count": 9, 263 | "metadata": {}, 264 | "output_type": "execute_result" 265 | } 266 | ], 267 | "source": [ 268 | "P.loc[(slice(None), 'High School'), :].sum()" 269 | ] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": 10, 274 | "metadata": {}, 275 | "outputs": [ 276 | { 277 | "data": { 278 | "text/plain": [ 279 | "Occurrence 0.611066\n", 280 | "dtype: float64" 281 | ] 282 | }, 283 | "execution_count": 10, 284 | "metadata": {}, 285 | "output_type": "execute_result" 286 | } 287 | ], 288 | "source": [ 289 | "P.loc['Female'].sum() + P.loc[(slice(None), 'High School'), :].sum() - P.loc[('Female', 'High School')]" 290 | ] 291 | }, 292 | { 293 | "cell_type": "code", 294 | "execution_count": 11, 295 | "metadata": {}, 296 | "outputs": [ 297 | { 298 | "data": { 299 | "text/plain": [ 300 | "Occurrence 231\n", 301 | "Name: (Male, High School), dtype: int64" 302 | ] 303 | }, 304 | "execution_count": 11, 305 | "metadata": {}, 306 | "output_type": "execute_result" 307 | } 308 | ], 309 | "source": [ 310 | "data.loc[('Male', 'High School')]" 311 | ] 312 | }, 313 | { 314 | "cell_type": "code", 315 | "execution_count": 12, 316 | "metadata": {}, 317 | "outputs": [], 318 | "source": [ 319 | "P_Female = data.loc['Female'].sum()/data.sum()" 320 | ] 321 | }, 322 | { 323 | "cell_type": "code", 324 | "execution_count": 13, 325 | "metadata": {}, 326 | "outputs": [ 327 | { 328 | "data": { 329 | "text/plain": [ 330 | "Occurrence 0.611066\n", 331 | "dtype: float64" 332 | ] 333 | }, 334 | "execution_count": 13, 335 | "metadata": {}, 336 | "output_type": "execute_result" 337 | } 338 | ], 339 | "source": [ 340 | "(data.loc[('Male', 'High School')] + data.loc['Female'].sum())/data.sum()" 341 | ] 342 | }, 343 | { 344 | "cell_type": "code", 345 | "execution_count": 14, 346 | "metadata": {}, 347 | "outputs": [], 348 | "source": [ 349 | "idx = pd.IndexSlice" 350 | ] 351 | }, 352 | { 353 | "cell_type": "code", 354 | "execution_count": 15, 355 | "metadata": {}, 356 | "outputs": [ 357 | { 358 | "data": { 359 | "text/plain": [ 360 | "Occurrence 0.172131\n", 361 | "dtype: float64" 362 | ] 363 | }, 364 | "execution_count": 15, 365 | "metadata": {}, 366 | "output_type": "execute_result" 367 | } 368 | ], 369 | "source": [ 370 | "data.loc[(slice(None), 'High School'), :].sum()/data.sum()" 371 | ] 372 | }, 373 | { 374 | "cell_type": "code", 375 | "execution_count": 16, 376 | "metadata": {}, 377 | "outputs": [ 378 | { 379 | "data": { 380 | "text/plain": [ 381 | "Occurrence 0.077459\n", 382 | "dtype: float64" 383 | ] 384 | }, 385 | "execution_count": 16, 386 | "metadata": {}, 387 | "output_type": "execute_result" 388 | } 389 | ], 390 | "source": [ 391 | "data.loc[('Female', 'High School')]/data.sum()" 392 | ] 393 | }, 394 | { 395 | "cell_type": "code", 396 | "execution_count": 17, 397 | "metadata": {}, 398 | "outputs": [], 399 | "source": [ 400 | "table = pd.pivot_table(data.reset_index(), index='Gender', columns='Education')" 401 | ] 402 | }, 403 | { 404 | "cell_type": "code", 405 | "execution_count": 18, 406 | "metadata": {}, 407 | "outputs": [ 408 | { 409 | "data": { 410 | "text/html": [ 411 | "
\n", 412 | "\n", 429 | "\n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | " \n", 453 | " \n", 454 | " \n", 455 | " \n", 456 | " \n", 457 | " \n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | " \n", 465 | " \n", 466 | "
Occurrence
EducationCollegeGrad SchoolHigh SchoolNo High School
Gender
Female763172189136
Male595242231112
\n", 467 | "
" 468 | ], 469 | "text/plain": [ 470 | " Occurrence \n", 471 | "Education College Grad School High School No High School\n", 472 | "Gender \n", 473 | "Female 763 172 189 136\n", 474 | "Male 595 242 231 112" 475 | ] 476 | }, 477 | "execution_count": 18, 478 | "metadata": {}, 479 | "output_type": "execute_result" 480 | } 481 | ], 482 | "source": [ 483 | "table" 484 | ] 485 | }, 486 | { 487 | "cell_type": "code", 488 | "execution_count": 19, 489 | "metadata": {}, 490 | "outputs": [], 491 | "source": [ 492 | "table.loc['Total'] = table.sum()\n", 493 | "table['Total'] = table.sum(axis=1)" 494 | ] 495 | }, 496 | { 497 | "cell_type": "code", 498 | "execution_count": 20, 499 | "metadata": {}, 500 | "outputs": [ 501 | { 502 | "data": { 503 | "text/html": [ 504 | "
\n", 505 | "\n", 522 | "\n", 523 | " \n", 524 | " \n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | " \n", 530 | " \n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | " \n", 537 | " \n", 538 | " \n", 539 | " \n", 540 | " \n", 541 | " \n", 542 | " \n", 543 | " \n", 544 | " \n", 545 | " \n", 546 | " \n", 547 | " \n", 548 | " \n", 549 | " \n", 550 | " \n", 551 | " \n", 552 | " \n", 553 | " \n", 554 | " \n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | "
OccurrenceTotal
EducationCollegeGrad SchoolHigh SchoolNo High School
Gender
Female7631721891361260
Male5952422311121180
Total13584144202482440
\n", 573 | "
" 574 | ], 575 | "text/plain": [ 576 | " Occurrence Total\n", 577 | "Education College Grad School High School No High School \n", 578 | "Gender \n", 579 | "Female 763 172 189 136 1260\n", 580 | "Male 595 242 231 112 1180\n", 581 | "Total 1358 414 420 248 2440" 582 | ] 583 | }, 584 | "execution_count": 20, 585 | "metadata": {}, 586 | "output_type": "execute_result" 587 | } 588 | ], 589 | "source": [ 590 | "table" 591 | ] 592 | }, 593 | { 594 | "cell_type": "markdown", 595 | "metadata": {}, 596 | "source": [ 597 | "
\n", 598 | " \"Data \n", 599 | "
" 600 | ] 601 | } 602 | ], 603 | "metadata": { 604 | "kernelspec": { 605 | "display_name": "Python 3", 606 | "language": "python", 607 | "name": "python3" 608 | }, 609 | "language_info": { 610 | "codemirror_mode": { 611 | "name": "ipython", 612 | "version": 3 613 | }, 614 | "file_extension": ".py", 615 | "mimetype": "text/x-python", 616 | "name": "python", 617 | "nbconvert_exporter": "python", 618 | "pygments_lexer": "ipython3", 619 | "version": "3.8.5" 620 | }, 621 | "toc": { 622 | "base_numbering": 1, 623 | "nav_menu": {}, 624 | "number_sections": true, 625 | "sideBar": true, 626 | "skip_h1_title": true, 627 | "title_cell": "Table of Contents", 628 | "title_sidebar": "Contents", 629 | "toc_cell": false, 630 | "toc_position": {}, 631 | "toc_section_display": true, 632 | "toc_window_display": false 633 | }, 634 | "varInspector": { 635 | "cols": { 636 | "lenName": 16, 637 | "lenType": 16, 638 | "lenVar": 40 639 | }, 640 | "kernels_config": { 641 | "python": { 642 | "delete_cmd_postfix": "", 643 | "delete_cmd_prefix": "del ", 644 | "library": "var_list.py", 645 | "varRefreshCmd": "print(var_dic_list())" 646 | }, 647 | "r": { 648 | "delete_cmd_postfix": ") ", 649 | "delete_cmd_prefix": "rm(", 650 | "library": "var_list.r", 651 | "varRefreshCmd": "cat(var_dic_list()) " 652 | } 653 | }, 654 | "types_to_exclude": [ 655 | "module", 656 | "function", 657 | "builtin_function_or_method", 658 | "instance", 659 | "_Feature" 660 | ], 661 | "window_display": false 662 | } 663 | }, 664 | "nbformat": 4, 665 | "nbformat_minor": 2 666 | } 667 | -------------------------------------------------------------------------------- /3.6 - Inverse Probability Weighing.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "
\n", 8 | "
\"Data
\n", 9 | "

Causal Inference In Statistics - A Primer

\n", 10 | "

3.6 Inverse Probability Weighing

\n", 11 | "

Bruno Gonçalves
\n", 12 | " www.data4sci.com
\n", 13 | " @bgoncalves, @data4sci

\n", 14 | "

\n", 15 | "

\n", 16 | "
" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 1, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "from collections import Counter\n", 26 | "from pprint import pprint\n", 27 | "\n", 28 | "import pandas as pd\n", 29 | "import numpy as np\n", 30 | "\n", 31 | "import matplotlib\n", 32 | "import matplotlib.pyplot as plt \n", 33 | "\n", 34 | "from CausalModel import CausalModel\n", 35 | "\n", 36 | "import watermark\n", 37 | "\n", 38 | "%load_ext watermark\n", 39 | "%matplotlib inline" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "We start by print out the versions of the libraries we're using for future reference" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 2, 52 | "metadata": {}, 53 | "outputs": [ 54 | { 55 | "name": "stdout", 56 | "output_type": "stream", 57 | "text": [ 58 | "Python implementation: CPython\n", 59 | "Python version : 3.8.5\n", 60 | "IPython version : 7.19.0\n", 61 | "\n", 62 | "Compiler : Clang 10.0.0 \n", 63 | "OS : Darwin\n", 64 | "Release : 20.2.0\n", 65 | "Machine : x86_64\n", 66 | "Processor : i386\n", 67 | "CPU cores : 16\n", 68 | "Architecture: 64bit\n", 69 | "\n", 70 | "Git hash: 6ba5a323994ecc2dbeee8be27219dbf5207f8dfb\n", 71 | "\n", 72 | "numpy : 1.19.2\n", 73 | "matplotlib: 3.3.2\n", 74 | "watermark : 2.1.0\n", 75 | "pandas : 1.1.3\n", 76 | "\n" 77 | ] 78 | } 79 | ], 80 | "source": [ 81 | "%watermark -n -v -m -g -iv" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "Load default figure style" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 3, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "plt.style.use('./d4sci.mplstyle')\n", 98 | "colors = plt.rcParams['axes.prop_cycle'].by_key()['color']" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "We load the DAG from Fig 1.10 for ease of reference" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": 4, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "G = CausalModel('dags/Primer.Fig.1.10.dot')" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 5, 120 | "metadata": {}, 121 | "outputs": [ 122 | { 123 | "data": { 124 | "image/png": "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\n", 125 | "text/plain": [ 126 | "
" 127 | ] 128 | }, 129 | "metadata": {}, 130 | "output_type": "display_data" 131 | } 132 | ], 133 | "source": [ 134 | "fig, ax = plt.subplots(1, figsize=(2.2,2.2))\n", 135 | "G.plot(ax=ax)" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": {}, 141 | "source": [ 142 | "In our example, the data for the joint probability $P\\left(X, Y, Z\\right)$ is:" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": 6, 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "PXYZ = pd.DataFrame({\n", 152 | " 'X': ['Yes', 'Yes', 'Yes', 'Yes', 'No', 'No', 'No', 'No'],\n", 153 | " 'Y': ['Yes', 'Yes', 'No', 'No', 'Yes', 'Yes', 'No', 'No'],\n", 154 | " 'Z': ['Male', 'Female', 'Male', 'Female', 'Male', 'Female', 'Male', 'Female'],\n", 155 | " 'Prob': [0.116, 0.274, 0.009, 0.101, 0.334, 0.079, 0.051, 0.036]\n", 156 | "})" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": 7, 162 | "metadata": {}, 163 | "outputs": [ 164 | { 165 | "data": { 166 | "text/html": [ 167 | "
\n", 168 | "\n", 181 | "\n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | "
XYZProb
0YesYesMale0.116
1YesYesFemale0.274
2YesNoMale0.009
3YesNoFemale0.101
4NoYesMale0.334
5NoYesFemale0.079
6NoNoMale0.051
7NoNoFemale0.036
\n", 250 | "
" 251 | ], 252 | "text/plain": [ 253 | " X Y Z Prob\n", 254 | "0 Yes Yes Male 0.116\n", 255 | "1 Yes Yes Female 0.274\n", 256 | "2 Yes No Male 0.009\n", 257 | "3 Yes No Female 0.101\n", 258 | "4 No Yes Male 0.334\n", 259 | "5 No Yes Female 0.079\n", 260 | "6 No No Male 0.051\n", 261 | "7 No No Female 0.036" 262 | ] 263 | }, 264 | "execution_count": 7, 265 | "metadata": {}, 266 | "output_type": "execute_result" 267 | } 268 | ], 269 | "source": [ 270 | "PXYZ" 271 | ] 272 | }, 273 | { 274 | "cell_type": "markdown", 275 | "metadata": {}, 276 | "source": [ 277 | "We check that it all sums up to one" 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": 8, 283 | "metadata": {}, 284 | "outputs": [ 285 | { 286 | "data": { 287 | "text/plain": [ 288 | "1.0" 289 | ] 290 | }, 291 | "execution_count": 8, 292 | "metadata": {}, 293 | "output_type": "execute_result" 294 | } 295 | ], 296 | "source": [ 297 | "PXYZ['Prob'].sum()" 298 | ] 299 | }, 300 | { 301 | "cell_type": "markdown", 302 | "metadata": {}, 303 | "source": [ 304 | "The conditional probability $P\\left(Y, Z | X\\right)$ for X=Yes:" 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": 9, 310 | "metadata": {}, 311 | "outputs": [], 312 | "source": [ 313 | "PYZ_X = PXYZ[PXYZ['X'] == 'Yes'].copy()\n", 314 | "PYZ_X['Prob'] /= PYZ_X['Prob'].sum()" 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": 10, 320 | "metadata": {}, 321 | "outputs": [ 322 | { 323 | "data": { 324 | "text/html": [ 325 | "
\n", 326 | "\n", 339 | "\n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | " \n", 362 | " \n", 363 | " \n", 364 | " \n", 365 | " \n", 366 | " \n", 367 | " \n", 368 | " \n", 369 | " \n", 370 | " \n", 371 | " \n", 372 | " \n", 373 | " \n", 374 | " \n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | "
XYZProb
0YesYesMale0.232
1YesYesFemale0.548
2YesNoMale0.018
3YesNoFemale0.202
\n", 380 | "
" 381 | ], 382 | "text/plain": [ 383 | " X Y Z Prob\n", 384 | "0 Yes Yes Male 0.232\n", 385 | "1 Yes Yes Female 0.548\n", 386 | "2 Yes No Male 0.018\n", 387 | "3 Yes No Female 0.202" 388 | ] 389 | }, 390 | "execution_count": 10, 391 | "metadata": {}, 392 | "output_type": "execute_result" 393 | } 394 | ], 395 | "source": [ 396 | "PYZ_X.round(3)" 397 | ] 398 | }, 399 | { 400 | "cell_type": "markdown", 401 | "metadata": {}, 402 | "source": [ 403 | "These numbers still differ slightly from the corrected in the [online Errata](http://bayes.cs.ucla.edu/PRIMER/pearl-etal-2016-primer-errata-pages-july2020.pdf)" 404 | ] 405 | }, 406 | { 407 | "cell_type": "markdown", 408 | "metadata": {}, 409 | "source": [ 410 | "Here we note that these values are just the corresponding values of $P\\left(X, Y, Z\\right)$ divided by $P\\left(X=\\mathrm{Yes}\\right)$" 411 | ] 412 | }, 413 | { 414 | "cell_type": "code", 415 | "execution_count": 11, 416 | "metadata": {}, 417 | "outputs": [], 418 | "source": [ 419 | "PX = PXYZ[['X', 'Prob']].groupby('X', as_index=False).sum()" 420 | ] 421 | }, 422 | { 423 | "cell_type": "code", 424 | "execution_count": 12, 425 | "metadata": {}, 426 | "outputs": [ 427 | { 428 | "data": { 429 | "text/html": [ 430 | "
\n", 431 | "\n", 444 | "\n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | " \n", 453 | " \n", 454 | " \n", 455 | " \n", 456 | " \n", 457 | " \n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | "
XProb
0No0.5
1Yes0.5
\n", 465 | "
" 466 | ], 467 | "text/plain": [ 468 | " X Prob\n", 469 | "0 No 0.5\n", 470 | "1 Yes 0.5" 471 | ] 472 | }, 473 | "execution_count": 12, 474 | "metadata": {}, 475 | "output_type": "execute_result" 476 | } 477 | ], 478 | "source": [ 479 | "PX" 480 | ] 481 | }, 482 | { 483 | "cell_type": "markdown", 484 | "metadata": {}, 485 | "source": [ 486 | "Please note that the calculations in earlier editions of the book are wrong. For instance, in Page 74 we have:" 487 | ] 488 | }, 489 | { 490 | "cell_type": "code", 491 | "execution_count": 13, 492 | "metadata": {}, 493 | "outputs": [ 494 | { 495 | "data": { 496 | "text/plain": [ 497 | "0.501" 498 | ] 499 | }, 500 | "execution_count": 13, 501 | "metadata": {}, 502 | "output_type": "execute_result" 503 | } 504 | ], 505 | "source": [ 506 | "0.116+0.274+0.01+0.101" 507 | ] 508 | }, 509 | { 510 | "cell_type": "markdown", 511 | "metadata": {}, 512 | "source": [ 513 | "since they didn't correct the 0.01 to 0.009." 514 | ] 515 | }, 516 | { 517 | "cell_type": "markdown", 518 | "metadata": {}, 519 | "source": [ 520 | "The gender distribution is:" 521 | ] 522 | }, 523 | { 524 | "cell_type": "code", 525 | "execution_count": 14, 526 | "metadata": {}, 527 | "outputs": [], 528 | "source": [ 529 | "PZ = PXYZ[['Z', 'Prob']].groupby('Z').sum()" 530 | ] 531 | }, 532 | { 533 | "cell_type": "code", 534 | "execution_count": 15, 535 | "metadata": {}, 536 | "outputs": [ 537 | { 538 | "data": { 539 | "text/html": [ 540 | "
\n", 541 | "\n", 554 | "\n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | " \n", 573 | " \n", 574 | " \n", 575 | "
Prob
Z
Female0.49
Male0.51
\n", 576 | "
" 577 | ], 578 | "text/plain": [ 579 | " Prob\n", 580 | "Z \n", 581 | "Female 0.49\n", 582 | "Male 0.51" 583 | ] 584 | }, 585 | "execution_count": 15, 586 | "metadata": {}, 587 | "output_type": "execute_result" 588 | } 589 | ], 590 | "source": [ 591 | "PZ" 592 | ] 593 | }, 594 | { 595 | "cell_type": "markdown", 596 | "metadata": {}, 597 | "source": [ 598 | "And the conditional probability $P\\left(\\mathrm{Drug}|\\mathrm{Gender}\\right)$ is:" 599 | ] 600 | }, 601 | { 602 | "cell_type": "code", 603 | "execution_count": 16, 604 | "metadata": {}, 605 | "outputs": [], 606 | "source": [ 607 | "PX_Z = PXYZ[['X', 'Z', 'Prob']].groupby(['X', 'Z'], as_index=False).sum()\n", 608 | "PX_Z['Prob'] = PX_Z.apply(lambda x: x['Prob']/PZ.loc[x.Z], axis=1) #Normalization" 609 | ] 610 | }, 611 | { 612 | "cell_type": "code", 613 | "execution_count": 17, 614 | "metadata": {}, 615 | "outputs": [], 616 | "source": [ 617 | "PX_Z.set_index(['X', 'Z'], inplace=True)" 618 | ] 619 | }, 620 | { 621 | "cell_type": "code", 622 | "execution_count": 18, 623 | "metadata": {}, 624 | "outputs": [ 625 | { 626 | "data": { 627 | "text/html": [ 628 | "
\n", 629 | "\n", 642 | "\n", 643 | " \n", 644 | " \n", 645 | " \n", 646 | " \n", 647 | " \n", 648 | " \n", 649 | " \n", 650 | " \n", 651 | " \n", 652 | " \n", 653 | " \n", 654 | " \n", 655 | " \n", 656 | " \n", 657 | " \n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | " \n", 672 | " \n", 673 | " \n", 674 | " \n", 675 | "
Prob
XZ
NoFemale0.234694
Male0.754902
YesFemale0.765306
Male0.245098
\n", 676 | "
" 677 | ], 678 | "text/plain": [ 679 | " Prob\n", 680 | "X Z \n", 681 | "No Female 0.234694\n", 682 | " Male 0.754902\n", 683 | "Yes Female 0.765306\n", 684 | " Male 0.245098" 685 | ] 686 | }, 687 | "execution_count": 18, 688 | "metadata": {}, 689 | "output_type": "execute_result" 690 | } 691 | ], 692 | "source": [ 693 | "PX_Z" 694 | ] 695 | }, 696 | { 697 | "cell_type": "markdown", 698 | "metadata": {}, 699 | "source": [ 700 | "The lack of correction for 0.009 subsists even in $P\\left(Y|Male\\right)$ which is why we obtain 0.245 instead of 0.247" 701 | ] 702 | }, 703 | { 704 | "cell_type": "markdown", 705 | "metadata": {}, 706 | "source": [ 707 | "The results of the intervention, as calculated the inverse probability weighing are then:" 708 | ] 709 | }, 710 | { 711 | "cell_type": "code", 712 | "execution_count": 19, 713 | "metadata": {}, 714 | "outputs": [], 715 | "source": [ 716 | "PYdoXZ = PXYZ.copy()" 717 | ] 718 | }, 719 | { 720 | "cell_type": "code", 721 | "execution_count": 20, 722 | "metadata": {}, 723 | "outputs": [], 724 | "source": [ 725 | "PYdoXZ['Prob'] = PYdoXZ.apply(lambda x: x.Prob/PX_Z.loc[x.X, x.Z], axis=1)" 726 | ] 727 | }, 728 | { 729 | "cell_type": "code", 730 | "execution_count": 21, 731 | "metadata": {}, 732 | "outputs": [ 733 | { 734 | "data": { 735 | "text/html": [ 736 | "
\n", 737 | "\n", 750 | "\n", 751 | " \n", 752 | " \n", 753 | " \n", 754 | " \n", 755 | " \n", 756 | " \n", 757 | " \n", 758 | " \n", 759 | " \n", 760 | " \n", 761 | " \n", 762 | " \n", 763 | " \n", 764 | " \n", 765 | " \n", 766 | " \n", 767 | " \n", 768 | " \n", 769 | " \n", 770 | " \n", 771 | " \n", 772 | " \n", 773 | " \n", 774 | " \n", 775 | " \n", 776 | " \n", 777 | " \n", 778 | " \n", 779 | " \n", 780 | " \n", 781 | " \n", 782 | " \n", 783 | " \n", 784 | " \n", 785 | " \n", 786 | " \n", 787 | " \n", 788 | " \n", 789 | " \n", 790 | " \n", 791 | " \n", 792 | " \n", 793 | " \n", 794 | " \n", 795 | " \n", 796 | " \n", 797 | " \n", 798 | " \n", 799 | " \n", 800 | " \n", 801 | " \n", 802 | " \n", 803 | " \n", 804 | " \n", 805 | " \n", 806 | " \n", 807 | " \n", 808 | " \n", 809 | " \n", 810 | " \n", 811 | " \n", 812 | " \n", 813 | " \n", 814 | " \n", 815 | " \n", 816 | " \n", 817 | " \n", 818 | "
XYZProb
0YesYesMale0.473
1YesYesFemale0.358
2YesNoMale0.037
3YesNoFemale0.132
4NoYesMale0.442
5NoYesFemale0.337
6NoNoMale0.068
7NoNoFemale0.153
\n", 819 | "
" 820 | ], 821 | "text/plain": [ 822 | " X Y Z Prob\n", 823 | "0 Yes Yes Male 0.473\n", 824 | "1 Yes Yes Female 0.358\n", 825 | "2 Yes No Male 0.037\n", 826 | "3 Yes No Female 0.132\n", 827 | "4 No Yes Male 0.442\n", 828 | "5 No Yes Female 0.337\n", 829 | "6 No No Male 0.068\n", 830 | "7 No No Female 0.153" 831 | ] 832 | }, 833 | "execution_count": 21, 834 | "metadata": {}, 835 | "output_type": "execute_result" 836 | } 837 | ], 838 | "source": [ 839 | "PYdoXZ.round(3)" 840 | ] 841 | }, 842 | { 843 | "cell_type": "markdown", 844 | "metadata": {}, 845 | "source": [ 846 | "The small differences we observe with respect to the corrected version in the [online Errata](http://bayes.cs.ucla.edu/PRIMER/pearl-etal-2016-primer-errata-pages-july2020.pdf) are due to the lack of correction from 0.01 to 0.009 as noted above." 847 | ] 848 | }, 849 | { 850 | "cell_type": "markdown", 851 | "metadata": {}, 852 | "source": [ 853 | "And finally, the intervention values are:" 854 | ] 855 | }, 856 | { 857 | "cell_type": "code", 858 | "execution_count": 22, 859 | "metadata": {}, 860 | "outputs": [], 861 | "source": [ 862 | "PYdoX = PYdoXZ[['X', 'Y', 'Prob']].groupby(['Y', 'X'], as_index=False).sum()" 863 | ] 864 | }, 865 | { 866 | "cell_type": "code", 867 | "execution_count": 23, 868 | "metadata": {}, 869 | "outputs": [ 870 | { 871 | "data": { 872 | "text/html": [ 873 | "
\n", 874 | "\n", 887 | "\n", 888 | " \n", 889 | " \n", 890 | " \n", 891 | " \n", 892 | " \n", 893 | " \n", 894 | " \n", 895 | " \n", 896 | " \n", 897 | " \n", 898 | " \n", 899 | " \n", 900 | " \n", 901 | " \n", 902 | " \n", 903 | " \n", 904 | " \n", 905 | " \n", 906 | " \n", 907 | " \n", 908 | " \n", 909 | " \n", 910 | " \n", 911 | " \n", 912 | " \n", 913 | " \n", 914 | " \n", 915 | " \n", 916 | " \n", 917 | " \n", 918 | " \n", 919 | " \n", 920 | " \n", 921 | " \n", 922 | "
YXProb
0NoNo0.221
1NoYes0.169
2YesNo0.779
3YesYes0.831
\n", 923 | "
" 924 | ], 925 | "text/plain": [ 926 | " Y X Prob\n", 927 | "0 No No 0.221\n", 928 | "1 No Yes 0.169\n", 929 | "2 Yes No 0.779\n", 930 | "3 Yes Yes 0.831" 931 | ] 932 | }, 933 | "execution_count": 23, 934 | "metadata": {}, 935 | "output_type": "execute_result" 936 | } 937 | ], 938 | "source": [ 939 | "PYdoX.round(3)" 940 | ] 941 | }, 942 | { 943 | "cell_type": "markdown", 944 | "metadata": {}, 945 | "source": [ 946 | "
\n", 947 | " \"Data \n", 948 | "
" 949 | ] 950 | } 951 | ], 952 | "metadata": { 953 | "kernelspec": { 954 | "display_name": "Python 3", 955 | "language": "python", 956 | "name": "python3" 957 | }, 958 | "language_info": { 959 | "codemirror_mode": { 960 | "name": "ipython", 961 | "version": 3 962 | }, 963 | "file_extension": ".py", 964 | "mimetype": "text/x-python", 965 | "name": "python", 966 | "nbconvert_exporter": "python", 967 | "pygments_lexer": "ipython3", 968 | "version": "3.8.5" 969 | }, 970 | "varInspector": { 971 | "cols": { 972 | "lenName": 16, 973 | "lenType": 16, 974 | "lenVar": 40 975 | }, 976 | "kernels_config": { 977 | "python": { 978 | "delete_cmd_postfix": "", 979 | "delete_cmd_prefix": "del ", 980 | "library": "var_list.py", 981 | "varRefreshCmd": "print(var_dic_list())" 982 | }, 983 | "r": { 984 | "delete_cmd_postfix": ") ", 985 | "delete_cmd_prefix": "rm(", 986 | "library": "var_list.r", 987 | "varRefreshCmd": "cat(var_dic_list()) " 988 | } 989 | }, 990 | "types_to_exclude": [ 991 | "module", 992 | "function", 993 | "builtin_function_or_method", 994 | "instance", 995 | "_Feature" 996 | ], 997 | "window_display": false 998 | } 999 | }, 1000 | "nbformat": 4, 1001 | "nbformat_minor": 4 1002 | } 1003 | -------------------------------------------------------------------------------- /3.7 - Mediation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "
\n", 8 | "
\"Data
\n", 9 | "

Causal Inference In Statistics - A Primer

\n", 10 | "

3.7 Mediation

\n", 11 | "

Bruno Gonçalves
\n", 12 | " www.data4sci.com
\n", 13 | " @bgoncalves, @data4sci

\n", 14 | "

\n", 15 | "

\n", 16 | "
" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 1, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "from collections import Counter\n", 26 | "from pprint import pprint\n", 27 | "\n", 28 | "import pandas as pd\n", 29 | "import numpy as np\n", 30 | "\n", 31 | "import matplotlib\n", 32 | "import matplotlib.pyplot as plt \n", 33 | "\n", 34 | "from CausalModel import CausalModel\n", 35 | "\n", 36 | "import watermark\n", 37 | "\n", 38 | "%load_ext watermark\n", 39 | "%matplotlib inline" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "We start by print out the versions of the libraries we're using for future reference" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 2, 52 | "metadata": {}, 53 | "outputs": [ 54 | { 55 | "name": "stdout", 56 | "output_type": "stream", 57 | "text": [ 58 | "Python implementation: CPython\n", 59 | "Python version : 3.8.5\n", 60 | "IPython version : 7.19.0\n", 61 | "\n", 62 | "Compiler : Clang 10.0.0 \n", 63 | "OS : Darwin\n", 64 | "Release : 20.3.0\n", 65 | "Machine : x86_64\n", 66 | "Processor : i386\n", 67 | "CPU cores : 16\n", 68 | "Architecture: 64bit\n", 69 | "\n", 70 | "Git hash: c41ed8befecf49059999a81adcec3b026fe5d326\n", 71 | "\n", 72 | "json : 2.0.9\n", 73 | "watermark : 2.1.0\n", 74 | "pandas : 1.1.3\n", 75 | "numpy : 1.19.2\n", 76 | "matplotlib: 3.3.2\n", 77 | "\n" 78 | ] 79 | } 80 | ], 81 | "source": [ 82 | "%watermark -n -v -m -g -iv" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "Load default figure style" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 3, 95 | "metadata": {}, 96 | "outputs": [], 97 | "source": [ 98 | "plt.style.use('./d4sci.mplstyle')\n", 99 | "colors = plt.rcParams['axes.prop_cycle'].by_key()['color']" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "metadata": {}, 105 | "source": [ 106 | "We build the DAG from Fig 3.11" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 4, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "G = CausalModel()\n", 116 | "G.add_causation('X', 'Y')\n", 117 | "G.add_causation('X', 'Z')\n", 118 | "G.add_causation('Z', 'Y')\n", 119 | "\n", 120 | "G.pos = {'X': (0, 0), 'Z': (1, 1), 'Y': (2, 0)}" 121 | ] 122 | }, 123 | { 124 | "cell_type": "code", 125 | "execution_count": 5, 126 | "metadata": {}, 127 | "outputs": [ 128 | { 129 | "data": { 130 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfgAAAH4CAYAAACmKP9/AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAC4jAAAuIwF4pT92AAAwC0lEQVR4nO3deXxV9Z3/8fe5NwuELYGALLIkQcUoCgT3qWKtldEuKi5TW2srbjhubXWmP6e1UttaZ8Y6KnVfOp1WrbWiVVu6qGhdGnZQQYQAyiYQkxAIZLv3/P64DXKykO0s33PO6/l4+OijX5KbL5eb88nr3JN7Ldu2bQEAgEhJBL0BAADgPgY8AAARxIAHACCCGPAAAEQQAx4AgAhiwAMAEEEMeAAAIogBDwBABDHgAQCIIAY8AAARxIAHACCCGPAAAEQQAx4AgAhiwAMAEEEMeAAAIogBDwBABDHgAQCIIAY8AAARxIAHACCCGPAAAEQQAx4AgAhiwAMAEEEMeAAAIogBDwBABDHgAQCIIAY8AAARxIAHACCCGPAAAEQQAx4AgAhiwAMAEEFZQW8AgLts21blHqmi2tamnbYaU7YaUlJTKvPn2UkpNynlJC0dPMhSSYGlwjzJsqxgNw7AVQx4IOQq62ytrba1riqtimpbFVW2ahu6dxsDc6WSwZlhXzw4ofEFlgr7MfCBMLNs27aD3gSA7mlK2XprY1rz1qa1utKbb+HDCi1NH5/QiaMTyk4y7IGwYcADIbJ9t60/V6T013Xpbld6Tw3MlT5XnNDnS5Ia1p9BD4QFAx4IgXe2pfXC6pQWb7EV1DesJalspKUvHpbUxIO4PhcwHQMeMFhdo63Hlqb06vp00FtxOLUooUsnJ9Uvh6IHTMWABwy1eEta9y9sVtXeoHfSvsF9pVnHZKlsJDUPmIgBDxjG1GrvCDUPmIkBDxhk2da05iwwt9o7MrivdM2xWZo0gpoHTMGABwzxyrqUfr4gFdhFdL1lSbrmuKROLUoGvRUAYsADRnhxdUqPLU0FvQ1XzJyS1FmHMuSBoDHggYDNXZXS/y2PxnBvcfHRSZ1zOEMeCBJPmAEB+tPa6A13Sfq/5Sn9aW30/l5AmDDggYD87cOUHloU3SH40KKU3vgwun8/wHQMeCAAG3faurc8vBfUdYUt6Z7ylDbujPLfEjAXAx7wWSpt697yZjWH49fce6U5Lc0pb1YqzZAH/MbbxQI+e351Wmurej7w6nZs0J+vO8zFHXVuwozv6fDzvt+jz11TZev3q9NcdAf4jIIHfLRxp62n3onf89JPvZPSJk7VA75iwAM+SaVtzYnJqfnWmtLSvZyqB3zFKXrAJ8+vTmtNL07Nt0gkczRo7FEu7Kitum3r1Fy/u816XuGYXt82p+oBf/FCN4AP6hptXf77JtU3B72Tjn2y+i397bbPy041OdZHnXC+jr3uV658jT5Z0sNfyuaNaQAfcIoe8MH8DWmjh/veqs0q/5+vtBnuA8dM1JQrH3Lt69Q3S69tiOFzFEAAGPCAx2zb1jyDX9Ut1dSg8p9dqIaajx3r2f0H6/jv/FZZuXmufr15a1PixCHgPQY84LF3t9vaXBv0Ljq27NFrVV2x0LFmJZI69rpfqd+wIte/3qZa6b3tDHjAawx4wGMm13vFvJ/ro9f+t836EV/5kYZNPM2zrztvLafpAa8x4AEPVe21Vb7JzFqtXPm63vnVv7VZP/jEC3XIF77t6dcu35RW1V4z7xcgKhjwgIf+WpGWib/6veeTjSq/+yLZKeeVf4PGHq3JVzzg+ddP2dLL66h4wEsMeMBD7243b4ilGutVfucFaqzd4VjP6T9Ex33nadcvquvIu9vMu2+AKGHAAx6xbVvrqs3L96UPz1LN+iWONSuR1DHX/0r9ho7zbR/rqm2upgc8xIAHPPLxbmlPU+cf56c1L/2PNr7xRJv1Iy76iYYd+Vlf91LXJG1r+6J5AFzCgAc8UlFl1inoHe++qveeuLnN+uiTvqJDzrrB/w1JWltt1n0ERAkDHvBIhUGn5+t2bNCCu78qO+38lb1B4yZp8hX3B7QraZ0Lr80PoH0MeMAjFYYMr+aGPSq/83w17v7EsZ4zoFDHf/tpJXP6BrQzc+4jIIoY8IAHTLrAbumDV2rnhysca1YyS8de/2vlDR0b0K4yuNAO8A4DHvBAc9qMC+w++P1/a9PbT7dZP/Ki2zX0iGn+b6iVuqbMfQXAfQx4wAONBrw67bYVf9HK39zSZn30Z76q8WdeF8CO2mfCfQVEEQMe8EDQQ2v3tgotvOfiNhfV5RdN1uTL7gtoV+1rouABTzDgAQ8EObSa63er/M7z1VRX7VjPGThUx337aSVz+gS0s/Y1UfCAJxjwgAeyAvzOWnz/Zard+J5jbd9FdYVjAtpVx4K8r4Ao41sL8EBuMpivu3ruT7Vlwdw26xO/doeGlp4SwI46lxPQfQVEHQMe8EB2AEPr46V/1Mrfzm6zPubki1Uy/Rr/N9RFDHjAGwx4wAPZCalvln9fb9fWD7RoziWS7XzyP7+4TJNmzvFvI93UN4tT9IBX+NYCPGBZlooKLF++VtPeXZmL6vbsdKznDhym4779G+MuqttfcYEly/LnfgLihgEPeKRksPeDy7ZtLb7vm9q1+X3HupXM0rE3PKG8IaM930NvFPtwHwFxxYAHPFLsQ8G//7sfaeuiF9qsT7z4v1R4+Gc8//q9VeLTWQ4gjhjwgEfGD/b222vrohf0/rM/brM+5pSvq+SMqz392m7x+j4C4ozvLsAjIwZ4d6Hdrs3va9F9l0qt3qiloGSq0RfV7a9vljR8QNC7AKKLAQ94JOHRhXZNe3bq73eep+a9tY713EEH6bhv/UbJ7FzXv6YXigssJbjADvCMj7/IA8TPEcMsrdzh7tuhLv/Ft7V765o267ad1tv/dY6rX2t/n/3pQldv74hhDHfASwx4wEOnFSf1zHtpuTni91Z+1O56Y+0ONdbucPErecdS5r4B4B1O0QMeGtbP0tRRlGprx4yyNLQf9wvgJQY84LHp4ynV1rhPAO9Ztm27+wQhAIe0beual5r08e6gd2KGEf2le8/K5gI7wGMUPOCxhGXpDIp1nzPGJxnugA8Y8IAPPluUCOQd5kyTnZROLeKwA/iB7zTABwNyLZ0zgW+3cyYkNCCXegf8wBEH8MmM0qTG5cd3uI3LtzSjlNMYgF8Y8IBPspOWrj0uqWQMZ3zSkq49LqnsOP7lgYAw4AEfFRUkNKM0ft92M0oTKiqI398bCBLfcYDP4naqnlPzQDAY8IDP4nSqnlPzQHAY8EAAigoSumJq9Kv2iqlJTs0DAeE7DwjI6SVJff3o6A75r09K6vSS6P79ANMx4IEAnX14UudF8KK780oTOnsCwx0IEq9FDwTMtm399r20nno3FfRWXPGViZkfWixejhYIFAMeMMRLH6T06JJwD/nLpiR15qGUO2ACBjxgkLc3pvXAwmbtagx6J90zIEeadUyWjh8dvacbgLBiwAOGqam39eCiZpVvCse35vEHW7piapby+3BKHjAJAx4wkG3bevOjtB5enDKv5m1bsiwNyJEun5rUSaN5vh0wEQMeMJhJNW/b9r5BPibxsW790miqHTAYAx4wnG3bWrDZ1gurU1q5I9hv18pVr2vtH+7VQc0facmSJZQ7YDAGPGC4mpoa9evXT9nZ2fqwJq0/rU1r/oa06pv9+fp9sqRRTRV68N/O065NK/etz507V2effbY/mwDQbQx4wFDV1dU66aSTtGrVKiWTST3yyCP6xje+IUna22TrtQ1p/XFtWht3evMtPGaQpenjEzplXEI5ibSOPPJIvf/++/v+fNKkSVQ8YDAGPGCghQsX6otf/KK2bdu2b23IkCGqrKx0fJxt29pYa6ui6tP/1tfYauzmr9PnJKWifEslgz/9b/RAyzG8n3zySV100UWOz6PiAXMx4AGD2LatOXPm6Dvf+Y6ampocf5aXl6e6urpObyOVtrWp1ta66sz/NjRLjSntG/o5ycx/uVnSwQMtFRdYOnigpWTiwCWeSqWoeCBEGPCAIWpqajRz5kw9++yz7f75sGHDHEUfBCoeCA8GPGCAhQsX6sILL9T69es7/Jji4mJVVFT4uKu2qHggPHhdSSBAtm3r3nvv1UknnXTA4W6KZDKpW265xbG2bNkyPf/88wHtCEBHKHggIJ2dkm/NhIKXqHggLCh4IAALFy7UlClTujzcTULFA+HAgAd8FLZT8h254IILNGHCBMfa7NmzxQlBwBwMeMAnNTU1Ou+883Tddde1+RW4sKHiAfPxHDzgg65cJd8ZU56Db8Fz8YDZKHjAY7/+9a9Df0q+PVQ8YDYKHvBQdXW1hg8frsbG3r+pu2kFL1HxgMkoeMBDtbW1rgx3U1HxgLkY8ICHxo4dq5kzZwa9DU9xRT1gJgY84LGHH35YL774oqZOnRr0VjxBxQNm4jl4wCe2besPf/iDbr31Vi1atKjbn2/ic/AteC4eMA8FD/jEsiydddZZWrBggV588UWVlJQEvSXXUPGAeRjwgM8sy9I///M/q1+/fkFvxVU8Fw+YhQEPBOD555/XihUrHGvFxcUB7cYdVDxgFgY84LN0Oq3Zs2c71kpLS7V69eoDXoyXnZ3tx/Z6hYoHzMGAB3z2/PPPa/ny5Y61W265RVlZWY7n6FsP+tNOO83PbfYIFQ+Yg6voAR+l02lNmTLFMeBLS0u1YsUKJZNJx8e2XHX/3HPPacyYMbrpppvUp08fv7fcbVxRD5iBAQ/4aO7cuTr33HMda0899ZQuvPDCgHbkjSeffFIXXXSRY23u3Lk6++yzg9kQEEMMeMAn3an3sKPigeDxHDzgk46ee4/acJd4Lh4wAQUP+CBO9d6CigeCRcEDPohTvbeg4oFgUfCAx+JY7y2oeCA4FDzgsTjWewsqHggOBQ94KM713oKKB4JBwQMeinO9t6DigWBQ8IBHqPdPUfGA/yh4wCPU+6eoeMB/FDzgAeq9LSoe8BcFD3iAem+Ligf8RcEDLqPeO0bFA/6h4AGXUe8do+IB/1DwgIuo985R8YA/KHjARdR756h4wB8UPOAS6r3rqHjAexQ84BLqveuoeMB7FDzgAuq9+6h4wFsUPOAC6r37qHjAWxQ80EvUe89R8YB3KHigl6j3nqPiAe9Q8EAvUO+9R8UD3qDggV6g3nuPige8QcEDPUS9u4eKB9xHwQM9RL27h4oH3EfBAz1AvbuPigfcRcEDPUC9u4+KB9xFwQPdRL17h4oH3EPBA91EvXuHigfcQ8ED3UC9e4+KB9xBwQPdQL17j4oH3EHBA11EvfuHigd6j4IHuoh69w8VD/QeBQ90AfXuPyoe6B0KHugC6t1/VDzQOxQ80AnqPThUPNBzFDzQCeo9OFQ80HMUPHAA1HvwqHigZyh44ACo9+BR8UDPUPBAB6h3c1DxQPdR8EAHqHdzUPFA91HwQDuod/NQ8UD3UPBAO6h381DxQPdQ8EAr1Lu5qHig6yh4oBXq3VxUPNB1FDywH+rdfFQ80DUUPLAf6t18VDzQNRQ88A/Ue3hQ8UDnKHjgH6j38KDigc5R8ICo9zCi4oEDo+ABUe9hRMUDB0bBI/ao9/Ci4oGOUfCIPeo9vKh4oGMUPGKNeg8/Kh5oHwWPWKPew4+KB9pHwSO2qPfooOKBtih4xBb1Hh1UPNAWBY9Yot6jh4oHnCh4xBL1Hj1UPOBEwSN2qPfoouKBT1HwiB3qPbqoeOBTFDxihXqPPioeyKDgESvUe/RR8UAGBY/YoN7jg4oHKHjECPUeH1Q8QMEjJqj3+KHiEXcUPGKBeo8fKh5xR8Ej8qj3+KLiEWcUPCKPeo8vKh5xRsEj0qh3UPGIKwoekUa9g4pHXFHwiCzqHS2oeMQRBY/Iot7RgopHHFHwiCTqHa1R8YgbCh6RRL2jNSoecUPBI3Kod3SEikecUPCIHOodHaHiEScUPCKFekdnqHjEBQWPSKHe0RkqHnFBwSMyqHd0FRWPOKDgERnUO7qKikccUPCIBOod3UXFI+ooeEQC9Y7uouIRdRQ8Qo96R09R8YgyCh6hR72jp6h4RBkFj1Cj3tFbVDyiioJHqFHv6C0qHlFFwSO0qHe4hYpHFFHwCC3qHW6h4hFFFDxCiXqH26h4RA0Fj1Ci3uE2Kh5RQ8EjdKh3eIWKR5RQ8Agd6h1eoeIRJRQ8QoV6h9eoeEQFBY9Qod7hNSoeUUHBIzSod/iFikcUUPAIDeodfqHiEQUUPEKBeoffqHiEHQWPUKDe4TcqHmFHwcN41DuCQsUjzCh4GI96R1CoeIQZBQ+jUe8IGhWPsKLgYTTqHUGj4hFWFDyMRb3DFFQ8woiCh7God5iCikcYUfAwEvUO01DxCBsKHkai3mEaKh5hQ8HDONQ7TEXFI0woeBiHeoepqHiECQUPo1DvMB0Vj7Cg4GEU6h2mo+IRFhQ8jEG9IyyoeIQBBQ9jUO8ICyoeYUDBwwjUO8KGiofpKHgYgXpH2FDxMB0Fj8BR7wgrKh4mo+AROOodYUXFw2QUPAJFvSPsqHiYioJHoKh3hB0VD1NR8AgM9Y6ooOJhIgoegaHeERVUPExEwSMQ1DuihoqHaSh4BIJ6R9R0VPHPPfdcMBtC7FHw8B31jqhKpVKaOHGiVq1atW+NikdQKHj4jnpHVPFcPExCwcNX7dX74YcfrnfeeYcBj0ig4mEKCh6+aq/ef/CDHzDcERlUPExBwcM3PPeOuKDiYQIKHr7huXfEBRUPE1Dw8AX1jrih4hE0Ch6+oN4RN1Q8gkbBw3PUO+KKikeQKHh4jnpHXFHxCBIFD09R74g7Kh5BoeDhKeodcUfFIygUPDxDvQMZVDyCQMHDM9Q7kEHFIwgUPDxBvQNOVDz8RsHDE9Q74ETFw28UPFxHvQPto+LhJwoerqPegfZR8fATBQ9XUe/AgVHx8AsFD1dR78CBUfHwCwUP11DvQNdQ8fADBQ/XUO9A11Dx8AMFD1dQ70D3UPHwGgUPV1DvQPdQ8fAaBY9eo96BnqHi4SUKHr1GvQM9Q8XDSxQ8eoV6B3qHiodXKHj0CvUO9A4VD69Q8Ogx6h1wBxUPL1Dw6DHqHXAHFQ8vUPDoEeodcBcVD7dR8OgR6h1wFxUPt1Hw6DbqHfAGFQ83UfDoNuod8AYVDzdR8OgW6h3wFhUPt1Dw6BbqHfAWFQ+3UPDoMuod8AcVDzdQ8Ogy6h3wBxUPN1Dw6BLqHfAXFY/eouDRJdQ74C8qHr1FwaNT1DsQDCoevUHBo1PUOxAMKh69QcHjgKh3IFhUPHqKgscBUe9AsKh49BQFjw5R74AZqHj0BAWPDlHvgBmoePQEBY92Ue+AWah4dBcFj3ZR74BZqHh0FwWPNqh3wExUPLqDgkcb1DtgJioe3UHBw4F6B8xGxaOrKHg4UO+A2ah4dBUFj32odyAcqHh0BQWPfah3IByoeHQFBQ9J1DsQNlQ8OkPBQxL1DoQNFY/OUPCg3oGQouJxIBQ8qHcgpKh4HAgFH3PUOxBuVDw6QsHHHPUOhBsVj45Q8DFGvQPRQMWjPRR8jFHvQDRQ8WgPBR9T1DsQLVQ8WqPgY4p6B6KFikdrFHwMUe9ANFHx2B8FH0PUOxBNVDz2R8HHDPUORBsVjxYUfMxQ70C0UfFoQcHHCPUOxAMVD4mCjxXqHYgHKh4SBR8b1DsQL1Q8KPiYoN6BeKHiQcHHAPUOxBMVH28UfAxQ70A8UfHxRsFHHPUOxBsVH18UfMRR70C8UfHxRcFHGPUOQKLi44qCjzDqHYBExccVBR9R1DuA/VHx8UPBRxT1DmB/VHz8UPARRL0DaA8VHy8UfARR7wDaQ8XHCwUfMdQ7gAOh4uODgo8Y6h3AgVDx8UHBRwj1DqArqPh4oOAjhHoH0BVUfDxQ8BFBvQPoDio++ij4iKDeAXQHFR99FHwEUO8AeoKKjzYKPgKodwA9QcVHGwUfctQ7gN6g4qOLgg856h1Ab1Dx0UXBhxj1DsANVHw0UfAhRr0DcAMVH00UfEhR7wDcRMVHDwUfUtQ7ADdR8dFDwYcQ9Q7AC1R8tFDwIUS9A/ACFR8tFHzIUO8AvETFRwcFHzLUOwAvUfHRQcGHCPUOwA9UfDRQ8CFCvQPwAxUfDRR8SFDvAPxExYcfBR8S1DsAP1Hx4UfBhwD1DiAIVHy4UfAhQL0DCAIVH24UvOGodwBBouLDi4I3HPUOIEhUfHhR8Aaj3gGYgIoPJwreYNQ7ABNQ8eFEwRuKegdgEio+fCh4Q1HvAExCxYcPBW8g6h2Aiaj4cKHgDUS9AzARFR8uFLxhqHcAJqPiw4OCNwz1DsBkVHx4UPAGod4BhAEVHw4UvEGodwBhQMWHAwVvCOodQJhQ8eaj4A1BvQMIEyrefBS8Aah3AGFExZuNgjcA9Q4gjKh4s1HwAaPeAYQZFW8uCj5g1DuAMKPizUXBB4h6BxAFVLyZKPgAUe8AooCKNxMFHxDqHUCUUPHmoeADQr0DiJLuVvzWrVs1e/ZszZw5U3//+9/92GLsUPABoN4BRFFXKn7r1q2644479OCDD6q+vl6SlJeXp7Vr12rEiBGB7DuqKPgAUO8AouhAFb9161bdcMMNKi4u1t13371vuEvSnj17VF5e7vd2I4+C9xn1DiDK2qv4wsJC7d692zHUW3v66ad1/vnn+7HF2KDgfUa9A4iy9iq+srLygMMd3mDA+yidTmv27NmOtdLSUp133nkB7QgA3LV161a9/fbbXDlvgKygNxAn1DuAqGrv4jkEiwHvE+odQBQx2M3FgPcJ9Q4gahYuXKjTTz9dO3fuDHoraAfPwfuAegcQRXfddRfD3WAMeB9Q7wCiaOjQoUFvAQfAgPcY9Q4gqm699VaVlZUFvQ10gAHvMeodQFQVFBTozTff1LXXXhv0VtAOBryHqHcAUZebm6t77rlHzzzzjAYOHBj0drAfBryHqHcAcTFjxgwtXbqUU/YGYcB7hHoHEDfFxcWcsjcIA94j1DuAOOKUvTkY8B6g3gHEHafsg8eA9wD1DgCcsg8aA95l1DsAfKqrp+xt2/ZxV/EQ29ei39Vgq6La1rpqWzV7bTWlpcZmqTktZSWk7KSUk5QG5loqHmyppMDSoD6dv/0h9Q4Abc2YMUOTJ0/WBRdcoMWLF7f5840bN3bpdnbW/+PYXWWrtsFWY0pqSn167M7JkrITUn5fS8UFmWP3gNx4vnVtLAb8niZbH3ySeUBUVKdVUWVre133b6cwTyoZbKmkIKHiwZYOHWKpf86nDxzqHQA61nLK/sYbb9ScOXMcfzZ8+PA2H7+7se2xu3JP97/usH5tj9152dEf+pYd4fMiFVVpzVub1t8+TKsx5f7tZyekE8ckNH18QocOsfTcc8/p3HPPdXzMU089pQsvvND9Lw4AIXbXXXfppptuUiqV0sSJE7Vs2TIlEgnZdmaoz1ub1psfpdWcdv9r5ySlz4zNHLtLBkf3merIDfjGlK03P8oM9jWf+PdXK8qXFjzxY7399H8r1ZD5EbO0tFQrVqzg9DwAtCOdTqumpkaDBw9WfbOtv32YOXavr/bv2H3IEEvTxyd00piEcpLRqvrIDPid9baefz+ll9eltasxuH001tXoo9f/T2teuFO/uP8u6h0ADqBqr625q1J6dX1ae5qC28eAHOm04oS+PCHZpeutwiASA/6tjWk9tKhZtQ1B7+RTqb21uv6UQTq1KEuWFY0HCwC4xbZtzd+Q1mNLUqoLcLC3NjBXunJqlk4YHf5T96Ee8DvrbT28OKW3NnrwJI1Lpo60dNUxWRrclyEPAFKm2h9Y2KxFW8wdPyeOTujysnDXfGgHvInV3pF+2dLMKUmdMi5BzQOILVOrvSNhr/nQDfi6Rlv3LzS72jsydaSla47L0sCY/k4mgPiqbbA1p9zsau/IiaMTmnVMUv1ywnXsDtWAr6m3ddv8Zq2vCc2W2xg1UPrBtGwV5oXrgQIAPVW5x9bs+U3aXBv0TnquqMDS90/JUn6ITtmHZsDvqMs8QLbsCnonvTc0T7r11GyNGBCeBwoA9MSWXbZmv9qkHT14gRrTjBrwj0DrF45jdygGfGWdrf94ORoPkBb5faQfnZatkQx5ABG1ZZet773cpJr6oHfinqF50o9PC8eQN/7KgZr6TLlHabhLUk29NPvVJlXuMf7nKwDotso9tm59NVrDXZJ27JFmz2/Sznrzj91GD/i6Rlu3vdaszRE4Ld+elgdKbYP5DxQA6KrahkyY9eR148Ng8y7ph681q67R7GO30QP+4cUpX1+yMAiba6U55c28VSKASLDtzNXyYb6grivWV9t6ZLEHb3LiImPfTW7BprRe/7B3vwq38reztfrZnzjWBh9yvE6+9RVZid69PvybPzlT29952bFWMv0aHXXJnd2+rUVbbL22Ia1pRbxmPYBwm78h3eNfhbPTKb12yymqrljoWD/8vFs0YcZ/uLE9NdTu0Ms3TVFD7XbH+vE3PasRU87q1m299mFaJ4xJ69hRZraykbva1WDrgUXNvb6dCefcrEFjj3asVa35u9a8eFevbnfdnx9oM9z7jzhER3zlRz2+zUeXpFS1l4oHEF5Ve209tqTnVWslkiq7+lElc/o61t+fe7tq1i/t7fYkScsevbbNcB877RvdHu4tHljYrF2GPs1q5IB/dEnKlQszElnZKpv1qBJZOY71Vc/8ULUbV/boNndvq9C7T9zsWLMSSZXNavug7I66pswDhVP1AMLItjMvP9vbV6gbMPIwlf7Lbc7bTjVp0X2XKtXUu5cu3fjGk9qyYK5jLa9wrCZe/F89vs2aevXqhxovGTfg3Tg1v79BYydqwozvOdbSTQ1afP+lSqe6d5bATqe1+P7LlGqoc6yP/8K3NPiQ43q915ZT9QAQNr05Nd9ayfRrVFg6zbG2a9NKrXz6Bz2+zb1VW7T8F99yLlqWplz1kLLzBvb4dqXMqfoFm807dhs14N06Nd/aoV+6UQUlxzjWatYv1QfP3dGt21nz0l2qWv2WY23g6CN0+Hm39HqPLThVDyBsentqvjXLslR21UPK6uscvGtfuluV77/Ro9tc+vAsNdVVO9ZKzvhXDT1iWk+36WDiqXqjBvxLH7hzar61zCn0R5TI7uNYzzyvs6xLt1G7caVW/Xa283aTmacAktm5bm1VdU3S3FVmnu4BgPbMXeX+m8fkDW3n1Lmd1pL7L1Nz/e5u3db6lx/VtmXzHGv9Rx7aq+umWqupz8wwkxgz4JvTtv5c4d0pjgGjJqj0glsda3aqSYvvn6l0c+MBPzedas58XKvnfw47+7vKL5rs9lb16vq06pvN+kkQANpT32zr1fXeHLvHnfoNDW918Vvd9vV651f/3uXbqNuxQe/+2vnxmeh7rFfXTbXnLxVpNafNOXYbM+AXbLI9f8Wj8Wder8GHnehYq934rlY9c1sHn5Gxeu7tqlm/xLGWXzRZh53zXdf3KEl7mqQ3XLwOAQC88rcP09rj4Vu/Tr78PuX0H+JY2/DyI9q27E+dfq5t21rywJVq3ut8tbRDv/xvGjz+mA4+q+eq66UFmxnwbcxb6/2pDSuRUNlVDyuZm+dYX/PCnapau7Ddz6let0Srn/upYy2RnauyWY8pkfTuZQT+uDbNFfUAjGbbtuat8TZG+uQP16SZ97ZZX/LQVWrcXd3OZ3yqYt4cVa6c71gbNPZoTTjXnd+pb8+8NeacpjdiwG/caevd7f4Ms/7Dx+uIr/zYsWanU1p8/0ylGp2nEFJNDVp8/0zZra62P/y8WzRwdKmn+1xfbWtNFQMegLk++MT25e27Rx0/QwefeKFjrb56i5b/4oYOP2fX1g+08qnvO9YS2bma+q+PK5GV7cU2JUnvbre1aacZx24jBrwf9b6/4s/PanPl5O4tq7XyN86r4Vc+/QPt2uT8ffnBhxyvQ77Q6lctPOL1T8YA0Bvz1vp3jDr6m3erT8FIx9qmN5/S5vJn23xsJtouU6pxr2M9E2dHeLpPyf+Z1pHAB3xDs635Hl2g0RHLsjTlyoeU1XeAY33tH+9V5ftvSpI+Wf2W1r50t+PPk7l5Kpv1SK9f5rar3vword2Gv5kBgHja3WjrzY/8O3bn9C/QlCseaLO+7NFrVV+zzbH2wQs/U/Wacsfa4MNO1CFf+Lane2wxf0NaDQZcKB34gN9QY2uv+7/63qm8oWN15Fdb/R68ndaSBy5Tw87tWnz/TMl2PniP+Jcfqf+IQ3zbY1NaWvNJ8A8SAOFUXl6usrIylZaW6vbbb9euXe69NecHn9hq9vkk40GTztC402Y61hp3VWrpw7P2/f/aje/p/Wd+6PiYZG4/lV31sKyEPyNvT1NmtgUt8AFfEeDzzEWnzdRBR3/esVa3bZ1e/u4xqtu2zrFeWDpNxWdc7ef2JAV7/wAIt4svvlhLlizRqlWrdPPNN2vcuHGuDfp1AR2bJn7tP9VvWJFj7eMlL+nD+f+rdHPmJW1b/+rzkV+9Xf2Hj/dzm1pnwDuhBj/gA74TJl/xgLL75TvWGmo+dvz/rL4DVHbVQ7Isy8edZayr5nl4AN3X2NioNWvWONaqqqpcG/QVAR2bsvr015RZj0iWc3yt+OWNWv74ddq5YZljfdhRn1Px6Vf6uMMME+Is+AEf8J3Qd/AoHfX1A7/F65FfvUN5Q8f6tCOntQY8SABEixuDPshjd+GEf9L4M69zrDXvrdWGVx5zrGX3y9eUKx70c2v7BD3bpIAHfEOzrU21wd8JY07+moYddXq7f3bQpDNU1Oo5Hz9V7pFqDXt9YwDR0NNBv7PeVuUejzfXidILf6gBow4/4Mccdcld6jvkYJ925LSx1g78QrtAB/yGGlsmvKpfQ22ldn64ot0/G3nsuT7vpi0TfhIEEF3dHfRBP7UqScnsXJVd/ZisDl5wbOSxZ2vMZy7yeVefStvBX2gX6IA34SIESVr+2HVq2Lmt3T9776nvqaF2h887cjLlfgIQbV0d9EFdYNdaQfEUHXZ225cMzxlQqEkz5wSwI6egj92BDvid9cE/SDa99bQ2l/+uwz9vrN2h5Y9d7+OO2uIUPQA/dTboTTomFZae3GZt4MGlyh04NIDdONU2dP4xXvLuxdS7oDHgF/upr/lYyx+/oc16dt4gNe3Zue//by7/nTb//XcadfwMH3f3qT/9db7emPNkIF8bQDilUr0/wLYM+tmzZ+vcc8/VL3/5S2VlZQV+7A6LxlSwPwgFOuCbAv4NsKWP/Ksad3/iWCv63OUadtTpKv/ZBY71ZY9fr8LSkwP5qXBNxXoteeyxzj8QADzQ0NCgJ598UlVVVZo3b56aGPBdEvQPQoGeovf7VZD29+Frv9THi190rOUNG6cjv/pTjTzmy23e2KCxdoeWPeb8tQy/JLJzA/m6ALC/hQsz77oZ5LE7TIK+nwId8FkBffW9n2zSO7+80bloWSq78mFl9ekvSTr6m/+j3Pzhjg/ZUv6sNr39jF/b3CfdFPATOQAg6eSTM893B3XsDpug76dAT9Hn+POeLW0seehKx3PsklRyxtWOizVy+g/WpJn3qvzO8x0ft/zx6zW09GTlDhrmy14l6cjDD9Up3/LnHewAREMqldI999zjym31799fl1xyyb7bywl0coRHbkAzrkWg/0zZAfx0s/4vD2n7ir861voNH6/Sf/lRm48dOfVLGv1PF2njG0/sW2vcVallj12n4771lOd7bTHt5JP0zcmn+Pb1AIRfY2Njrwf8qFGjdPPNN2vmzJnKzf30qcIgjt1hlJ30/+XN9xfoP1NBX3//8nXb1+vdJ/6fc9FKqGzWI8rKzWv3c4665Gfqkz/CsbZlwVxtevu3Xm2zjUF9gn2QAIiXUaNG6ec//7kqKip09dVXO4a7JOX7fOwOq/w+wX79QAd8cYF/DxLbtrXkgSvUXL/bsT7+rOs15NATOvy8nP4FmnRZ2xdMWP74DWrYud31fbanxMf7CUB8dTbYW/h57A6zoO+nQAf82HxLfp3BqPjjvapc9bpjbcCoCSo9/9ZOP3dE2Rc0+jNfc6xlTtVf6+YWOxT0gwRAtHV1sLcgOjqXtDIzLkiBDvicpKXRg7y/A3Zt/UArf3OLY81KJFU261Elc7p2DuWoS+5Un4KRjrUtC57Tpreedm2f7TmonzQgl28mAO7r7mBvMSDX0rB+Hm8u5MYMspQT5+fgJalksLd3gJ1OafH9lynVuNexfsiXblRBydQu305Ov3xNvuznbdaXP36D6mvafx17N5QMDvyfCEDE9HSw78/rY3fYmXD/BD49vD7V88ELP1P1mnLH2sDRR+rwGd/r9m0Nn3Kmxpzydcda4+5PPD1VX2zAgwRA+OTk5Ki0tNSx5sZgb1FSEPj4MBoDXt7eCbUb39P7v7vNsWYls1U261ElsnJ6dJtHff2/1WfwKMfa1oXPa+Obv+nxPg+E57oA9NQTTzyhU089VSeccIJrg70F8XFgJlw7FfjLFYzLt9Q/R9rd6O7tplPNWnz/zDavAnfY2d9VftGkHt9udt4gTb78Pr19x5cd6yt+8S0NPWKa+uQf1OPbbi0nKR0yJPgHCYBwOvroo/XKK694ctuHDrGUkwz+9dZN1D8nM9uCFnjBZyctnVrk/jZWz71dNeuXOtbyiybrsHPavndwdw2fNF1jp33Dsda4+xMte/SaXt/2/k4em1BedvAPEgBoLS/b0mfGBj5CjPTZokTgL3IjGTDgJemM8e6+nl/N+qVa/dxPHWuJrJzMqfmkOyctJl78n+o7ZLRjbeui32vjm+69wt0Z44345wGAdk3nGNUut2daT1m2bQf7hrX/8MP5TVr2sRFbMcKhQyz99PTsoLcBAAf0739p0ppPOHa3mDTc0i3TzDh2G/Pj13RDfuIxBT8ZAwgDjlVO0w8xZ5YZ8y9TNtJSYfsvBx87A3KkE8cY808DAB06aUxC/Xv2S0mRU5gnlY0I/rn3FsZMkWTC0udLzPnJJ0inFScCfwUkAOiKnKSlzxUbM0oC9fmSpJIJc47dRv2rnHloIvYVPzBX+vIEftABEB5fnpDUwN7/an2oFeZlZphJjNpNXralq48J/FfzA3Xl1CzeHhZAqAzqY+mKqfE+dl99bJZxv9Zs1ICXpEkjEjo9pqd7ThqT0Amj4/l3BxBuJ45O6MSYHr9OL0lo0nDz/u7m7UjSJZOTsTtVPzBXumwKp+YBhNflZfE7VV+YJ10yycxjt5EDPo6n6jk1DyDs4niq3sRT8y2MHPBSvE7Vc2oeQFTE6VS9qafmW5i7M0mXTklqQqGZPxm5pajA0lVTzTy9AwA9MeuYpIoMeDc1L00otHTpZLOP3UYP+NwsS/9xcpaKDHhXHi+MGiDdckqW+uVE8+8HIJ765Vj6/ilZGjkg6J14oyg/M5tys8w+dhs94KV/PFCmZWlUxB4oQ/OkH0zL5nl3AJGU38fSrdOyNTRiF0yPGiB9f1o4wsyYN5vpTE29rdvmN2t9TSi2e0CjBmaGe2Ge+Q8QAOiNyjpbs+c3afOuoHfSe0X5meDMD0mYhWbAS1Jdo60fv96s9ytDs+U2igsyp64odwBxsbPe1g9fa9b66vAeuycUZk7Lh6HcW4RqwEtSfbOtx5ek9Jd16aC30m2njE3osrJkqB4gAOCGukZbjyxO6bUPw3fsPr0koUsnJ41/zr210A34Fsu2pnXfwmZV7gl6J53L7yNddUyWjh1l/CUPAOCpBZvTemBhs2rqg95J5wrzMr/nbvKvwh1IaAe8JO1psvW/S82u+VPGJnTplKQG5IbrJz8A8MquBluPLknpdYNr/vSShC6ZlDT2RWy6ItQDvoWJNU+1A8CBLdiU1gOLzKr5sFf7/iIx4KVMzf/hg7T+XJEKdNAX9Mn85HfWoVQ7AHRmV4Otlz5I6S8VaVUHOOgL8zLv537moYlQV/v+IjPgW6TSthZvtTVvTUrLPvbvr3bkMEvTD0nq2FGWshLReHAAgF+a07YWbM4cu9/d7t+xe/IIS9PHJzVlhKVkxI7dkRvw+9u6y9af1qb0yvq0dje6f/t52dK0cQlNH5/UwYOi9cAAgKBs3Jk5ds/fkNaeJvdvv3+O9NmihM4Yn9SIAdE9dkd6wLdoStnaUGNrXbWttVW21lXZ+minrVQ3/uYJSxo90FLJ4Mx/xQWWigos5SSj++AAgCA1NLc9dm+stZXuxrE7aUljBjmP3ePyLWXH4NgdiwHfnsaUrQ//8cCpqc/8ENCQkprTUlZCyklKOUlLA3O17wERtt+BBICo2X/o1zZkjuWN+x27c5NSdtJSfp/MsXtsfnxDLLYDHgCAKAv/7wEAAIA2GPAAAEQQAx4AgAhiwAMAEEEMeAAAIogBDwBABDHgAQCIIAY8AAARxIAHACCCGPAAAEQQAx4AgAhiwAMAEEEMeAAAIogBDwBABDHgAQCIIAY8AAARxIAHACCCGPAAAEQQAx4AgAhiwAMAEEEMeAAAIogBDwBABDHgAQCIIAY8AAARxIAHACCCGPAAAEQQAx4AgAhiwAMAEEEMeAAAIogBDwBABP1/gYSNS5YWxrUAAAAASUVORK5CYII=\n", 131 | "text/plain": [ 132 | "
" 133 | ] 134 | }, 135 | "metadata": {}, 136 | "output_type": "display_data" 137 | } 138 | ], 139 | "source": [ 140 | "fig, ax = plt.subplots(1, figsize=(2.2,2.2))\n", 141 | "G.plot(ax=ax)" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 6, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "G.save_model('dags/Primer.Fig.3.11.dot')" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "And the DAG from Fig 3.12" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": 7, 163 | "metadata": {}, 164 | "outputs": [], 165 | "source": [ 166 | "G = CausalModel()\n", 167 | "G.add_causation('X', 'Y')\n", 168 | "G.add_causation('X', 'Z')\n", 169 | "G.add_causation('Z', 'Y')\n", 170 | "G.add_causation('I', 'Y')\n", 171 | "G.add_causation('I', 'Z')\n", 172 | "\n", 173 | "G.pos = {'X': (0, 0), 'Z': (1, 1), 'Y': (2, 0), 'I': (2, 1)}" 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": 8, 179 | "metadata": {}, 180 | "outputs": [ 181 | { 182 | "data": { 183 | "image/png": "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YsWesSlTpmjZsmWyLKvP262srNSyZcucmKIk6YQTTtBLL73k2PZMRoP3Ce0dQJj0tsVv3bpVc+bM0ezZs/XWW291ud3du3c7Os+mpiZHt2cyGrwPaO8AwqgnLX7r1q36yU9+ol/84hd7wragoEBr1qzR8OHDO2zz9ttv14033ujYHO+9915deeWVjm3PZDR4H9DeAYTRvlr81q1bdd1116m8vFw/+9nPMpr0rl27tGjRok63edVVV6m0tNSR+Y0cOVKzZ892ZFtBQMB7jDPnAYRZZ2fUX3LJJZ0G+95aW1s7HS8sLNQNN9zgyNxuvPFG5efnO7KtICDgPUZ7BxBmnbX46urqrI59O9Hio9beJQLeU7R3AGG3detWvfnmm1mdOd+eEy0+au1dIuA9RXsHEFZ7H2O/6667HL9UbTYtPortXSLgPUN7BxBG+zp5zknZtPgotneJgPcM7R1A2CxZskQTJ050Ndj31pcWH9X2LhHwnqC9AwijO++8U/X19Z7dXl9afFTbu0TAe4L2DiCMhg4d6vlt9qbFR7m9SwS862jvAMLq1ltvVWVlpae32ZsWH+X2LhHwrqO9AwirkpISvf7667r66qs9vd2etPiot3eJgHcV7R1A2OXn5+uuu+7SU089paKiIk9usyctPurtXSLgXUV7BxAV5557rt555x3Pluz31eJp72kEvEto7wCipry83LMl+321eNp7GgHvEto7gCjycsn+qquu6hDkAwcOpL1/joB3Ae0dQNR5sWRfWFioc845J2PswgsvpL1/joB3Ae0dALxZsn/00Ud1yimnqLS0VGeddZbuuusu124raCzb6U8EiLhUKqVp06ZlBPykSZP03nvvEfAAIuv3v/+9LrroIjU0NHT69SeeeELnn3++x7MKtxy/J+CXHc22qmptra21VbfbVmtKaklIiZSUE5Ny41JeXCrKt1ReaqmixNKgft1//CHtHQA6OvfcczV16lSdf/75Wrp0aYevb9y4sUfbqW/6fN9dY6uh2VZLUmpNfrHvzsuRcmNScX9L5SXpfXdhvnMfXRskkQj4Xa22Vm1PPyCqalOqqrH1WWPvtzOkQKootVRRElN5qaXxgy0NzPvigcOxdwDoWtuS/Q033KB77rkn42v7779/h+/f2dJx3129q/e3O2xAx313QW74Qz/US/RVNSktWJPSaxtSakk6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\n", 184 | "text/plain": [ 185 | "
" 186 | ] 187 | }, 188 | "metadata": {}, 189 | "output_type": "display_data" 190 | } 191 | ], 192 | "source": [ 193 | "fig, ax = plt.subplots(1, figsize=(2.2,2.2))\n", 194 | "G.plot(ax=ax)" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 9, 200 | "metadata": {}, 201 | "outputs": [], 202 | "source": [ 203 | "G.save_model('dags/Primer.Fig.3.12.dot')" 204 | ] 205 | }, 206 | { 207 | "cell_type": "markdown", 208 | "metadata": {}, 209 | "source": [ 210 | "We can easily compute the modified graph resulting from intervening on X and Z:" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": 10, 216 | "metadata": {}, 217 | "outputs": [ 218 | { 219 | "data": { 220 | "image/png": "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\n", 221 | "text/plain": [ 222 | "
" 223 | ] 224 | }, 225 | "metadata": {}, 226 | "output_type": "display_data" 227 | } 228 | ], 229 | "source": [ 230 | "G2 = G.intervention_graph(['X', 'Z'])\n", 231 | "fig, ax = plt.subplots(1, figsize=(2.2,2.2))\n", 232 | "G2.plot(ax=ax)" 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "metadata": {}, 238 | "source": [ 239 | "
\n", 240 | " \"Data \n", 241 | "
" 242 | ] 243 | } 244 | ], 245 | "metadata": { 246 | "kernelspec": { 247 | "display_name": "Python 3", 248 | "language": "python", 249 | "name": "python3" 250 | }, 251 | "language_info": { 252 | "codemirror_mode": { 253 | "name": "ipython", 254 | "version": 3 255 | }, 256 | "file_extension": ".py", 257 | "mimetype": "text/x-python", 258 | "name": "python", 259 | "nbconvert_exporter": "python", 260 | "pygments_lexer": "ipython3", 261 | "version": "3.8.5" 262 | }, 263 | "varInspector": { 264 | "cols": { 265 | "lenName": 16, 266 | "lenType": 16, 267 | "lenVar": 40 268 | }, 269 | "kernels_config": { 270 | "python": { 271 | "delete_cmd_postfix": "", 272 | "delete_cmd_prefix": "del ", 273 | "library": "var_list.py", 274 | "varRefreshCmd": "print(var_dic_list())" 275 | }, 276 | "r": { 277 | "delete_cmd_postfix": ") ", 278 | "delete_cmd_prefix": "rm(", 279 | "library": "var_list.r", 280 | "varRefreshCmd": "cat(var_dic_list()) " 281 | } 282 | }, 283 | "types_to_exclude": [ 284 | "module", 285 | "function", 286 | "builtin_function_or_method", 287 | "instance", 288 | "_Feature" 289 | ], 290 | "window_display": false 291 | } 292 | }, 293 | "nbformat": 4, 294 | "nbformat_minor": 4 295 | } 296 | -------------------------------------------------------------------------------- /CausalModel.py: -------------------------------------------------------------------------------- 1 | ### −∗− mode : python ; −∗− 2 | # @file CausalModel.py 3 | # @author Bruno Goncalves 4 | ###################################################### 5 | 6 | import networkx as nx 7 | from networkx.drawing.nx_pydot import graphviz_layout 8 | from itertools import combinations 9 | import numpy as np 10 | import pandas as pd 11 | import matplotlib as mpl 12 | import matplotlib.pyplot as plt 13 | import re 14 | import base64 15 | import requests 16 | 17 | import warnings 18 | warnings.filterwarnings("ignore") 19 | 20 | from tqdm import tqdm 21 | tqdm.pandas() 22 | 23 | plt.style.use('./d4sci.mplstyle') 24 | 25 | class CausalModel(object): 26 | """Simple Causal Model Implementation 27 | 28 | Provides a way to represent causal DAGs 29 | """ 30 | 31 | def __init__(self, filename=None): 32 | self.pos = None 33 | 34 | if filename is not None: 35 | self.load_model(filename) 36 | else: 37 | self.dag = nx.DiGraph() 38 | 39 | self.colors = plt.rcParams['axes.prop_cycle'].by_key()['color'] 40 | 41 | def copy(self): 42 | G = CausalModel() 43 | G.dag = self.dag.copy() 44 | G.pos = dict(self.pos) 45 | G.colors = [color for color in self.colors] 46 | 47 | return G 48 | 49 | 50 | def add_causation(self, source, target, label=None): 51 | """Add a causal link between source and target with an optional label. 52 | 53 | Parameters 54 | ---------- 55 | source : node-like 56 | The source node 57 | 58 | target : node-like 59 | The target node 60 | 61 | label : string-like or None 62 | The label for the causal link 63 | 64 | Returns 65 | ------- 66 | None 67 | 68 | Examples 69 | -------- 70 | >>> G = CausalModel() 71 | >>> G.add_causation('X', 'Y') 72 | 73 | """ 74 | 75 | if label is None: 76 | self.dag.add_edge(source, target) 77 | else: 78 | self.dag.add_edge(source, target, label=label) 79 | 80 | def load_model(self, path): 81 | """Initialize the CausalModel object by reading the information from the dot file with the passed path. 82 | 83 | The file should be a `dot` file and if it contains multiple graphs, only the first such graph is returned. All graphs _except_ the first are silently ignored. 84 | 85 | Parameters 86 | ---------- 87 | path : str or file 88 | Filename or file handle. 89 | 90 | Returns 91 | ------- 92 | None 93 | 94 | Examples 95 | -------- 96 | >>> G = CausalModel() 97 | >>> G.load_model('temp.dot') 98 | 99 | Notes 100 | ----- 101 | The heavy lifting is done by `networkx.drawing.nx_pydot.read_dot` 102 | 103 | """ 104 | 105 | G = nx.drawing.nx_pydot.read_dot(path) 106 | 107 | pos = {} 108 | 109 | for key, values in G.nodes(data=True): 110 | if 'x' not in values: 111 | pos = None 112 | break 113 | 114 | x = values['x'] 115 | y = values['y'] 116 | 117 | if x[0] == '"': 118 | x = x[1:-1] 119 | 120 | if y[0] == '"': 121 | y = y[1:-1] 122 | 123 | pos[key] = (float(x), float(y)) 124 | 125 | self.dag = nx.DiGraph(G) 126 | self.pos = pos 127 | 128 | 129 | def save_model(self, path): 130 | """Save the causal model as a `dot` file. 131 | 132 | Parameters 133 | ---------- 134 | path : str or file 135 | Filename or file handle. 136 | 137 | Returns 138 | ------- 139 | None 140 | 141 | Examples 142 | -------- 143 | >>> G = CausalModel() 144 | >>> G.add_causation('X', 'Y') 145 | >>> G.add_causation('Y', 'Z') 146 | >>> G.pos = {'X':(-1, 0), 'Y': (0, 0), 'Z': (1, 0)} 147 | >>> G.save_model('temp.dot') 148 | 149 | Notes 150 | ----- 151 | The heavy lifting is done by `networkx.drawing.nx_pydot.write_dot` 152 | 153 | """ 154 | G = self.dag.copy() 155 | 156 | if self.pos is not None: 157 | nodes = list(G.nodes()) 158 | 159 | for node in nodes: 160 | G.nodes[node]['x'] = str(self.pos[node][0]) 161 | G.nodes[node]['y'] = str(self.pos[node][1]) 162 | 163 | nx.drawing.nx_pydot.write_dot(G, path) 164 | 165 | def layout(self): 166 | """Initialize the CausalModel object by reading the information from the dot file with the passed path. 167 | 168 | The file should be a `dot` file and if it contains multiple graphs, only the first such graph is returned. All graphs _except_ the first are silently ignored. 169 | 170 | Parameters 171 | ---------- 172 | path : str or file 173 | Filename or file handle. 174 | 175 | Returns 176 | ------- 177 | None 178 | 179 | Examples 180 | -------- 181 | >>> G = CausalModel() 182 | >>> G.load_model('temp.dot') 183 | 184 | Notes 185 | ----- 186 | The heavy lifting is done by `networkx.drawing.nx_pydot.read_dot` 187 | 188 | """ 189 | pos = graphviz_layout(self.dag, 'dot') 190 | 191 | keys = list(pos.keys()) 192 | coords = np.array([pos[key] for key in keys]) 193 | coords = nx.rescale_layout(coords, 1) 194 | pos = dict(zip(keys, coords)) 195 | 196 | xs = [] 197 | ys = [] 198 | 199 | for key, value in pos.items(): 200 | xs.append(value[0]) 201 | ys.append(value[1]) 202 | 203 | # All xx coordinates are the same, switch x and y 204 | # To make it horizontal instead of vertical 205 | if len(set(xs)) == 1: 206 | pos = {key: [-value[1], value[0]] for key, value in pos.items()} 207 | 208 | return pos 209 | 210 | def parents(self, node): 211 | """Initialize the CausalModel object by reading the information from the dot file with the passed path. 212 | 213 | The file should be a `dot` file and if it contains multiple graphs, only the first such graph is returned. All graphs _except_ the first are silently ignored. 214 | 215 | Parameters 216 | ---------- 217 | path : str or file 218 | Filename or file handle. 219 | 220 | Returns 221 | ------- 222 | None 223 | 224 | Examples 225 | -------- 226 | >>> G = CausalModel() 227 | >>> G.load_model('temp.dot') 228 | 229 | Notes 230 | ----- 231 | The heavy lifting is done by `networkx.drawing.nx_pydot.read_dot` 232 | 233 | """ 234 | return list(self.dag.predecessors(node)) 235 | 236 | def ancestors(self, node): 237 | """Initialize the CausalModel object by reading the information from the dot file with the passed path. 238 | 239 | The file should be a `dot` file and if it contains multiple graphs, only the first such graph is returned. All graphs _except_ the first are silently ignored. 240 | 241 | Parameters 242 | ---------- 243 | path : str or file 244 | Filename or file handle. 245 | 246 | Returns 247 | ------- 248 | None 249 | 250 | Examples 251 | -------- 252 | >>> G = CausalModel() 253 | >>> G.load_model('temp.dot') 254 | 255 | Notes 256 | ----- 257 | The heavy lifting is done by `networkx.drawing.nx_pydot.read_dot` 258 | 259 | """ 260 | return list(nx.ancestors(self.dag, node)) 261 | 262 | def children(self, source): 263 | """Obtain the children of a node. 264 | 265 | Children are the nodes at the other end of outgoing edges. 266 | 267 | Parameters 268 | ---------- 269 | source : node in `G` 270 | The parent node 271 | 272 | Returns 273 | ------- 274 | list() 275 | List of the children of `source` in `G` 276 | 277 | Examples 278 | -------- 279 | >>> G.children('X') 280 | 281 | Notes 282 | ----- 283 | The heavy lifting is done by `networkx.successors` 284 | 285 | """ 286 | return list(self.dag.successors(source)) 287 | 288 | def descendants(self, source): 289 | """Obtain the descendants of a node. 290 | 291 | Descendants are all the nodes reacheable through outgoing edges. 292 | 293 | Parameters 294 | ---------- 295 | path : str or file 296 | Filename or file handle. 297 | 298 | Returns 299 | ------- 300 | list() 301 | List of the descendants of `source` in `G` 302 | 303 | Examples 304 | -------- 305 | >>> G = CausalModel() 306 | >>> G.load_model('temp.dot') 307 | 308 | Notes 309 | ----- 310 | The heavy lifting is done by `networkx.descendants` 311 | 312 | """ 313 | return list(nx.descendants(self.dag, source)) 314 | 315 | def backdoor_paths(self, source, target): 316 | """Initialize the CausalModel object by reading the information from the dot file with the passed path. 317 | 318 | The file should be a `dot` file and if it contains multiple graphs, only the first such graph is returned. All graphs _except_ the first are silently ignored. 319 | 320 | Parameters 321 | ---------- 322 | path : str or file 323 | Filename or file handle. 324 | 325 | Returns 326 | ------- 327 | None 328 | 329 | Examples 330 | -------- 331 | >>> G = CausalModel() 332 | >>> G.load_model('temp.dot') 333 | 334 | Notes 335 | ----- 336 | The heavy lifting is done by `networkx.drawing.nx_pydot.read_dot` 337 | 338 | """ 339 | 340 | allPaths = self.all_paths(source, target) 341 | directed = self.directed_paths(source, target) 342 | 343 | return allPaths-directed 344 | 345 | 346 | def directed_paths(self, source, target): 347 | """Initialize the CausalModel object by reading the information from the dot file with the passed path. 348 | 349 | The file should be a `dot` file and if it contains multiple graphs, only the first such graph is returned. All graphs _except_ the first are silently ignored. 350 | 351 | Parameters 352 | ---------- 353 | path : str or file 354 | Filename or file handle. 355 | 356 | Returns 357 | ------- 358 | None 359 | 360 | Examples 361 | -------- 362 | >>> G = CausalModel() 363 | >>> G.load_model('temp.dot') 364 | 365 | Notes 366 | ----- 367 | The heavy lifting is done by `networkx.drawing.nx_pydot.read_dot` 368 | 369 | """ 370 | return {tuple(path) for path in nx.all_simple_paths(self.dag, source, target)} 371 | 372 | def all_paths(self, source, target): 373 | """Initialize the CausalModel object by reading the information from the dot file with the passed path. 374 | 375 | The file should be a `dot` file and if it contains multiple graphs, only the first such graph is returned. All graphs _except_ the first are silently ignored. 376 | 377 | Parameters 378 | ---------- 379 | path : str or file 380 | Filename or file handle. 381 | 382 | Returns 383 | ------- 384 | None 385 | 386 | Examples 387 | -------- 388 | >>> G = CausalModel() 389 | >>> G.load_model('temp.dot') 390 | 391 | Notes 392 | ----- 393 | The heavy lifting is done by `networkx.drawing.nx_pydot.read_dot` 394 | 395 | """ 396 | return {tuple(path) for path in nx.all_simple_paths(self.dag.to_undirected(), source, target)} 397 | 398 | 399 | def all_paths_conditional(self, source, target, remove): 400 | """Initialize the CausalModel object by reading the information from the dot file with the passed path. 401 | 402 | The file should be a `dot` file and if it contains multiple graphs, only the first such graph is returned. All graphs _except_ the first are silently ignored. 403 | 404 | Parameters 405 | ---------- 406 | path : str or file 407 | Filename or file handle. 408 | 409 | Returns 410 | ------- 411 | None 412 | 413 | Examples 414 | -------- 415 | >>> G = CausalModel() 416 | >>> G.load_model('temp.dot') 417 | 418 | Notes 419 | ----- 420 | The heavy lifting is done by `networkx.drawing.nx_pydot.read_dot` 421 | 422 | """ 423 | 424 | dag = self.dag.to_undirected() 425 | dag.remove_nodes_from(remove) 426 | 427 | return {tuple(path) for path in nx.all_simple_paths(dag, source, target)} 428 | 429 | 430 | def plot_path(self, path, edges=False, ax=None, conditional=False, lw=3): 431 | """Initialize the CausalModel object by reading the information from the dot 432 | file with the passed path. 433 | 434 | The file should be a `dot` file and if it contains multiple graphs, only the 435 | first such graph is returned. All graphs _except_ the first are silently ignored. 436 | 437 | Parameters 438 | ---------- 439 | path : str or file 440 | Filename or file handle. 441 | 442 | Returns 443 | ------- 444 | None 445 | 446 | Examples 447 | -------- 448 | >>> G = CausalModel() 449 | >>> G.load_model('temp.dot') 450 | 451 | Notes 452 | ----- 453 | The heavy lifting is done by `networkx.drawing.nx_pydot.read_dot` 454 | 455 | 456 | """ 457 | fig = None 458 | 459 | if ax == None: 460 | fig, ax = plt.subplots(1) 461 | 462 | if edges: 463 | edgelist = path 464 | else: 465 | edgelist = {(path[i], path[i+1]) for i in range(len(path)-1)} 466 | 467 | edges = set(self.dag.edges()) - set(edgelist) 468 | 469 | nx.draw(self.dag, self.pos, node_color=self.colors[0], ax=ax, edgelist=[]) 470 | nx.draw_networkx_labels(self.dag, self.pos, ax=ax) 471 | 472 | if conditional: 473 | nx.draw_networkx_edges(self.dag, self.pos, 474 | edgelist=edgelist, 475 | width=lw, edge_color=self.colors[1], ax=ax, style='dotted') 476 | else: 477 | nx.draw_networkx_edges(self.dag, self.pos, 478 | edgelist=edgelist, 479 | width=lw, edge_color=self.colors[1], ax=ax) 480 | 481 | nx.draw_networkx_edges(self.dag, self.pos, 482 | edgelist=edges, 483 | width=1, ax=ax) 484 | 485 | if fig is not None: 486 | fig.tight_layout() 487 | 488 | 489 | def inputs(self): 490 | nodes = set() 491 | 492 | for node, deg in self.dag.in_degree(): 493 | if deg == 0: 494 | nodes.add(node) 495 | 496 | return nodes 497 | 498 | def outputs(self): 499 | nodes = set() 500 | 501 | for node, deg in self.dag.out_degree(): 502 | if deg == 0: 503 | nodes.add(node) 504 | 505 | return nodes 506 | 507 | def plot(self, output=None, pos=None, legend=False, ax=None, colors=False): 508 | if pos is None: 509 | if self.pos is None: 510 | self.pos = self.layout() 511 | 512 | pos = self.pos 513 | 514 | nodes = list(pos.keys()) 515 | inputs = self.inputs() 516 | outputs = self.outputs() 517 | 518 | node_colors = [] 519 | node_pos = [] 520 | 521 | for node in nodes: 522 | node_pos.append(pos[node]) 523 | 524 | if colors: 525 | if node in inputs: 526 | node_colors.append(self.colors[2]) 527 | elif node in outputs: 528 | node_colors.append(self.colors[1]) 529 | else: 530 | node_colors.append(self.colors[0]) 531 | else: 532 | node_colors.append(self.colors[0]) 533 | 534 | node_pos = np.array(node_pos) 535 | 536 | if ax is None: 537 | ax = nx.draw(self.dag, pos, nodelist=nodes, node_color=node_colors) 538 | else: 539 | nx.draw(self.dag, pos, nodelist=nodes, node_color=node_colors, ax=ax) 540 | 541 | 542 | labels = {(node_i, node_j) : label for node_i, node_j, label in self.dag.edges(data='label', default='')} 543 | 544 | nx.draw_networkx_labels(self.dag, pos, ax=ax) 545 | nx.draw_networkx_edge_labels(self.dag, pos, labels, ax=ax) 546 | 547 | if legend: 548 | node_types = ['Regular node', 'Input', 'Output'] 549 | node_colors = [self.colors[0], self.colors[2], self.colors[1]] 550 | 551 | patches = [mpl.patches.Patch(color=node_colors[i], label=label) for i, label in enumerate(node_types)] 552 | 553 | plt.legend(handles=patches, fontsize=10) 554 | 555 | plt.gcf().tight_layout() 556 | 557 | if output is None: 558 | plt.show() 559 | else: 560 | plt.savefig(output, dpi=300) 561 | plt.close() 562 | 563 | 564 | def v_structures(self): 565 | structs = set() 566 | 567 | degrees = dict(self.dag.in_degree()) 568 | 569 | for node in degrees: 570 | if degrees[node] >= 2: 571 | for edge_i, edge_j in combinations(self.dag.in_edges(node), 2): 572 | node_i = edge_i[0] 573 | node_j = edge_j[0] 574 | 575 | if not (node_i, node_j) in self.dag.edges and not (node_j, node_i) in self.dag.edges: 576 | structs.add(tuple(sorted([edge_i, edge_j]))) 577 | 578 | return structs 579 | 580 | def equivalence_class(self): 581 | edges = list(self.dag.edges(data=True)) 582 | 583 | equivalent = [[self.copy(), []]] 584 | 585 | structs = self.v_structures() 586 | 587 | for i, edge in enumerate(edges): 588 | new_edges = list(edges) 589 | 590 | new_edges[i] = (edge[1], edge[0], edge[2]) 591 | 592 | G = CausalModel() 593 | G.dag.add_edges_from(new_edges) 594 | 595 | new_structs = CausalModel.v_structures(G) 596 | 597 | if new_structs == structs and len(list(nx.simple_cycles(G.dag)))==0: 598 | G.pos = dict(self.pos) 599 | G.colors = [color for color in self.colors] 600 | equivalent.append([G, new_edges[i][:2]]) 601 | 602 | return equivalent 603 | 604 | 605 | def basis_set(self): 606 | nodes = set(self.dag.nodes()) 607 | 608 | eqn = [] 609 | 610 | for node in nodes: 611 | parents = set(self.parents(node)) 612 | descendants = set(self.descendants(node)) 613 | 614 | others = {n for n in nodes if n != node} 615 | others -= parents 616 | others -= descendants 617 | 618 | others = sorted(others) 619 | parents = sorted(parents) 620 | 621 | if len(others) > 0: 622 | if len(parents) > 0: 623 | eqn.append('%s _||_ %s | %s' % (node, ", ".join(others), ', '.join(parents))) 624 | else: 625 | eqn.append('%s _||_ %s' % (node, ", ".join(others))) 626 | 627 | return sorted(eqn) 628 | 629 | 630 | def intervention_graph(self, nodes, drop_nodes=False): 631 | G = self.copy() 632 | 633 | for node in nodes: 634 | G.dag.remove_edges_from(list(self.dag.in_edges(nodes))) 635 | 636 | if drop_nodes: 637 | degrees = dict(G.dag.degree()) 638 | 639 | remove = [] 640 | 641 | for node in degrees: 642 | if degrees[node] == 0: 643 | remove.append(node) 644 | del G.pos[node] 645 | 646 | G.dag.remove_nodes_from(remove) 647 | 648 | return G 649 | 650 | def conditional_intervention_graph(self, nodes, dependencies, drop_nodes=False): 651 | G = self.copy() 652 | 653 | for node in nodes: 654 | G.dag.remove_edges_from(list(self.dag.in_edges(nodes))) 655 | 656 | G.dag.add_edges_from(dependencies) 657 | 658 | if drop_nodes: 659 | degrees = dict(G.dag.degree()) 660 | 661 | remove = [] 662 | 663 | for node in degrees: 664 | if degrees[node] == 0: 665 | remove.append(node) 666 | del G.pos[node] 667 | 668 | G.dag.remove_nodes_from(remove) 669 | 670 | return G 671 | 672 | 673 | 674 | if __name__ == "__main__": 675 | names = ['m331', 'moAh6a6', 'vcFQ'] 676 | 677 | graph_id = 'temp'#names[2] 678 | 679 | G = CausalModel()#graph_id) 680 | G.load_model('dags/Primer.Fig.2.9.dot') 681 | 682 | Gx = G.intervention_graph('X') 683 | Gx2 = Gx.intervention_graph('Z3') 684 | print("ok") -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Data For Science 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub](https://img.shields.io/github/license/DataForScience/Causality) 2 | [![Twitter @data4sci](https://img.shields.io/twitter/follow/data4sci)](https://twitter.com/intent/follow?screen_name=data4sci) 3 | ![GitHub top language](https://img.shields.io/github/languages/top/DataForScience/Causality) 4 | ![GitHub repo size](https://img.shields.io/github/repo-size/DataForScience/Causality) 5 | ![GitHub last commit](https://img.shields.io/github/last-commit/DataForScience/Causality) 6 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/DataForScience/Causality/master) 7 | 8 | [![Sunday Briefing](https://img.shields.io/badge/Sunday_Briefing-Subscribe-blue)](https://data4sci.ck.page/8a51c452bc) 9 | 10 | # Causality 11 | 12 | How do causes lead to effects? Can you associate the cause leading to the observed effect? Big Data opens the doors for us to be able to answer questions such as this, but before we are able to do so, we must dive into the field of Causal Inference, a field championed by Judea Pearl. 13 | In this series of blog posts we will learn about the main ideas of Causality by working our way through “Causal Inference In Statistics” a nice Primer co-authored by Pearl himself. 14 | 15 |

16 | 17 |
18 | Amazon Affiliate Link: https://amzn.to/3gsFlkO 19 |

20 | 21 | 22 | The book is divided into Four chapters. The first chapter covers background material in probability and statistics. The other three chapters are (roughly) organized to match the “Three steps” in the ladder of causality as defined by Pearl: 23 | 24 | 1. — Association 25 | 2. — Intervention 26 | 3. — Counterfactuals 27 | 28 | In this series of blog posts we will cover most of the content of the book, with a special emphasis on the parts that I believe are more interesting or relevant to practical applications. In addition to summarizing and explaining the content, we will also explore some of the ideas using simple (or as simple as possible) Python code you can run on Binder: [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/DataForScience/Causality/master) 29 | 30 | 31 | 32 | ## Chapter 1 33 | 1.2 - [Simpson's Paradox](https://data4sci.substack.com/p/simpsons-paradox) -- [1.2 - Simpson's Paradox.ipynb](https://github.com/DataForScience/Causality/blob/master/1.2%20-%20Simpsons%20Paradox.ipynb) 34 | 35 | 1.3 - [Probability Theory](https://data4sci.substack.com/p/probability-theory) -- [1.3 - Probability and Statistics.ipynb](https://github.com/DataForScience/Causality/blob/master/1.3%20-%20Probability%20and%20Statistics.ipynb) 36 | 37 | 1.4 - [Graphs](https://data4sci.substack.com/p/graphs) -- [1.4 - Graphs.ipynb](https://github.com/DataForScience/Causality/blob/master/1.4%20-%20Graphs.ipynb) 38 | 39 | 1.5 - [Structural Causal Models](https://data4sci.substack.com/p/structural-causal-models-e4d) -- [1.5 - Structural Causal Models.ipynb](https://github.com/DataForScience/Causality/blob/master/1.5%20-%20Structural%20Causal%20Models.ipynb) 40 | 41 | ## Chapter 2 42 | 2.2 - [Chains and Forks](https://data4sci.substack.com/p/chains-and-forks) -- [2.2 - Chains and Forks.ipynb](https://github.com/DataForScience/Causality/blob/master/2.2%20-%20Chains%20and%20Forks.ipynb) 43 | 44 | 2.3 - [Colliders](https://data4sci.substack.com/p/colliders) -- [2.3 - Colliders.ipynb](https://github.com/DataForScience/Causality/blob/master/2.3%20-%20Colliders.ipynb) 45 | 46 | 2.4 - [d-separation](https://data4sci.substack.com/p/d-separation) -- [2.4 - d-separation.ipynb](https://github.com/DataForScience/Causality/blob/master/2.4%20-%20d-separation.ipynb) 47 | 48 | 2.5 - [Model Testing and Causal Search](https://data4sci.substack.com/p/model-testing-and-causal-search) -- [2.5 - Model Testing and Causal Search.ipynb](https://github.com/DataForScience/Causality/blob/master/2.5%20-%20Model%20Testing%20and%20Causal%20Search.ipynb) 49 | 50 | ## Chapter 3 51 | 52 | 3.1 - [Interventions](https://data4sci.substack.com/p/interventions) -- [3.1 - Interventions.ipynb](https://github.com/DataForScience/Causality/blob/master/3.1%20-%20Interventions.ipynb) 53 | 54 | 3.2 - [Adjustment Formula](https://data4sci.substack.com/p/the-adjustment-formula) -- [3.2 - The Adjustment Formula.ipynb](https://github.com/DataForScience/Causality/blob/master/3.2%20-%20The%20Adjustment%20Formula.ipynb) 55 | 56 | 3.3 - [Backdoor Criterion](https://data4sci.substack.com/p/backdoor-criterion) -- [3.3 - Backdoor Criterion.ipynb](https://github.com/DataForScience/Causality/blob/master/3.3%20-%20Backdoor%20Criterion.ipynb) 57 | 58 | 3.4 - [Front-Door Criterion](https://data4sci.substack.com/p/front-door-criterion) -- [3.4 - Front-Door Criterion.ipynb](https://github.com/DataForScience/Causality/blob/master/3.4%20-%20Front-Door%20Criterion.ipynb) 59 | 60 | 3.5 - [Conditional Interventions and Covariate-Specific Effects](https://data4sci.substack.com/p/conditional-interventions-and-covariate) -- [3.5 - Conditional Interventions and Covariate-Specific Effects.ipynb](https://github.com/DataForScience/Causality/blob/master/3.5%20-%20Conditional%20Interventions%20and%20Covariate-Specific%20Effects.ipynb) 61 | 62 | 3.6 - [Inverse Probability Weighing](https://data4sci.substack.com/p/inverse-probability-weighing) -- [3.6 - Inverse Probability Weighing.ipynb](https://github.com/DataForScience/Causality/blob/master/3.6%20-%20Inverse%20Probability%20Weighing.ipynb) 63 | 64 | 3.7 - [Mediation](https://data4sci.substack.com/p/mediation) -- [3.7 - Mediation.ipynb](https://github.com/DataForScience/Causality/blob/master/3.7%20-%20Mediation.ipynb) 65 | 66 | --- 67 | 68 | For a more in-depth analysis, checkout Pearl's more technical book: 69 | 70 |

71 | 72 |
73 | Amazon Affiliate Link: https://amzn.to/2OSBP6u 74 |

75 | 76 | --- 77 | 78 | Sign up to the [Data Science Briefing](http://data4sci.com/newsletter) newsletter to be the first to know when we publish new posts: 79 | 80 |

81 | 82 |

83 | -------------------------------------------------------------------------------- /d4sci.mplstyle: -------------------------------------------------------------------------------- 1 | # Data For Science style 2 | # Author: Bruno Goncalves 3 | # Modified from the matplotlib FiveThirtyEight style by 4 | # Author: Cameron Davidson-Pilon, replicated styles from FiveThirtyEight.com 5 | # See https://www.dataorigami.net/blogs/fivethirtyeight-mpl 6 | 7 | lines.linewidth: 4 8 | lines.solid_capstyle: butt 9 | 10 | legend.fancybox: true 11 | 12 | axes.prop_cycle: cycler('color', ['51a7f9', 'cf51f9', '70bf41', 'f39019', 'f9e351', 'f9517b', '6d904f', '8b8b8b','810f7c']) 13 | 14 | axes.labelsize: large 15 | axes.axisbelow: true 16 | axes.grid: true 17 | axes.edgecolor: f0f0f0 18 | axes.linewidth: 3.0 19 | axes.titlesize: x-large 20 | 21 | patch.edgecolor: f0f0f0 22 | patch.linewidth: 0.5 23 | 24 | svg.fonttype: path 25 | 26 | grid.linestyle: - 27 | grid.linewidth: 1.0 28 | 29 | xtick.major.size: 0 30 | xtick.minor.size: 0 31 | ytick.major.size: 0 32 | ytick.minor.size: 0 33 | 34 | font.size: 24.0 35 | 36 | savefig.edgecolor: f0f0f0 37 | savefig.facecolor: f0f0f0 38 | 39 | figure.subplot.left: 0.08 40 | figure.subplot.right: 0.95 41 | figure.subplot.bottom: 0.07 42 | figure.figsize: 12.8, 8.8 43 | figure.autolayout: True 44 | figure.dpi: 300 45 | -------------------------------------------------------------------------------- /dags/Causality.Fig.1.2.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | X1 [x=0, y=2]; 3 | X2 [x=1, y=1]; 4 | X3 [x="-1", y=1]; 5 | X4 [x=0, y=0]; 6 | X5 [x=0, y="-1"]; 7 | X1 -> X2; 8 | X1 -> X3; 9 | X2 -> X4; 10 | X3 -> X4; 11 | X4 -> X5; 12 | } 13 | -------------------------------------------------------------------------------- /dags/Primer.Fig.1.10.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | "Z" [x=0, y=1]; 3 | "X" [x="-1", y=0]; 4 | "Y" [x=1, y=0]; 5 | "Z" -> "X"; 6 | "Z" -> "Y"; 7 | "X" -> "Y"; 8 | } 9 | -------------------------------------------------------------------------------- /dags/Primer.Fig.1.6.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | X [x="-1.0", y="0.0"]; 3 | Y [x="-0.0", y="0.0"]; 4 | Z [x="1.0", y="0.0"]; 5 | X -> Y [label=A]; 6 | Y -> Z [label=B]; 7 | } 8 | -------------------------------------------------------------------------------- /dags/Primer.Fig.1.7a.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | X [x=0, y=1]; 3 | Y [x="-1", y=0]; 4 | Z [x=1, y=0]; 5 | X -> Y [label=A]; 6 | X -> Z [label=C]; 7 | Y -> Z [label=B]; 8 | } 9 | -------------------------------------------------------------------------------- /dags/Primer.Fig.1.8.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | X [x="-1", y=0]; 3 | Y [x=0, y=0]; 4 | W [x=0, y=1]; 5 | Z [x=1, y=0]; 6 | T [x="0.5", y="-0.5"]; 7 | X -> Y; 8 | X -> W; 9 | Y -> T; 10 | Y -> Z; 11 | W -> Y; 12 | W -> Z; 13 | Z -> T; 14 | } 15 | -------------------------------------------------------------------------------- /dags/Primer.Fig.1.9.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | X [x="-1", y=1]; 3 | Z [x=0, y=0]; 4 | Y [x=1, y=1]; 5 | X -> Z; 6 | Y -> Z; 7 | } 8 | -------------------------------------------------------------------------------- /dags/Primer.Fig.2.1.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | Ux [x="-0.5", y="1.5"]; 3 | X [x=0, y=1]; 4 | Uy [x="-0.5", y="0.5"]; 5 | Y [x=0, y=0]; 6 | Uz [x="-0.5", y="-0.5"]; 7 | Z [x=0, y="-1"]; 8 | Ux -> X; 9 | X -> Y; 10 | Uy -> Y; 11 | Y -> Z; 12 | Uz -> Z; 13 | } 14 | -------------------------------------------------------------------------------- /dags/Primer.Fig.2.2.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | Ux [x=0, y=2]; 3 | X [x=0, y=1]; 4 | Uy [x="-1", y=1]; 5 | Y [x="-1", y=0]; 6 | Uz [x=1, y=1]; 7 | Z [x=1, y=0]; 8 | Ux -> X; 9 | X -> Y; 10 | X -> Z; 11 | Uy -> Y; 12 | Uz -> Z; 13 | } 14 | -------------------------------------------------------------------------------- /dags/Primer.Fig.2.3.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | Ux [x="-1", y=1]; 3 | X [x="-1", y=0]; 4 | Uy [x=1, y=1]; 5 | Y [x=1, y=0]; 6 | Uz [x=0, y=0]; 7 | Z [x=0, y="-1"]; 8 | Ux -> X; 9 | X -> Z; 10 | Uy -> Y; 11 | Y -> Z; 12 | Uz -> Z; 13 | } 14 | -------------------------------------------------------------------------------- /dags/Primer.Fig.2.5.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | X [x=0, y=0]; 3 | R [x=1, y=0]; 4 | S [x=2, y=0]; 5 | T [x=3, y=0]; 6 | U [x=4, y=0]; 7 | V [x=5, y=0]; 8 | Y [x=6, y=0]; 9 | X -> R; 10 | R -> S; 11 | S -> T; 12 | U -> T; 13 | V -> U; 14 | V -> Y; 15 | } 16 | -------------------------------------------------------------------------------- /dags/Primer.Fig.2.6.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | X [x=0, y=0]; 3 | R [x=1, y=0]; 4 | S [x=2, y=0]; 5 | T [x=3, y=0]; 6 | U [x=4, y=0]; 7 | V [x=5, y=0]; 8 | Y [x=6, y=0]; 9 | P [x=3, y="-1"]; 10 | X -> R; 11 | R -> S; 12 | S -> T; 13 | T -> P; 14 | U -> T; 15 | V -> U; 16 | V -> Y; 17 | } 18 | -------------------------------------------------------------------------------- /dags/Primer.Fig.2.7.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | Uz [x="-1", y="1.5"]; 3 | Z [x="-1", y=1]; 4 | Uw [x=0, y="0.5"]; 5 | W [x=0, y=0]; 6 | Ux [x=1, y="1.5"]; 7 | X [x=1, y=1]; 8 | Uy [x=2, y="1.5"]; 9 | Y [x=2, y=1]; 10 | Uu [x="-0.5", y="-0.5"]; 11 | U [x=0, y="-1"]; 12 | Uz -> Z; 13 | Z -> W; 14 | Uw -> W; 15 | W -> U; 16 | Ux -> X; 17 | X -> W; 18 | X -> Y; 19 | Uy -> Y; 20 | Uu -> U; 21 | } 22 | -------------------------------------------------------------------------------- /dags/Primer.Fig.2.8.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | Uz [x="-1", y="1.5"]; 3 | Z [x="-1", y=1]; 4 | Uw [x=0, y="0.5"]; 5 | W [x=0, y=0]; 6 | Ux [x=1, y="1.5"]; 7 | X [x=1, y=1]; 8 | Uy [x=2, y="1.5"]; 9 | Y [x=2, y=1]; 10 | Uu [x="-0.5", y="-0.5"]; 11 | U [x=0, y="-1"]; 12 | Ut [x="0.5", y="2.5"]; 13 | T [x="0.5", y=2]; 14 | Uz -> Z; 15 | Z -> W; 16 | Uw -> W; 17 | W -> U; 18 | Ux -> X; 19 | X -> W; 20 | X -> Y; 21 | Uy -> Y; 22 | Uu -> U; 23 | Ut -> T; 24 | T -> Z; 25 | T -> Y; 26 | } 27 | -------------------------------------------------------------------------------- /dags/Primer.Fig.2.9.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | Z1 [x="-1", y=1]; 3 | Z3 [x=0, y=0]; 4 | X [x="-1", y="-1"]; 5 | Z2 [x=1, y=1]; 6 | Y [x=1, y="-1"]; 7 | W [x=0, y="-1"]; 8 | Z1 -> Z3; 9 | Z1 -> X; 10 | Z3 -> X; 11 | Z3 -> Y; 12 | X -> W; 13 | Z2 -> Z3; 14 | Z2 -> Y; 15 | W -> Y; 16 | } 17 | -------------------------------------------------------------------------------- /dags/Primer.Fig.3.1.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | Ux [x="-1", y=1]; 3 | X [x="-1", y=0]; 4 | Uy [x=1, y=1]; 5 | Y [x=1, y=0]; 6 | Uz [x=0, y=2]; 7 | Z [x=0, y=1]; 8 | Ux -> X; 9 | Uy -> Y; 10 | Uz -> Z; 11 | Z -> X; 12 | Z -> Y; 13 | } 14 | -------------------------------------------------------------------------------- /dags/Primer.Fig.3.10b.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | X [x=0, y=0]; 3 | Z [x="0.5", y=0]; 4 | Y [x=1, y=0]; 5 | U [x="0.5", y=1]; 6 | X -> Z; 7 | Z -> Y; 8 | U -> X; 9 | U -> Y; 10 | } 11 | -------------------------------------------------------------------------------- /dags/Primer.Fig.3.11.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | X [x=0, y=0]; 3 | Y [x=2, y=0]; 4 | Z [x=1, y=1]; 5 | X -> Y; 6 | X -> Z; 7 | Z -> Y; 8 | } 9 | -------------------------------------------------------------------------------- /dags/Primer.Fig.3.12.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | X [x=0, y=0]; 3 | Y [x=2, y=0]; 4 | Z [x=1, y=1]; 5 | I [x=2, y=1]; 6 | X -> Y; 7 | X -> Z; 8 | Z -> Y; 9 | I -> Y; 10 | I -> Z; 11 | } 12 | -------------------------------------------------------------------------------- /dags/Primer.Fig.3.2.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | X [x="-1", y=0]; 3 | Uy [x=1, y=1]; 4 | Y [x=1, y=0]; 5 | Uz [x=0, y=2]; 6 | Z [x=0, y=1]; 7 | Uy -> Y; 8 | Uz -> Z; 9 | Z -> Y; 10 | } 11 | -------------------------------------------------------------------------------- /dags/Primer.Fig.3.3.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | Ux [x="-1", y=1]; 3 | X [x="-1", y=0]; 4 | Uy [x=1, y=1]; 5 | Y [x=1, y=0]; 6 | Uz [x=0, y=2]; 7 | Z [x=0, y=1]; 8 | Ux -> X; 9 | X -> Y; 10 | Uy -> Y; 11 | Uz -> Z; 12 | Z -> X; 13 | Z -> Y; 14 | } 15 | -------------------------------------------------------------------------------- /dags/Primer.Fig.3.4.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | Ux [x="-1", y=1]; 3 | X [x="-1", y=0]; 4 | Uy [x=1, y=1]; 5 | Y [x=1, y=0]; 6 | Uz [x=0, y=2]; 7 | Z [x=0, y=1]; 8 | Ux -> X; 9 | X -> Y; 10 | Uy -> Y; 11 | Uz -> Z; 12 | Z -> X; 13 | Z -> Y; 14 | } 15 | -------------------------------------------------------------------------------- /dags/Primer.Fig.3.5.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | X [x="-1", y=0]; 3 | Z [x=0, y=1]; 4 | Y [x=1, y=0]; 5 | X -> Z; 6 | X -> Y; 7 | Z -> Y; 8 | } 9 | -------------------------------------------------------------------------------- /dags/Primer.Fig.3.6.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | X [x=0, y=0]; 3 | Y [x=1, y=0]; 4 | W [x=1, y=1]; 5 | Z [x=0, y=1]; 6 | X -> Y; 7 | W -> Y; 8 | Z -> X; 9 | Z -> W; 10 | } 11 | -------------------------------------------------------------------------------- /dags/Primer.Fig.3.7.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | E [x="-1", y=2]; 3 | Z [x=1, y=1]; 4 | X [x=0, y=0]; 5 | A [x=3, y=2]; 6 | Y [x=2, y=0]; 7 | E -> Z; 8 | E -> X; 9 | Z -> X; 10 | Z -> Y; 11 | X -> Y; 12 | A -> Z; 13 | A -> Y; 14 | } 15 | -------------------------------------------------------------------------------- /dags/Primer.Fig.3.8.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | B [x="-1", y=1]; 3 | A [x="-1", y=0]; 4 | Z [x=0, y=0]; 5 | C [x=1, y=1]; 6 | D [x=1, y=0]; 7 | X [x="-1", y="-1"]; 8 | W [x=0, y="-1"]; 9 | Y [x=1, y="-1"]; 10 | B -> A; 11 | B -> Z; 12 | A -> X; 13 | Z -> X; 14 | Z -> Y; 15 | C -> Z; 16 | C -> D; 17 | D -> Y; 18 | X -> W; 19 | W -> Y; 20 | } 21 | -------------------------------------------------------------------------------- /dags/Primer.SCM.1.5.3.dot: -------------------------------------------------------------------------------- 1 | strict digraph { 2 | "$U_X$" [x="-1", y=1]; 3 | X [x="-1", y=0]; 4 | "$U_Y$" [x=0, y=1]; 5 | Y [x=0, y=0]; 6 | "$U_Z$" [x=1, y=1]; 7 | Z [x=1, y=0]; 8 | "$U_X$" -> X; 9 | X -> Y; 10 | "$U_Y$" -> Y; 11 | Y -> Z; 12 | "$U_Z$" -> Z; 13 | } 14 | -------------------------------------------------------------------------------- /data/D4Sci_logo_ball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataForScience/Causality/f7b1abb57541776dc786174deb102ca4ab864df6/data/D4Sci_logo_ball.png -------------------------------------------------------------------------------- /data/D4Sci_logo_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataForScience/Causality/f7b1abb57541776dc786174deb102ca4ab864df6/data/D4Sci_logo_full.png -------------------------------------------------------------------------------- /data/book2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataForScience/Causality/f7b1abb57541776dc786174deb102ca4ab864df6/data/book2.jpeg -------------------------------------------------------------------------------- /data/causality.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataForScience/Causality/f7b1abb57541776dc786174deb102ca4ab864df6/data/causality.jpeg -------------------------------------------------------------------------------- /data/iris.csv: -------------------------------------------------------------------------------- 1 | sepal_length,sepal_width,petal_length,petal_width,species 2 | 5.1,3.5,1.4,0.2,setosa 3 | 4.9,3.0,1.4,0.2,setosa 4 | 4.7,3.2,1.3,0.2,setosa 5 | 4.6,3.1,1.5,0.2,setosa 6 | 5.0,3.6,1.4,0.2,setosa 7 | 5.4,3.9,1.7,0.4,setosa 8 | 4.6,3.4,1.4,0.3,setosa 9 | 5.0,3.4,1.5,0.2,setosa 10 | 4.4,2.9,1.4,0.2,setosa 11 | 4.9,3.1,1.5,0.1,setosa 12 | 5.4,3.7,1.5,0.2,setosa 13 | 4.8,3.4,1.6,0.2,setosa 14 | 4.8,3.0,1.4,0.1,setosa 15 | 4.3,3.0,1.1,0.1,setosa 16 | 5.8,4.0,1.2,0.2,setosa 17 | 5.7,4.4,1.5,0.4,setosa 18 | 5.4,3.9,1.3,0.4,setosa 19 | 5.1,3.5,1.4,0.3,setosa 20 | 5.7,3.8,1.7,0.3,setosa 21 | 5.1,3.8,1.5,0.3,setosa 22 | 5.4,3.4,1.7,0.2,setosa 23 | 5.1,3.7,1.5,0.4,setosa 24 | 4.6,3.6,1.0,0.2,setosa 25 | 5.1,3.3,1.7,0.5,setosa 26 | 4.8,3.4,1.9,0.2,setosa 27 | 5.0,3.0,1.6,0.2,setosa 28 | 5.0,3.4,1.6,0.4,setosa 29 | 5.2,3.5,1.5,0.2,setosa 30 | 5.2,3.4,1.4,0.2,setosa 31 | 4.7,3.2,1.6,0.2,setosa 32 | 4.8,3.1,1.6,0.2,setosa 33 | 5.4,3.4,1.5,0.4,setosa 34 | 5.2,4.1,1.5,0.1,setosa 35 | 5.5,4.2,1.4,0.2,setosa 36 | 4.9,3.1,1.5,0.2,setosa 37 | 5.0,3.2,1.2,0.2,setosa 38 | 5.5,3.5,1.3,0.2,setosa 39 | 4.9,3.6,1.4,0.1,setosa 40 | 4.4,3.0,1.3,0.2,setosa 41 | 5.1,3.4,1.5,0.2,setosa 42 | 5.0,3.5,1.3,0.3,setosa 43 | 4.5,2.3,1.3,0.3,setosa 44 | 4.4,3.2,1.3,0.2,setosa 45 | 5.0,3.5,1.6,0.6,setosa 46 | 5.1,3.8,1.9,0.4,setosa 47 | 4.8,3.0,1.4,0.3,setosa 48 | 5.1,3.8,1.6,0.2,setosa 49 | 4.6,3.2,1.4,0.2,setosa 50 | 5.3,3.7,1.5,0.2,setosa 51 | 5.0,3.3,1.4,0.2,setosa 52 | 7.0,3.2,4.7,1.4,versicolor 53 | 6.4,3.2,4.5,1.5,versicolor 54 | 6.9,3.1,4.9,1.5,versicolor 55 | 5.5,2.3,4.0,1.3,versicolor 56 | 6.5,2.8,4.6,1.5,versicolor 57 | 5.7,2.8,4.5,1.3,versicolor 58 | 6.3,3.3,4.7,1.6,versicolor 59 | 4.9,2.4,3.3,1.0,versicolor 60 | 6.6,2.9,4.6,1.3,versicolor 61 | 5.2,2.7,3.9,1.4,versicolor 62 | 5.0,2.0,3.5,1.0,versicolor 63 | 5.9,3.0,4.2,1.5,versicolor 64 | 6.0,2.2,4.0,1.0,versicolor 65 | 6.1,2.9,4.7,1.4,versicolor 66 | 5.6,2.9,3.6,1.3,versicolor 67 | 6.7,3.1,4.4,1.4,versicolor 68 | 5.6,3.0,4.5,1.5,versicolor 69 | 5.8,2.7,4.1,1.0,versicolor 70 | 6.2,2.2,4.5,1.5,versicolor 71 | 5.6,2.5,3.9,1.1,versicolor 72 | 5.9,3.2,4.8,1.8,versicolor 73 | 6.1,2.8,4.0,1.3,versicolor 74 | 6.3,2.5,4.9,1.5,versicolor 75 | 6.1,2.8,4.7,1.2,versicolor 76 | 6.4,2.9,4.3,1.3,versicolor 77 | 6.6,3.0,4.4,1.4,versicolor 78 | 6.8,2.8,4.8,1.4,versicolor 79 | 6.7,3.0,5.0,1.7,versicolor 80 | 6.0,2.9,4.5,1.5,versicolor 81 | 5.7,2.6,3.5,1.0,versicolor 82 | 5.5,2.4,3.8,1.1,versicolor 83 | 5.5,2.4,3.7,1.0,versicolor 84 | 5.8,2.7,3.9,1.2,versicolor 85 | 6.0,2.7,5.1,1.6,versicolor 86 | 5.4,3.0,4.5,1.5,versicolor 87 | 6.0,3.4,4.5,1.6,versicolor 88 | 6.7,3.1,4.7,1.5,versicolor 89 | 6.3,2.3,4.4,1.3,versicolor 90 | 5.6,3.0,4.1,1.3,versicolor 91 | 5.5,2.5,4.0,1.3,versicolor 92 | 5.5,2.6,4.4,1.2,versicolor 93 | 6.1,3.0,4.6,1.4,versicolor 94 | 5.8,2.6,4.0,1.2,versicolor 95 | 5.0,2.3,3.3,1.0,versicolor 96 | 5.6,2.7,4.2,1.3,versicolor 97 | 5.7,3.0,4.2,1.2,versicolor 98 | 5.7,2.9,4.2,1.3,versicolor 99 | 6.2,2.9,4.3,1.3,versicolor 100 | 5.1,2.5,3.0,1.1,versicolor 101 | 5.7,2.8,4.1,1.3,versicolor 102 | 6.3,3.3,6.0,2.5,virginica 103 | 5.8,2.7,5.1,1.9,virginica 104 | 7.1,3.0,5.9,2.1,virginica 105 | 6.3,2.9,5.6,1.8,virginica 106 | 6.5,3.0,5.8,2.2,virginica 107 | 7.6,3.0,6.6,2.1,virginica 108 | 4.9,2.5,4.5,1.7,virginica 109 | 7.3,2.9,6.3,1.8,virginica 110 | 6.7,2.5,5.8,1.8,virginica 111 | 7.2,3.6,6.1,2.5,virginica 112 | 6.5,3.2,5.1,2.0,virginica 113 | 6.4,2.7,5.3,1.9,virginica 114 | 6.8,3.0,5.5,2.1,virginica 115 | 5.7,2.5,5.0,2.0,virginica 116 | 5.8,2.8,5.1,2.4,virginica 117 | 6.4,3.2,5.3,2.3,virginica 118 | 6.5,3.0,5.5,1.8,virginica 119 | 7.7,3.8,6.7,2.2,virginica 120 | 7.7,2.6,6.9,2.3,virginica 121 | 6.0,2.2,5.0,1.5,virginica 122 | 6.9,3.2,5.7,2.3,virginica 123 | 5.6,2.8,4.9,2.0,virginica 124 | 7.7,2.8,6.7,2.0,virginica 125 | 6.3,2.7,4.9,1.8,virginica 126 | 6.7,3.3,5.7,2.1,virginica 127 | 7.2,3.2,6.0,1.8,virginica 128 | 6.2,2.8,4.8,1.8,virginica 129 | 6.1,3.0,4.9,1.8,virginica 130 | 6.4,2.8,5.6,2.1,virginica 131 | 7.2,3.0,5.8,1.6,virginica 132 | 7.4,2.8,6.1,1.9,virginica 133 | 7.9,3.8,6.4,2.0,virginica 134 | 6.4,2.8,5.6,2.2,virginica 135 | 6.3,2.8,5.1,1.5,virginica 136 | 6.1,2.6,5.6,1.4,virginica 137 | 7.7,3.0,6.1,2.3,virginica 138 | 6.3,3.4,5.6,2.4,virginica 139 | 6.4,3.1,5.5,1.8,virginica 140 | 6.0,3.0,4.8,1.8,virginica 141 | 6.9,3.1,5.4,2.1,virginica 142 | 6.7,3.1,5.6,2.4,virginica 143 | 6.9,3.1,5.1,2.3,virginica 144 | 5.8,2.7,5.1,1.9,virginica 145 | 6.8,3.2,5.9,2.3,virginica 146 | 6.7,3.3,5.7,2.5,virginica 147 | 6.7,3.0,5.2,2.3,virginica 148 | 6.3,2.5,5.0,1.9,virginica 149 | 6.5,3.0,5.2,2.0,virginica 150 | 6.2,3.4,5.4,2.3,virginica 151 | 5.9,3.0,5.1,1.8,virginica 152 | -------------------------------------------------------------------------------- /data/newsletter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataForScience/Causality/f7b1abb57541776dc786174deb102ca4ab864df6/data/newsletter.png -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - matplotlib==3.1.3 3 | - networkx==2.4 4 | - numpy==1.18.1 5 | - pandas==1.0.1 6 | - requests=2.21.0 7 | - scikit-learn==0.22.1 8 | - statsmodels==0.11.1 9 | - tqdm==4.46.0 10 | - watermark==2.0.2 11 | - pygraphviz==1.5 12 | - pydot==1.4.1 --------------------------------------------------------------------------------