From 8265f640f633b94a64c9866107ee2166455bfab3 Mon Sep 17 00:00:00 2001 From: Angel Droth <67913551+angeldrothqb@users.noreply.github.com> Date: Thu, 10 Sep 2020 13:45:06 +0100 Subject: [PATCH] Release/0.8.0 (#80) * Merge back to develop * Simplifying viz.draw syntax in tutorial notebook (#46) * Add non negativity constraint in numpy lasso (#41) * Add plotting tutorial to the documentation (#47) * Unpin some requirements * Mixed type data generation (#55) Added DAG-based synthetic data generator for mixed types (binary, categorical, continuous) using a linear SEM approach. * Merge back to develop (#59) * Pytorch NOTEARS (#63) * NoTears as ScoreSolver * refactor continuous solver * adding attribute to access weight matrix * refactoring continuous solver * Adding fit_lasso method * add data_gen_continuous.py and tests (#38) * add data_gen.py * rename * wrap SM * move data_gen_continous, create test * more coverage * test fixes * move discrete sem to another file * node list dupe check test * ValueError tests * replace dag and sem functions with Ben's verions * add Ben's tests * fix fstring * to_numpy_array coverage * Ben's comments * remove unreachable ValueError for coverage * remove unused fixture * remove redundant test * remove extensions Co-Authored-By: Ben Horsburgh * docstring Co-Authored-By: Ben Horsburgh * docstring Co-Authored-By: Ben Horsburgh * docs Co-Authored-By: Ben Horsburgh * doc Co-Authored-By: Ben Horsburgh * rename file, g_dag rename to sm * add new tests for equal weights * docstring * steve docstring, leq fix * steve comments + docstrings Co-authored-by: Ben Horsburgh * Adding check input and removing some inner functions * Removing attribute original_ndarray * Aligning from pandas with new implementation * Adding tests for fit_lasso * More tests for lasso * wrapping tabu params in a dict * Aligning tests with new tabu params * Aligning from_pandas with new tabu_params * Adding fit_intercept option to _fit method * Adding scaling option * fixing lasso tests * Adding a test for fit_intercept * scaling option only with mean * Correction in lasso bounds * Fix typos * Remove duplicated bounds function * adding comments * add torch files from xunzheng * add from_numpy_torch function that works like from_numpy_lasso * lint * add requirements * add debug functionality * add visual debug test * add license * allow running as main for viz, comments * move to contrib * make multi layer work a bit better * add comment for multi layer * use polynomial dag constraint for better speed comparison * revert unnecessary changes to keep PR lean * revert unnecessary changes to keep PR lean * revert unnecessary changes to keep PR lean * fixes * refactor * Integrated tests * Checkpoint * Refactoring * Finished initial refactoring * All tests passed * Cleaning * Git add testing * Get adjacency matrix * Done cleaning * Revert change to original notears * Revert change to original structuremodel * Revert change to pylintrc * Undo deletion * Apply suggestions from Zain Co-authored-by: Zain Patel * Addressed Zain comments * Migrated from_numpy * Delete contrib test * Migrated w_threshold * Some linting * Change to None * Undo deletion * List comprehension * Refactoring scipy and remove scipy optimiser * Refactoring * Refactoring * Refactoring complete * change from np to torch tensor * More refactoring * Remove hnew equal to None * Refactor again and remove commented line * Minor change * change to params * Addressing Philip's comment * Add property * Add fc2 property weights * Change to weights * Docstring * Linting * Linting completed * Add gpu code * Add gpu to from_numpy and from_pandas * cuda 0 run out of memory * Debugging * put 5 * debugging gpu * shift to inner loop * debugging not in place * Use cada instead of to * Support both interfaces * Benchmarking gpu * Minor fix * correct import path for test * change gpu from 5 to 1 * Debugging * Debugging * Experimenting * Linting * Remove hidden layer and gpu * Linting * Testing and linting * Correct pytorch to torch * Add init zeros * Change weight threshold to 0.25 * Revert requirements.txt * Update release.md * Address coments * Corrected release.md * fc1 to adjacency Co-authored-by: Ben Horsburgh Co-authored-by: LiseDiagneQB <60981366+LiseDiagneQB@users.noreply.github.com> Co-authored-by: Casey Juanxi Li <50737712+caseyliqb@users.noreply.github.com> Co-authored-by: qbphilip Co-authored-by: Zain Patel * Pinned sphinx-auto-doc-typehints (#66) * Corrected a spelling/grammar mistake (#55) * Fix/lint (#73) * Hotfix/0.4.3 (#7) - Address broken links and grammar * Fix documentation links in README (#2) * Fix links in README * library -> libraries * Fix github link in docs * Clean up grammar and consistency in documentation (#4) * Clean up grammar and consistency in `README` files * Add esses, mostly * Reword feature description to not appear automatic * Update docs/source/05_resources/05_faq.md Co-Authored-By: Ben Horsburgh Co-authored-by: Ben Horsburgh * hotfix/0.4.3: fix broken links Co-authored-by: Zain Patel <30357972+mzjp2@users.noreply.github.com> Co-authored-by: Nikos Tsaousis Co-authored-by: Deepyaman Datta * Release/0.5.0 * Plotting now backed by pygraphviz. This allows: * More powerful layout manager * Cleaner fully customisable theme * Out-the-box styling for different node and edge types * Can now get subgraphs from StructureModel containing a specific node * Bugfix to resolve issue when fitting CPDs with some missing states in data * Minor documentation fixes and improvements * Release/0.6.0 * Release/0.7.0 (#57) * Added plottting tutorial to the documentation * Updated `viz.draw` syntax in tutorial notebooks * Bugfix on notears lasso (`from_numpy_lasso` and `from_pandas_lasso`) where the non-negativity constraint was not being set * Added DAG-based synthetic data generator for mixed types (binary, categorical, continuous) using a linear SEM approach. * Unpinned some requirements * black * pin pytorch version * pin pytorch version Co-authored-by: Ben Horsburgh Co-authored-by: Zain Patel <30357972+mzjp2@users.noreply.github.com> Co-authored-by: Nikos Tsaousis Co-authored-by: Deepyaman Datta * Structure learning regressor (#68) * initial commit (local copy-paste) * fixed minor comments * minor bugfix * impute from children inital commit * bugfixes and method option * auto thresholding * autothreshold and bugfix * make threshold removal explicit * add l1 argument * remove child imputation * feat importance fix and tabu logic * moved threshold till dag * restructure with base class * coef mask * recipe * enable bias fitting * persist bias as node attribute * allow fit_intercept * minor PR comment fixes * minor comment adjustment * test coverage and l1 clarification * recipe * minor test fixes * more tests * full test coverage * revove python 3.5/3.6 unsupported import * add normalization option * idiomatic typing * correct pylint errors * update some tests * more typeing updates * more pylint requirements * more pylint disable * python 3.5 support * try to get to work with 3.5 * full coverage and 3.5 support * remove base class to pass test * remove unneeded supression * black formatting changes * remove unused import * pytlint supression * minor reformat change * isort fix * better defensive programming * fix unittests * docstring update * do Raises docstring properly * action SWE suggestions * hotfixes * minor update * minor black formatting change * final merge checkbox * fix end of file * Data Gen root node initialisation fix (#72) * Hotfix/0.4.3 (#7) - Address broken links and grammar * Fix documentation links in README (#2) * Fix links in README * library -> libraries * Fix github link in docs * Clean up grammar and consistency in documentation (#4) * Clean up grammar and consistency in `README` files * Add esses, mostly * Reword feature description to not appear automatic * Update docs/source/05_resources/05_faq.md Co-Authored-By: Ben Horsburgh Co-authored-by: Ben Horsburgh * hotfix/0.4.3: fix broken links Co-authored-by: Zain Patel <30357972+mzjp2@users.noreply.github.com> Co-authored-by: Nikos Tsaousis Co-authored-by: Deepyaman Datta * Release/0.5.0 * Plotting now backed by pygraphviz. This allows: * More powerful layout manager * Cleaner fully customisable theme * Out-the-box styling for different node and edge types * Can now get subgraphs from StructureModel containing a specific node * Bugfix to resolve issue when fitting CPDs with some missing states in data * Minor documentation fixes and improvements * Release/0.6.0 * Release/0.7.0 (#57) * Added plottting tutorial to the documentation * Updated `viz.draw` syntax in tutorial notebooks * Bugfix on notears lasso (`from_numpy_lasso` and `from_pandas_lasso`) where the non-negativity constraint was not being set * Added DAG-based synthetic data generator for mixed types (binary, categorical, continuous) using a linear SEM approach. * Unpinned some requirements * fix for consinuous normal data * generalise across all dtypes * support fit_intercept * fixed many test errors * test logic fixes * lint test fixes * python 3.5 failure change * minor test bugfix * black * pin pytorch version * pin pytorch version * additional test parameter * black formatting * requested changes * test updates and docstring * black format change * disable too many lines * change * move recipe to tutorial folder * releaseMD changes Co-authored-by: Ben Horsburgh Co-authored-by: Zain Patel <30357972+mzjp2@users.noreply.github.com> Co-authored-by: Nikos Tsaousis Co-authored-by: Deepyaman Datta Co-authored-by: Philip Pilgerstorfer <34248114+qbphilip@users.noreply.github.com> Co-authored-by: qbphilip * [1/2] Poisson data for data gen (#61) * Hotfix/0.4.3 (#7) - Address broken links and grammar * Fix documentation links in README (#2) * Fix links in README * library -> libraries * Fix github link in docs * Clean up grammar and consistency in documentation (#4) * Clean up grammar and consistency in `README` files * Add esses, mostly * Reword feature description to not appear automatic * Update docs/source/05_resources/05_faq.md Co-Authored-By: Ben Horsburgh Co-authored-by: Ben Horsburgh * hotfix/0.4.3: fix broken links Co-authored-by: Zain Patel <30357972+mzjp2@users.noreply.github.com> Co-authored-by: Nikos Tsaousis Co-authored-by: Deepyaman Datta * Release/0.5.0 * Plotting now backed by pygraphviz. This allows: * More powerful layout manager * Cleaner fully customisable theme * Out-the-box styling for different node and edge types * Can now get subgraphs from StructureModel containing a specific node * Bugfix to resolve issue when fitting CPDs with some missing states in data * Minor documentation fixes and improvements * Release/0.6.0 * Release/0.7.0 (#57) * Added plottting tutorial to the documentation * Updated `viz.draw` syntax in tutorial notebooks * Bugfix on notears lasso (`from_numpy_lasso` and `from_pandas_lasso`) where the non-negativity constraint was not being set * Added DAG-based synthetic data generator for mixed types (binary, categorical, continuous) using a linear SEM approach. * Unpinned some requirements * refactor & docstring * remove unused helper object * add data gen to init * make test more robust * add count data and test, use logs for poisson samples for stability * fix tests * duplicate fixtures * remove unused fixtures * refactor data_generators into package with core and wrappers * move wrapper to test_wrapper * variable name change bugfix * fix tests Co-authored-by: Ben Horsburgh Co-authored-by: Zain Patel <30357972+mzjp2@users.noreply.github.com> Co-authored-by: Nikos Tsaousis Co-authored-by: Deepyaman Datta Co-authored-by: angeldrothqb * [2/2] Nonlinear Data gen (#60) * Hotfix/0.4.3 (#7) - Address broken links and grammar * Fix documentation links in README (#2) * Fix links in README * library -> libraries * Fix github link in docs * Clean up grammar and consistency in documentation (#4) * Clean up grammar and consistency in `README` files * Add esses, mostly * Reword feature description to not appear automatic * Update docs/source/05_resources/05_faq.md Co-Authored-By: Ben Horsburgh Co-authored-by: Ben Horsburgh * hotfix/0.4.3: fix broken links Co-authored-by: Zain Patel <30357972+mzjp2@users.noreply.github.com> Co-authored-by: Nikos Tsaousis Co-authored-by: Deepyaman Datta * Release/0.5.0 * Plotting now backed by pygraphviz. This allows: * More powerful layout manager * Cleaner fully customisable theme * Out-the-box styling for different node and edge types * Can now get subgraphs from StructureModel containing a specific node * Bugfix to resolve issue when fitting CPDs with some missing states in data * Minor documentation fixes and improvements * Release/0.6.0 * Release/0.7.0 (#57) * Added plottting tutorial to the documentation * Updated `viz.draw` syntax in tutorial notebooks * Bugfix on notears lasso (`from_numpy_lasso` and `from_pandas_lasso`) where the non-negativity constraint was not being set * Added DAG-based synthetic data generator for mixed types (binary, categorical, continuous) using a linear SEM approach. * Unpinned some requirements * refactor & docstring * remove unused helper object * add data gen to init * make test more robust * add count data and test, use logs for poisson samples for stability * add nonlinear * fix tests * duplicate fixtures * remove unused fixtures * refactor data_generators into package with core and wrappers * move wrapper to test_wrapper * add nonlinear to init * change order in all * change release.md * root node fix on core + count * nonlinear support to wrappers * docstring update * bugfix and reproducability fix * many tests and test updates * poiss bugfix and test fix * moar test coverage * categorical dataframe test coverage * full test coverage and linting * fix linting and fstring * black reformat * fix unused pylint argument * pytest fix * FINAL linting fix * Fix stuff (#75) CircleCI fixes Co-authored-by: Ben Horsburgh Co-authored-by: Zain Patel <30357972+mzjp2@users.noreply.github.com> Co-authored-by: Nikos Tsaousis Co-authored-by: Deepyaman Datta Co-authored-by: angeldrothqb Co-authored-by: Zain Patel * update black version (#76) * fix black * Fix/check for NA or Infinity when notears is used (#54) * update scipy version (#77) * add DYNOTEARS implementation (#50) Adds DYNOTEARS and corresponding data generator (for testing) * Pytorch NOTEARS extension - Non-Linear/Hidden Layer (#65) * NoTears as ScoreSolver * refactor continuous solver * adding attribute to access weight matrix * refactoring continuous solver * Adding fit_lasso method * add data_gen_continuous.py and tests (#38) * add data_gen.py * rename * wrap SM * move data_gen_continous, create test * more coverage * test fixes * move discrete sem to another file * node list dupe check test * ValueError tests * replace dag and sem functions with Ben's verions * add Ben's tests * fix fstring * to_numpy_array coverage * Ben's comments * remove unreachable ValueError for coverage * remove unused fixture * remove redundant test * remove extensions Co-Authored-By: Ben Horsburgh * docstring Co-Authored-By: Ben Horsburgh * docstring Co-Authored-By: Ben Horsburgh * docs Co-Authored-By: Ben Horsburgh * doc Co-Authored-By: Ben Horsburgh * rename file, g_dag rename to sm * add new tests for equal weights * docstring * steve docstring, leq fix * steve comments + docstrings Co-authored-by: Ben Horsburgh * Adding check input and removing some inner functions * Removing attribute original_ndarray * Aligning from pandas with new implementation * Adding tests for fit_lasso * More tests for lasso * wrapping tabu params in a dict * Aligning tests with new tabu params * Aligning from_pandas with new tabu_params * Adding fit_intercept option to _fit method * Adding scaling option * fixing lasso tests * Adding a test for fit_intercept * scaling option only with mean * Correction in lasso bounds * Fix typos * Remove duplicated bounds function * adding comments * add torch files from xunzheng * add from_numpy_torch function that works like from_numpy_lasso * lint * add requirements * add debug functionality * add visual debug test * add license * allow running as main for viz, comments * move to contrib * make multi layer work a bit better * add comment for multi layer * use polynomial dag constraint for better speed comparison * revert unnecessary changes to keep PR lean * revert unnecessary changes to keep PR lean * revert unnecessary changes to keep PR lean * fixes * refactor * Integrated tests * Checkpoint * Refactoring * Finished initial refactoring * All tests passed * Cleaning * Git add testing * Get adjacency matrix * Done cleaning * Revert change to original notears * Revert change to original structuremodel * Revert change to pylintrc * Undo deletion * Apply suggestions from Zain Co-authored-by: Zain Patel * Addressed Zain comments * Migrated from_numpy * Delete contrib test * Migrated w_threshold * Some linting * Change to None * Undo deletion * List comprehension * Refactoring scipy and remove scipy optimiser * Refactoring * Refactoring * Refactoring complete * change from np to torch tensor * More refactoring * Remove hnew equal to None * Refactor again and remove commented line * Minor change * change to params * Addressing Philip's comment * Add property * Add fc2 property weights * Change to weights * Docstring * Linting * Linting completed * Add gpu code * Add gpu to from_numpy and from_pandas * cuda 0 run out of memory * Debugging * put 5 * debugging gpu * shift to inner loop * debugging not in place * Use cada instead of to * Support both interfaces * Benchmarking gpu * Minor fix * correct import path for test * change gpu from 5 to 1 * Debugging * Debugging * Experimenting * Linting * Remove hidden layer and gpu * Linting * Testing and linting * Correct pytorch to torch * Add init zeros * Change weight threshold to 0.25 * Revert requirements.txt * Add hidden layer * small refactor * directional adj * minor edits * fix bias issues * breaking changes update to the interface * typo * new regressor regularisation interface * update forward method * forward(X) predictions work * working! * bugfix data normalisation * some fixes * average regularisation and adj calc at end * give credit! Co-authored-by: Philip Pilgerstorfer <34248114+qbphilip@users.noreply.github.com> * loc lin docstring update Co-authored-by: Philip Pilgerstorfer <34248114+qbphilip@users.noreply.github.com> * docstring + fc1/fc2 name updates * moar docstring updates * more minor updates * remove normalize option * plotting util * rename to DAGRegressor * rename and checks * more util functions * fix bias * fix bias with no intercept * fix linear adj * add tests * minor fix * minor fixes * extend interface to bias * differentialte coef_ and feature_imporances * seperate bias element * tests * more test coverage * nonlinear test coverage * test hotfix * more test coverage * test requirements update * more test coverage * formatting changes * final pylint change * more linting * more bestpractice structuring * more minor fixes * FINAL linting updates * actual last change * update to reg defaults, additions to the tutorial * nonlinear regularisation updates * regressor tutorial * almost finishing touches * gradient based h function! * soft clamp and coef feature importance seperation * small api update, closer to batchnorm * docstring updates * stronger soft clamping * gradient L1 rather than L2 * fcpos neg removal, gradient optim * revert back to create_graph=True for 2nd derivative * remove print and test fix * black reformatting * new black version * full test coverage * isort fix * pylint fix * first layer h(W) for speed optimization * fix batch norm system * add nonlinear test * test hotfix * black reformat * isort fix * remove X requirement from h_func * regressor tutorial final commit and black update * LayerNorm replacement Co-authored-by: Philip Pilgerstorfer <34248114+qbphilip@users.noreply.github.com> * major changes * add standardization * minort changes * fix tests * rename reg parameters * linting * test coverage, docstting * check array for infs * fix isinstance to base type * fix isort, add test coverage * new tutorial * docstring fix Co-authored-by: Zain Patel * test string match Co-authored-by: Zain Patel * assert improvement Co-authored-by: Zain Patel * SWE suggestions * minor bugfix * more test fixing Co-authored-by: Ben Horsburgh Co-authored-by: LiseDiagneQB <60981366+LiseDiagneQB@users.noreply.github.com> Co-authored-by: Casey Juanxi Li <50737712+caseyliqb@users.noreply.github.com> Co-authored-by: qbphilip Co-authored-by: Zain Patel Co-authored-by: angeldrothqb Co-authored-by: angeldrothqb <67913551+angeldrothqb@users.noreply.github.com> Co-authored-by: Philip Pilgerstorfer <34248114+qbphilip@users.noreply.github.com> * release.md, version bump, docs Co-authored-by: Ben Horsburgh Co-authored-by: GabrielAzevedoFerreiraQB <57528979+GabrielAzevedoFerreiraQB@users.noreply.github.com> Co-authored-by: Philip Pilgerstorfer <34248114+qbphilip@users.noreply.github.com> Co-authored-by: stevelersl <55385183+SteveLerQB@users.noreply.github.com> Co-authored-by: LiseDiagneQB <60981366+LiseDiagneQB@users.noreply.github.com> Co-authored-by: Casey Juanxi Li <50737712+caseyliqb@users.noreply.github.com> Co-authored-by: qbphilip Co-authored-by: Zain Patel Co-authored-by: KING-SID Co-authored-by: Zain Patel <30357972+mzjp2@users.noreply.github.com> Co-authored-by: Nikos Tsaousis Co-authored-by: Deepyaman Datta Co-authored-by: Jebq --- .circleci/config.yml | 4 + .pylintrc | 2 +- CONTRIBUTING.md | 2 +- RELEASE.md | 14 +- causalnex/__init__.py | 2 +- causalnex/inference/inference.py | 4 +- causalnex/network/network.py | 124 +-- causalnex/structure/__init__.py | 3 +- .../structure/categorical_variable_mapper.py | 6 +- .../structure/data_generators/__init__.py | 59 ++ .../core.py} | 713 +++++++++++------ .../structure/data_generators/wrappers.py | 712 +++++++++++++++++ causalnex/structure/dynotears.py | 494 ++++++++++++ causalnex/structure/notears.py | 33 + causalnex/structure/pytorch/__init__.py | 36 + causalnex/structure/pytorch/core.py | 469 +++++++++++ causalnex/structure/pytorch/nonlinear.py | 111 +++ causalnex/structure/pytorch/notears.py | 306 ++++++++ causalnex/structure/sklearn.py | 347 ++++++++ causalnex/structure/structuremodel.py | 9 + causalnex/structure/transformers.py | 290 +++++++ doc_requirements.txt | 2 +- docs/conf.py | 6 +- .../03_tutorial/regressor_tutorial.ipynb | 644 +++++++++++++++ docs/source/api_docs/index.rst | 1 + requirements.txt | 2 +- setup.py | 1 + test_requirements.txt | 10 +- tests/conftest.py | 533 ++++++++++++- tests/structure/data_generators/__init__.py | 27 + tests/structure/data_generators/test_core.py | 543 +++++++++++++ .../test_wrappers.py} | 648 +++++++-------- tests/structure/test_dynotears.py | 738 ++++++++++++++++++ tests/structure/test_nonlinear.py | 41 + tests/structure/test_notears.py | 94 ++- tests/structure/test_pytorch_notears.py | 422 ++++++++++ tests/structure/test_sklearn.py | 221 ++++++ tests/structure/test_transformers.py | 153 ++++ tools/license_and_headers.py | 7 +- 39 files changed, 7148 insertions(+), 685 deletions(-) create mode 100644 causalnex/structure/data_generators/__init__.py rename causalnex/structure/{data_generators.py => data_generators/core.py} (51%) create mode 100644 causalnex/structure/data_generators/wrappers.py create mode 100644 causalnex/structure/dynotears.py create mode 100644 causalnex/structure/pytorch/__init__.py create mode 100644 causalnex/structure/pytorch/core.py create mode 100644 causalnex/structure/pytorch/nonlinear.py create mode 100644 causalnex/structure/pytorch/notears.py create mode 100644 causalnex/structure/sklearn.py create mode 100644 causalnex/structure/transformers.py create mode 100644 docs/source/03_tutorial/regressor_tutorial.ipynb create mode 100644 tests/structure/data_generators/__init__.py create mode 100644 tests/structure/data_generators/test_core.py rename tests/structure/{test_data_generators.py => data_generators/test_wrappers.py} (63%) create mode 100644 tests/structure/test_dynotears.py create mode 100644 tests/structure/test_nonlinear.py create mode 100644 tests/structure/test_pytorch_notears.py create mode 100644 tests/structure/test_sklearn.py create mode 100644 tests/structure/test_transformers.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 05428ea..dce8c54 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,12 +27,16 @@ utils: echo ". /home/circleci/miniconda/etc/profile.d/conda.sh" >> $BASH_ENV echo "conda deactivate; conda activate causalnex_env" >> $BASH_ENV + # needed to control numpy multithreading code since circleci gives incorrect CPU counts + echo "export MKL_NUM_THREADS=1 && export OMP_NUM_THREADS=1 && export NUMEXPR_NUM_THREADS=1" >> $BASH_ENV + setup_requirements: &setup_requirements name: Install PIP dependencies command: | echo "Python version: $(python --version 2>&1)" pip install -r requirements.txt -U pip install -r test_requirements.txt -U + pip install ".[pytorch]" conda install -y virtualenv setup_pre_commit: &setup_pre_commit name: Install pre-commit hooks diff --git a/.pylintrc b/.pylintrc index bfb1093..4668a76 100644 --- a/.pylintrc +++ b/.pylintrc @@ -269,7 +269,7 @@ contextmanager-decorators=contextlib.contextmanager # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. -generated-members= +generated-members=torch.* # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b175ca..4c67432 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ The CausalNex team pledges to foster and maintain a welcoming and friendly commu We use [GitHub Issues](https://github.com/quantumblacklabs/causalnex/issues) to keep track of known bugs. We keep a close eye on them and try to make it clear when we have an internal fix in progress. Before reporting a new issue, please do your best to ensure your problem hasn't already been reported. If so, it's often better to just leave a comment on an existing issue, rather than create a new one. Old issues also can often include helpful tips and solutions to common problems. -If you are looking for help with your code in our documentation haven't helped you, please consider posting a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/causalnex). If you tag it `causalnex` and `python`, more people will see it and may be able to help. We are unable to provide individual support via email. In the interest of community engagement we also believe that help is much more valuable if it's shared publicly, so that more people can benefit from it. +If you are looking for help with your code and our documentation hasn't helped you, please consider posting a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/causalnex). If you tag it `causalnex` and `python`, more people will see it and may be able to help. We are unable to provide individual support via email. In the interest of community engagement we also believe that help is much more valuable if it's shared publicly, so that more people can benefit from it. If you're over on Stack Overflow and want to boost your points, take a look at the `causalnex` tag and see if you can help others out by sharing your knowledge. It's another great way to contribute. diff --git a/RELEASE.md b/RELEASE.md index 26019d1..6ea6d4b 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,9 +1,19 @@ # Upcoming release +# Release 0.8.0 + +* Add DYNOTEARS (`from_numpy_dynamic`, an algorithm for structure learning on Dynamic Bayesian Networks). +* Added Pytorch implementation for NOTEARS MLP (`pytorch.from_numpy`) which is much faster and allows nonlinear modelling. +* Added `DAGRegressor` sklearn interface using the Pytorch NOTEARS implementation. +* Add non-linear data generators for multiple data types. +* Add a count data type to the data generator using a zero-inflated Poisson. +* Set bounds/max class imbalance for binary features for the data generators. +* Bugfix to resolve issue when applying NOTEARS on data containing NaN. +* Bugfix for data_gen system. Fixes issues with root node initialization. # Release 0.7.0 -* Added plottting tutorial to the documentation +* Added plotting tutorial to the documentation * Updated `viz.draw` syntax in tutorial notebooks * Bugfix on notears lasso (`from_numpy_lasso` and `from_pandas_lasso`) where the non-negativity constraint was not being set * Added DAG-based synthetic data generator for mixed types (binary, categorical, continuous) using a linear SEM approach. @@ -42,6 +52,6 @@ The initial release of CausalNex. ## Thanks for supporting contributions CausalNex was originally designed by [Paul Beaumont](https://www.linkedin.com/in/pbeaumont/) and [Ben Horsburgh](https://www.linkedin.com/in/benhorsburgh/) to solve challenges they faced in inferencing causality in their project work. This work was later turned into a product thanks to the following contributors: -[Yetunde Dada](https://github.com/yetudada), [Wesley Leong](https://www.linkedin.com/in/wesleyleong/), [Steve Ler](https://www.linkedin.com/in/song-lim-steve-ler-380366106/), [Viktoriia Oliinyk](https://www.linkedin.com/in/victoria-oleynik/), [Roxana Pamfil](https://www.linkedin.com/in/roxana-pamfil-1192053b/), [Nisara Sriwattanaworachai](https://www.linkedin.com/in/nisara-sriwattanaworachai-795b357/) and [Nikolaos Tsaousis](https://www.linkedin.com/in/ntsaousis/). +[Yetunde Dada](https://github.com/yetudada), [Wesley Leong](https://www.linkedin.com/in/wesleyleong/), [Steve Ler](https://www.linkedin.com/in/song-lim-steve-ler-380366106/), [Viktoriia Oliinyk](https://www.linkedin.com/in/victoria-oleynik/), [Roxana Pamfil](https://www.linkedin.com/in/roxana-pamfil-1192053b/), [Nisara Sriwattanaworachai](https://www.linkedin.com/in/nisara-sriwattanaworachai-795b357/), [Nikolaos Tsaousis](https://www.linkedin.com/in/ntsaousis/), [Angel Droth](https://www.linkedin.com/in/angeldroth/), and [Zain Patel](https://www.linkedin.com/in/zain-patel/). CausalNex would also not be possible without the generous sharing from leading researches in the field of causal inference and we are grateful to everyone who advised and supported us, filed issues or helped resolve them, asked and answered questions or simply be part of inspiring discussions. diff --git a/causalnex/__init__.py b/causalnex/__init__.py index a800c17..01312ed 100644 --- a/causalnex/__init__.py +++ b/causalnex/__init__.py @@ -30,6 +30,6 @@ causalnex toolkit for causal reasoning (Bayesian Networks / Inference) """ -__version__ = "0.7.0" +__version__ = "0.8.0" __all__ = ["structure", "discretiser", "evaluation", "inference", "network", "plots"] diff --git a/causalnex/inference/inference.py b/causalnex/inference/inference.py index d653502..cdb93bb 100644 --- a/causalnex/inference/inference.py +++ b/causalnex/inference/inference.py @@ -284,9 +284,7 @@ def template() -> float: # initially there are none present, but caller will add appropriate arguments to the function # getargvalues was "inadvertently marked as deprecated in Python 3.5" # https://docs.python.org/3/library/inspect.html#inspect.getfullargspec - arg_spec = inspect.getargvalues( # pylint: disable=deprecated-method - inspect.currentframe() - ) + arg_spec = inspect.getargvalues(inspect.currentframe()) return self._cpds[arg_spec.args[0]][ # target name arg_spec.locals[arg_spec.args[0]] diff --git a/causalnex/network/network.py b/causalnex/network/network.py index 43fde1e..154e363 100644 --- a/causalnex/network/network.py +++ b/causalnex/network/network.py @@ -46,67 +46,67 @@ class BayesianNetwork: """ - Base class for Bayesian Network (BN), a probabilistic weighted DAG where nodes represent variables, - edges represent the causal relationships between variables. - - ``BayesianNetwork`` stores nodes with their possible states, edges and - conditional probability distributions (CPDs) of each node. - - ``BayesianNetwork`` is built on top of the ``StructureModel``, which is an extension of ``networkx.DiGraph`` - (see :func:`causalnex.structure.structuremodel.StructureModel`). - - In order to define the ``BayesianNetwork``, users should provide a relevant ``StructureModel``. - Once ``BayesianNetwork`` is initialised, no changes to the ``StructureModel`` can be made - and CPDs can be learned from the data. - - The learned CPDs can be then used for likelihood estimation and predictions. - - Example: - :: - >>> # Create a Bayesian Network with a manually defined DAG. - >>> from causalnex.structure import StructureModel - >>> from causalnex.network import BayesianNetwork - >>> - >>> sm = StructureModel() - >>> sm.add_edges_from([ - >>> ('rush_hour', 'traffic'), - >>> ('weather', 'traffic') - >>> ]) - >>> bn = BayesianNetwork(sm) - >>> # A created ``BayesianNetwork`` stores nodes and edges defined by the ``StructureModel`` - >>> bn.nodes - ['rush_hour', 'traffic', 'weather'] - >>> - >>> bn.edges - [('rush_hour', 'traffic'), ('weather', 'traffic')] - >>> # A ``BayesianNetwork`` doesn't store any CPDs yet - >>> bn.cpds - >>> {} - >>> - >>> # Learn the nodes' states from the data - >>> import pandas as pd - >>> data = pd.DataFrame({ - >>> 'rush_hour': [True, False, False, False, True, False, True], - >>> 'weather': ['Terrible', 'Good', 'Bad', 'Good', 'Bad', 'Bad', 'Good'], - >>> 'traffic': ['heavy', 'light', 'heavy', 'light', 'heavy', 'heavy', 'heavy'] - >>> }) - >>> bn = bn.fit_node_states(data) - >>> bn.node_states - {'rush_hour': {False, True}, 'weather': {'Bad', 'Good', 'Terrible'}, 'traffic': {'heavy', 'light'}} - >>> # Learn the CPDs from the data - >>> bn = bn.fit_cpds(data) - >>> # Use the learned CPDs to make predictions on the unseen data - >>> test_data = pd.DataFrame({ - >>> 'rush_hour': [False, False, True, True], - >>> 'weather': ['Good', 'Bad', 'Good', 'Bad'] - >>> }) - >>> bn.predict(test_data, "traffic").to_dict() - >>> {'traffic_prediction': {0: 'light', 1: 'heavy', 2: 'heavy', 3: 'heavy'}} - >>> bn.predict_probability(test_data, "traffic").to_dict() - {'traffic_prediction': {0: 'light', 1: 'heavy', 2: 'heavy', 3: 'heavy'}} - {'traffic_light': {0: 0.75, 1: 0.25, 2: 0.3333333333333333, 3: 0.3333333333333333}, - 'traffic_heavy': {0: 0.25, 1: 0.75, 2: 0.6666666666666666, 3: 0.6666666666666666}} - """ + Base class for Bayesian Network (BN), a probabilistic weighted DAG where nodes represent variables, + edges represent the causal relationships between variables. + + ``BayesianNetwork`` stores nodes with their possible states, edges and + conditional probability distributions (CPDs) of each node. + + ``BayesianNetwork`` is built on top of the ``StructureModel``, which is an extension of ``networkx.DiGraph`` + (see :func:`causalnex.structure.structuremodel.StructureModel`). + + In order to define the ``BayesianNetwork``, users should provide a relevant ``StructureModel``. + Once ``BayesianNetwork`` is initialised, no changes to the ``StructureModel`` can be made + and CPDs can be learned from the data. + + The learned CPDs can be then used for likelihood estimation and predictions. + + Example: + :: + >>> # Create a Bayesian Network with a manually defined DAG. + >>> from causalnex.structure import StructureModel + >>> from causalnex.network import BayesianNetwork + >>> + >>> sm = StructureModel() + >>> sm.add_edges_from([ + >>> ('rush_hour', 'traffic'), + >>> ('weather', 'traffic') + >>> ]) + >>> bn = BayesianNetwork(sm) + >>> # A created ``BayesianNetwork`` stores nodes and edges defined by the ``StructureModel`` + >>> bn.nodes + ['rush_hour', 'traffic', 'weather'] + >>> + >>> bn.edges + [('rush_hour', 'traffic'), ('weather', 'traffic')] + >>> # A ``BayesianNetwork`` doesn't store any CPDs yet + >>> bn.cpds + >>> {} + >>> + >>> # Learn the nodes' states from the data + >>> import pandas as pd + >>> data = pd.DataFrame({ + >>> 'rush_hour': [True, False, False, False, True, False, True], + >>> 'weather': ['Terrible', 'Good', 'Bad', 'Good', 'Bad', 'Bad', 'Good'], + >>> 'traffic': ['heavy', 'light', 'heavy', 'light', 'heavy', 'heavy', 'heavy'] + >>> }) + >>> bn = bn.fit_node_states(data) + >>> bn.node_states + {'rush_hour': {False, True}, 'weather': {'Bad', 'Good', 'Terrible'}, 'traffic': {'heavy', 'light'}} + >>> # Learn the CPDs from the data + >>> bn = bn.fit_cpds(data) + >>> # Use the learned CPDs to make predictions on the unseen data + >>> test_data = pd.DataFrame({ + >>> 'rush_hour': [False, False, True, True], + >>> 'weather': ['Good', 'Bad', 'Good', 'Bad'] + >>> }) + >>> bn.predict(test_data, "traffic").to_dict() + >>> {'traffic_prediction': {0: 'light', 1: 'heavy', 2: 'heavy', 3: 'heavy'}} + >>> bn.predict_probability(test_data, "traffic").to_dict() + {'traffic_prediction': {0: 'light', 1: 'heavy', 2: 'heavy', 3: 'heavy'}} + {'traffic_light': {0: 0.75, 1: 0.25, 2: 0.3333333333333333, 3: 0.3333333333333333}, + 'traffic_heavy': {0: 0.25, 1: 0.75, 2: 0.6666666666666666, 3: 0.6666666666666666}} + """ def __init__(self, structure: StructureModel): """ @@ -573,7 +573,7 @@ def _predict_probability_from_incomplete_data( cols = [] pattern = re.compile("^{node}_[0-9]+$".format(node=node)) # disabled open pylint issue (https://github.com/PyCQA/pylint/issues/2962) - for col in probability.columns: # pylint: disable=E1133 + for col in probability.columns: if pattern.match(col): cols.append(col) probability = probability[cols] diff --git a/causalnex/structure/__init__.py b/causalnex/structure/__init__.py index bfe7dfa..3651f9a 100644 --- a/causalnex/structure/__init__.py +++ b/causalnex/structure/__init__.py @@ -30,6 +30,7 @@ ``causalnex.structure`` provides functionality to define or learn structure. """ -__all__ = ["StructureModel", "notears"] +__all__ = ["StructureModel", "notears", "dynotears", "data_generators", "DAGRegressor"] +from .sklearn import DAGRegressor from .structuremodel import StructureModel diff --git a/causalnex/structure/categorical_variable_mapper.py b/causalnex/structure/categorical_variable_mapper.py index 3127b50..62cfe74 100644 --- a/causalnex/structure/categorical_variable_mapper.py +++ b/causalnex/structure/categorical_variable_mapper.py @@ -49,7 +49,7 @@ class VariableFeatureMapper: attribute ``PERMISSIBLE_TYPES``. """ - PERMISSIBLE_TYPES = {"binary", "categorical", "continuous"} + PERMISSIBLE_TYPES = {"binary", "categorical", "continuous", "count"} EXPANDABLE_TYPE = "categorical" def __init__(self, schema: Dict[Hashable, str]): @@ -81,10 +81,11 @@ def __init__(self, schema: Dict[Hashable, str]): ) cat_feature_list = list(self._cat_fte_var_dict.keys()) - # we put them together with the cont + binayr in a feature list + # we put them together with the cont + binary in a feature list self.feature_list = ( self.variable_type_dict["binary"] + self.variable_type_dict["continuous"] + + self.variable_type_dict["count"] + cat_feature_list ) @@ -98,6 +99,7 @@ def __init__(self, schema: Dict[Hashable, str]): var: [self._fte_index_dict[var]] for var in self.variable_type_dict["continuous"] + self.variable_type_dict["binary"] + + self.variable_type_dict["count"] } self.var_indices_dict.update( { diff --git a/causalnex/structure/data_generators/__init__.py b/causalnex/structure/data_generators/__init__.py new file mode 100644 index 0000000..ced528e --- /dev/null +++ b/causalnex/structure/data_generators/__init__.py @@ -0,0 +1,59 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Data generators using DAGs for benchmarking and synthetic data generation. +""" + +__all__ = [ + "generate_structure", + "nonlinear_sem_generator", + "sem_generator", + "generate_binary_data", + "generate_binary_dataframe", + "generate_categorical_dataframe", + "generate_continuous_data", + "generate_continuous_dataframe", + "generate_count_dataframe", + "gen_stationary_dyn_net_and_df", + "generate_dataframe_dynamic", + "generate_structure_dynamic", +] + +from .core import generate_structure, nonlinear_sem_generator, sem_generator +from .wrappers import ( + gen_stationary_dyn_net_and_df, + generate_binary_data, + generate_binary_dataframe, + generate_categorical_dataframe, + generate_continuous_data, + generate_continuous_dataframe, + generate_count_dataframe, + generate_dataframe_dynamic, + generate_structure_dynamic, +) diff --git a/causalnex/structure/data_generators.py b/causalnex/structure/data_generators/core.py similarity index 51% rename from causalnex/structure/data_generators.py rename to causalnex/structure/data_generators/core.py index ee30db1..384dc2e 100644 --- a/causalnex/structure/data_generators.py +++ b/causalnex/structure/data_generators/core.py @@ -32,17 +32,29 @@ Structure generator based on implementation found in: from https://github.com/xunzheng/notears git hash: 31923cb22517f7bb6420dd0b6ef23ca550702b97 """ -from typing import Dict, Hashable, List, Optional, Tuple +from typing import Dict, Hashable, List, Optional, Tuple, Union import networkx as nx import numpy as np import pandas as pd +from sklearn.gaussian_process.kernels import RBF, Kernel +from causalnex.structure import StructureModel from causalnex.structure.categorical_variable_mapper import ( VariableFeatureMapper, validate_schema, ) -from causalnex.structure.structuremodel import StructureModel + +# dict mapping distributions names to their functions +__distribution_mapper = { + "gaussian": np.random.normal, + "normal": np.random.normal, + "student-t": np.random.standard_t, + "gumbel": np.random.gumbel, + "exponential": np.random.exponential, + "probit": np.random.normal, + "logit": np.random.logistic, +} def generate_structure( @@ -105,7 +117,10 @@ def generate_structure( edge_flags = np.tril(np.ones([num_nodes, num_nodes]), k=-1) else: - raise ValueError("unknown graph type") + raise ValueError( + "Unknown graph type {t}. ".format(t=graph_type) + + "Available types are ['erdos-renyi', 'barabasi-albert', 'full']" + ) # randomly permute edges - required because we limited ourselves to lower diagonal previously perms = np.random.permutation(np.eye(num_nodes, num_nodes)) @@ -120,214 +135,24 @@ def generate_structure( return graph -def generate_continuous_data( - sm: nx.DiGraph, - n_samples: int, - distribution: str = "gaussian", - noise_scale: float = 1.0, - intercept: bool = False, - seed: int = None, -) -> np.ndarray: - """ - Simulate samples from SEM with specified type of noise. - The order of the columns on the returned array is the one provided by `sm.nodes` - - Args: - sm: A DAG in form of a networkx or StructureModel. Does not require weights. - n_samples: The number of rows/observations to sample. - distribution: The type of distribution to use for the noise - of a variable. Options: 'gaussian'/'normal' (alias), 'student-t', - 'exponential', 'gumbel'. - noise_scale: The standard deviation of the noise. - intercept: Whether to use an intercept for each feature. - seed: Random state - Returns: - x_mat: [n_samples,d_nodes] sample matrix - Raises: - ValueError: if distribution isn't gaussian/normal/student-t/exponential/gumbel - """ - df = sem_generator( - graph=sm, - default_type="continuous", - n_samples=n_samples, - distributions={"continuous": distribution}, - noise_std=noise_scale, - intercept=intercept, - seed=seed, - ) - return df[list(sm.nodes())].values - - -def generate_binary_data( - sm: nx.DiGraph, - n_samples: int, - distribution: str = "logit", - noise_scale: float = 1.0, - intercept: bool = False, - seed: int = None, -) -> np.ndarray: - """ - Simulate samples from SEM with specified type of noise. - The order of the columns on the returned array is the one provided by `sm.nodes` - - Args: - sm: A DAG in form of a networkx or StructureModel. Does not require weights. - n_samples: The number of rows/observations to sample. - distribution: The type of distribution to use for the noise - of a variable. Options: 'probit'/'normal' (alias), - 'logit' (default). - noise_scale: The standard deviation of the noise. The binary and - categorical features are created using a latent variable approach. - The noise standard deviation determines how much weight the "mean" - estimate has on the feature value. - intercept: Whether to use an intercept for the latent variable of each feature. - seed: Random state - Returns: - x_mat: [n_samples,d_nodes] sample matrix - Raises: - ValueError: if distribution isn't 'probit', 'normal', 'logit' - """ - df = sem_generator( - graph=sm, - default_type="binary", - n_samples=n_samples, - distributions={"binary": distribution}, - noise_std=noise_scale, - intercept=intercept, - seed=seed, - ) - return df[list(sm.nodes())].values - - -def generate_continuous_dataframe( - sm: nx.DiGraph, - n_samples: int, - distribution: str = "gaussian", - noise_scale: float = 1.0, - intercept: bool = False, - seed: int = None, -) -> pd.DataFrame: - """ - Generates a dataframe with samples from SEM with specified type of noise. - Args: - sm: A DAG in form of a networkx or StructureModel. Does not require weights. - n_samples: The number of rows/observations to sample. - distribution: The type of distribution to use for the noise - of a variable. Options: 'gaussian'/'normal' (alias), 'student-t', - 'exponential', 'gumbel'. - noise_scale: The standard deviation of the noise. - intercept: Whether to use an intercept for each feature. - seed: Random state - Returns: - Dataframe with the node names as column names - Raises: - ValueError: if distribution is not 'gaussian', 'normal', 'student-t', - 'exponential', 'gumbel' - """ - return sem_generator( - graph=sm, - default_type="continuous", - n_samples=n_samples, - distributions={"continuous": distribution}, - noise_std=noise_scale, - intercept=intercept, - seed=seed, - ) - - -def generate_binary_dataframe( - sm: nx.DiGraph, - n_samples: int, - distribution: str = "logit", - noise_scale: float = 1.0, - intercept: bool = False, - seed: int = None, -) -> pd.DataFrame: - """ - Generates a dataframe with samples from SEM with specified type of noise. - - Args: - sm: A DAG in form of a networkx or StructureModel. Does not require weights. - n_samples: The number of rows/observations to sample. - distribution: The type of distribution to use for the noise - of a variable. Options: 'probit'/'normal' (alias), - 'logit' (default). - noise_scale: The standard deviation of the noise. The binary and - categorical features are created using a latent variable approach. - The noise standard deviation determines how much weight the "mean" - estimate has on the feature value. - intercept: Whether to use an intercept for the latent variable of each feature. - seed: Random state - Returns: - x_mat: [n_samples,d_nodes] sample matrix - Raises: - ValueError: if distribution is not 'probit', 'normal', 'logit' - """ - return sem_generator( - graph=sm, - default_type="binary", - n_samples=n_samples, - distributions={"binary": distribution}, - noise_std=noise_scale, - intercept=intercept, - seed=seed, - ) - - -def generate_categorical_dataframe( - sm: nx.DiGraph, - n_samples: int, - distribution: str = "logit", - n_categories: int = 3, - noise_scale: float = 1.0, - intercept: bool = False, - seed: int = None, -) -> pd.DataFrame: - """ - Generates a dataframe with samples from SEM with specified type of noise. - - Args: - sm: A DAG in form of a networkx or StructureModel. Does not require weights. - n_samples: The number of rows/observations to sample. - distribution: The type of distribution to use for the noise - of a variable. Options: 'probit'/'normal' (alias), - "logit"/"gumbel" (alias). Logit is default. - n_categories: Number of categories per variable/node. - noise_scale: The standard deviation of the noise. The categorical features - are created using a latent variable approach. The noise standard - deviation determines how much weight the "mean" estimate has on - the feature value. - intercept: Whether to use an intercept for the latent variable of each feature. - seed: Random state - Returns: - x_mat: [n_samples, d_nodes] sample matrix - Raises: - ValueError: if distribution is not 'probit', 'normal', 'logit', 'gumbel' - """ - return sem_generator( - graph=sm, - default_type="categorical:{}".format(n_categories), - n_samples=n_samples, - distributions={"categorical": distribution}, - noise_std=noise_scale, - intercept=intercept, - seed=seed, - ) - - def sem_generator( graph: nx.DiGraph, schema: Optional[Dict] = None, default_type: str = "continuous", noise_std: float = 1.0, n_samples: int = 1000, - distributions: Dict[str, str] = None, + distributions: Dict[str, Union[str, float]] = None, intercept: bool = True, seed: int = None, ) -> pd.DataFrame: """ Generator for tabular data with mixed variable types from a DAG. + NOTE: the root nodes of the DAG are sampled from a distribution with noise_std=1.0 always. + This is so that increases in the noise_std are in relation to a fixed spread, and therefore + actually have an impact on the fit. Not using this method causes the noise_std to only change + the axis scaling. + Supported variable types: `'binary', 'categorical', 'continuous'`. The number of categories can be determined using a colon, e.g. `'categorical:5'` specifies a categorical feature with 5 categories. @@ -362,6 +187,7 @@ def sem_generator( ``intercept'': The type of distribution to use for the intercept. For binary/categorical: this is the mean in the latent space. Options: 'gaussian'/'normal' (alias), 'uniform' (default). + ``count``: The zero-inflation probability as a float. intercept: Whether to use an intercept for each feature. The intercept is sampled once and held constant for all rows. For binary or categorical the intercept determines the class imbalance. @@ -381,7 +207,7 @@ def sem_generator( ValueError: if distributions['categorical'] is not 'probit', 'normal', 'logit', 'gumbel'. ValueError: if distributions['weight'] is not 'normal' / 'gaussian' (alias), 'uniform'. ValueError: if distributions['intercept'] is not 'normal' / 'gaussian' (alias), 'uniform'. - + ValueError: if distributions['count'], the zero-inflation factor is not a float in [0, 1]. Example: sm = StructureModel() @@ -398,19 +224,14 @@ def sem_generator( intercept=True, ) """ - - np.random.seed(seed) - - if not nx.algorithms.is_directed_acyclic_graph(graph): - raise ValueError("Provided graph is not a DAG.") - - distributions = _set_default_distributions(distributions=distributions) - validated_schema = validate_schema( - nodes=graph.nodes(), schema=schema, default_type=default_type + distributions, var_fte_mapper, x_mat = _init_sem_data_gen( + graph=graph, + schema=schema, + n_samples=n_samples, + default_type=default_type, + distributions=distributions, + seed=seed, ) - var_fte_mapper = VariableFeatureMapper(validated_schema) - - n_columns = var_fte_mapper.n_features # get dependence based on edges in graph (not via adjacency matrix) w_mat = _create_weight_matrix( @@ -421,11 +242,13 @@ def sem_generator( intercept=intercept, ) - # pre-allocate array - x_mat = np.empty([n_samples, n_columns + 1 if intercept else n_columns]) # intercept, append ones to the feature matrix if intercept: - x_mat[:, -1] = 1 + x_mat = np.append(x_mat, np.ones(shape=(n_samples, 1)), axis=1) + intercept_idx = [x_mat.shape[1] - 1] + + # if intercept is used, the root nodes have len = 1 + root_node_len = 1 if intercept else 0 # loop over sorted features according to ancestry (no parents first) for j_node in nx.topological_sort(graph): @@ -435,7 +258,10 @@ def sem_generator( # get all parent feature indices for the variable/node parents_idx = var_fte_mapper.get_indices(list(graph.predecessors(j_node))) if intercept: - parents_idx += [n_columns] + parents_idx += intercept_idx + + # if the data is a root node, must initialise the axis separate from noise parameter + root_node = len(parents_idx) <= root_node_len # continuous variable if var_fte_mapper.is_var_of_type(j_node, "continuous"): @@ -443,6 +269,7 @@ def sem_generator( mean=x_mat[:, parents_idx].dot(w_mat[parents_idx, j_idx_list[0]]), distribution=distributions["continuous"], noise_std=noise_std, + root_node=root_node, ) # binary variable @@ -453,6 +280,15 @@ def sem_generator( ), distribution=distributions["binary"], noise_std=noise_std, + root_node=root_node, + ) + + # count variable + elif var_fte_mapper.is_var_of_type(j_node, "count"): + x_mat[:, j_idx_list[0]] = _sample_count_from_latent( + eta=x_mat[:, parents_idx].dot(w_mat[parents_idx, j_idx_list[0]]), + zero_inflation_pct=distributions["count"], + root_node=root_node, ) # categorical variable @@ -463,6 +299,7 @@ def sem_generator( ), distribution=distributions["categorical"], noise_std=noise_std, + root_node=root_node, ) return pd.DataFrame( @@ -470,67 +307,188 @@ def sem_generator( ) +def _handle_distribution_sampling( + distribution: str, + distribution_func, + noise_std: float, + size: Tuple[int], + root_node: bool, +): + # force scale to be 1 for the root node + if root_node: + noise_std = 1 + + # special sampling syntax + if distribution == "student-t": + return distribution_func(df=5, size=size) * noise_std + + # default sampling syntax + return distribution_func(scale=noise_std, size=size) + + def _add_continuous_noise( - mean: np.ndarray, distribution: str, noise_std: float, + mean: np.ndarray, + distribution: str, + noise_std: float, + root_node: bool, ) -> np.ndarray: n_samples = mean.shape[0] - # add noise to mean - if distribution in ("gaussian", "normal"): - x = mean + np.random.normal(scale=noise_std, size=n_samples) - elif distribution == "student-t": - x = mean + np.random.standard_t(df=5, size=n_samples) * noise_std - elif distribution == "exponential": - x = mean + np.random.exponential(scale=noise_std, size=n_samples) - elif distribution == "gumbel": - x = mean + np.random.gumbel(scale=noise_std, size=n_samples) - else: + # try and get the requested distribution from the mapper + distribution_func = __distribution_mapper.get(distribution, None) + if distribution_func is None: _raise_dist_error( "continuous", distribution, ["gaussian", "normal", "student-t", "exponential", "gumbel"], ) - return x + # add noise to mean + mean += _handle_distribution_sampling( + distribution=distribution, + distribution_func=distribution_func, + noise_std=noise_std, + size=(n_samples,), + root_node=root_node, + ) + + return mean def _sample_binary_from_latent( - latent_mean: np.ndarray, distribution: str, noise_std: float, + latent_mean: np.ndarray, + distribution: str, + noise_std: float, + root_node: bool, + max_imbalance: float = 0.05, ) -> np.ndarray: n_samples = latent_mean.shape[0] - # add noise to latent variable - if distribution in ("normal", "probit"): - eta = latent_mean + np.random.normal(scale=noise_std, size=n_samples) - elif distribution == "logit": - eta = latent_mean + np.random.logistic(scale=noise_std, size=n_samples) - else: + # try and get the requested distribution from the mapper + distribution_func = __distribution_mapper.get(distribution, None) + if distribution_func is None: _raise_dist_error("binary", distribution, ["logit", "probit", "normal"]) - # using a latent variable approach - return (eta > 0).astype(int) + # add noise to mean + latent_mean += _handle_distribution_sampling( + distribution=distribution, + distribution_func=distribution_func, + noise_std=noise_std, + size=(n_samples,), + root_node=root_node, + ) + + # use an alternative threshold if 0 leads to heavy imbalance + labels = (latent_mean > 0).astype(int) + share_positive = np.mean(labels) + if share_positive < max_imbalance: + return (latent_mean > np.quantile(latent_mean, max_imbalance)).astype(int) + if share_positive > (1 - max_imbalance): + return (latent_mean > np.quantile(latent_mean, 1 - max_imbalance)).astype(int) + return labels + + +def _sample_count_from_latent( + eta: np.ndarray, + root_node: bool, + zero_inflation_pct: float = 0.05, +) -> np.ndarray: + """ + Samples a zero-inflated poisson distribution. + Returns: + Samples from a Poisson distribution. + Raises: + ValueError: Unsupported zero-inflation factor. + """ + if ( + not isinstance(zero_inflation_pct, (float, int)) + or zero_inflation_pct < 0 + or zero_inflation_pct > 1 + ): + raise ValueError( + "Unsupported zero-inflation factor, distribution['count'] needs to be a float in [0, 1]" + ) + n_samples = eta.shape[0] + + # add noise manually if root node + # uniform [0, 1] makes sure that the counts are small + if root_node: + eta += np.random.uniform(size=n_samples) + + zif = np.random.uniform(size=n_samples) < zero_inflation_pct + count = _sample_poisson(expected_count=_exp_relu(eta)) + + # inflate the zeros: + count[zif] = 0 + return count + + +def _exp_relu(x): + x[x < 0] = np.exp(x[x < 0]) + return x + + +def _sample_poisson(expected_count: np.ndarray, max_count: int = 5000) -> np.ndarray: + """ + Samples from a poisson distribution using each element in ``latent_mean`` + as the Poisson parameter. + + Args: + expected_count: Event rate of the Poisson process, can be of any array + dimension. Defined on (0, infty). + max_count: Bounds the count from above. The count sample is created + with a while loop. This argument is the maximum number of loop + iterations before stopping. Default value should run on most + machines in reasonable amount of time. + Returns: + Sampled count of a Poisson distribution from the given mean. + """ + # use log for numeric stability for large count values + log_cond_intensity = -expected_count + log_intensity_budget = np.copy(log_cond_intensity) + + count = np.zeros_like(expected_count) + + log_uni = np.log(np.random.uniform(size=expected_count.shape)) + mask = log_uni >= log_intensity_budget + + while np.any(mask) and count.max() < max_count: + mask = log_uni >= log_intensity_budget + count[mask] += 1 + log_cond_intensity[mask] += np.log(expected_count[mask] / count[mask]) + log_intensity_budget[mask] = np.logaddexp( + log_intensity_budget[mask], log_cond_intensity[mask] + ) + + return count def _sample_categories_from_latent( - latent_mean: np.ndarray, distribution: str, noise_std: float, + latent_mean: np.ndarray, + distribution: str, + noise_std: float, + root_node: bool, ) -> np.ndarray: one_hot = np.empty_like(latent_mean) n_samples, n_cardinality = latent_mean.shape - if distribution in ("normal", "probit"): - latent_mean += np.random.normal( - scale=noise_std, size=(n_samples, n_cardinality) - ) - elif distribution in ("logit", "gumbel"): - latent_mean += np.random.gumbel( - scale=noise_std, size=(n_samples, n_cardinality) - ) - else: + # try and get the requested distribution from the mapper + distribution_func = __distribution_mapper.get(distribution, None) + if distribution_func is None: _raise_dist_error( "categorical", distribution, ["logit", "gumbel", "probit", "normal"] ) + # add noise to mean + latent_mean += _handle_distribution_sampling( + distribution=distribution, + distribution_func=distribution_func, + noise_std=noise_std, + size=(n_samples, n_cardinality), + root_node=root_node, + ) + x_cat = np.argmax(latent_mean, axis=1) for i in range(n_cardinality): @@ -539,13 +497,16 @@ def _sample_categories_from_latent( return one_hot -def _set_default_distributions(distributions: Dict[str, str]) -> Dict[str, str]: +def _set_default_distributions( + distributions: Dict[str, Union[str, float]] +) -> Dict[str, Union[str, float]]: default_distributions = { "continuous": "gaussian", "binary": "logit", "categorical": "logit", "weight": "uniform", "intercept": "uniform", + "count": 0.05, } if distributions is None: @@ -626,3 +587,259 @@ def _raise_dist_error(name: str, dist: str, dist_options): ", ".join(valid_dist for valid_dist in dist_options) ) ) + + +def _init_sem_data_gen( + graph: nx.DiGraph, + schema: Dict, + n_samples: int, + default_type: str, + distributions: Dict[str, str], + seed: int, +): + np.random.seed(seed) + + if not nx.algorithms.is_directed_acyclic_graph(graph): + raise ValueError("Provided graph is not a DAG.") + + distributions = _set_default_distributions(distributions=distributions) + validated_schema = validate_schema( + nodes=graph.nodes(), schema=schema, default_type=default_type + ) + var_fte_mapper = VariableFeatureMapper(validated_schema) + + # pre-allocate array + n_columns = var_fte_mapper.n_features + x_mat = np.empty([n_samples, n_columns]) + + return distributions, var_fte_mapper, x_mat + + +def nonlinear_sem_generator( + graph: nx.DiGraph, + kernel: Kernel = RBF(1), + schema: Optional[Dict] = None, + default_type: str = "continuous", + noise_std: float = 1.0, + n_samples: int = 1000, + distributions: Dict[str, str] = None, + seed: int = None, +) -> pd.DataFrame: + """ + Generator for non-linear tabular data with mixed variable types from a DAG. + + The nonlinearity can be controlled via the ``kernel``. Note that a + ``DotProduct`` is equivalent to a linear function (without mean). + + Supported variable types: `'binary', 'categorical', 'continuous'`. The number + of categories can be determined using a colon, e.g. `'categorical:5'` + specifies a categorical feature with 5 categories. + + Notation: For binary and continuous variables, a ``variable'' refers to a + ``node'', a ``feature'' refers to the one-hot column for categorical + variables and is equivalent to a binary or continuous variable. + + Args: + graph: A DAG in form of a networkx or StructureModel. + kernel: A kernel from sklearn.gaussian_process.kernels like RBF(1) or + Matern(1) or any combinations thereof. The kernels are used to + create the latent variable for the binary / categorical variables + and are directly used for continuous variables. + schema: Dictionary with schema for a node/variable, if a node is missing + uses ``default_type``. Format, {node_name: variable type}. + default_type: The default data type for a node/variable not listed + in the schema, or when the schema is empty. + noise_std: The standard deviation of the noise. The binary and + categorical features are created using a latent variable approach. + The noise standard deviation determines how much weight the "mean" + estimate has on the feature value. + n_samples: The number of rows/observations to sample. + distributions: + ``continuous'': The type of distribution to use for the noise + of a continuous variable. Options: 'gaussian'/'normal' (alias) + (default), 'student-t', 'exponential', 'gumbel'. + ``binary'': The type of distribution to use for the noise + of the latent binary variable. Options: 'probit'/'normal' (alias), + 'logit' (default). + ``categorical'': The type of distribution to use for the noise + of a latent continuous feature. Options: 'probit'/'normal' (alias), + 'logit'/'gumbel' (alias) (default). + seed: Random State + + Returns: + DataFrame with generated features, uses a one-hot coding for + categorical features. + + Raises: + ValueError: if the graph is not a DAG. + ValueError: if schema variable type is not in `'binary', 'categorical', + 'continuous', 'continuous:X` (for variables with X categories). + ValueError: if distributions['continuous'] is not 'gaussian', 'normal', 'student-t', + 'exponential', 'gumbel'. + ValueError: if distributions['binary'] is not 'probit', 'normal', 'logit'. + ValueError: if distributions['categorical'] is not 'probit', 'normal', 'logit', 'gumbel'. + ValueError: if distributions['count'], the zero-inflation factor is not a float in [0, 1]. + + Example: + sm = StructureModel() + + sm.add_edges_from([('A', 'C'), ('D', 'C'), ('E', 'D')]) + + sm.add_nodes_from(['B', 'F']) + + schema = {'B': 'binary', 'C': 'categorical:5', + 'E': 'binary', 'F': 'continuous'} + + df = sem_generator(sm, schema, kernel=RBF(1), noise_scale=1, + n_samples=10000) + """ + distributions, var_fte_mapper, x_mat = _init_sem_data_gen( + graph=graph, + schema=schema, + n_samples=n_samples, + default_type=default_type, + distributions=distributions, + seed=seed, + ) + + # loop over sorted features according to ancestry (no parents first) + for j_node in nx.topological_sort(graph): + # all feature indices corresponding to the node/variable + j_idx_list = var_fte_mapper.get_indices(j_node) + + # get all parent feature indices for the variable/node + parents_idx = var_fte_mapper.get_indices(list(graph.predecessors(j_node))) + + # if the data is a root node, must initialise the axis separate from noise parameter + root_node = len(parents_idx) <= 0 + + # continuous variable + if var_fte_mapper.is_var_of_type(j_node, "continuous"): + x_mat[:, j_idx_list[0]] = _add_continuous_noise( + mean=_gp_index(x_mat[:, parents_idx], kernel), + distribution=distributions["continuous"], + noise_std=noise_std, + root_node=root_node, + ) + + # binary variable + elif var_fte_mapper.is_var_of_type(j_node, "binary"): + x_mat[:, j_idx_list[0]] = _sample_binary_from_latent( + latent_mean=_gp_index(x_mat[:, parents_idx], kernel), + distribution=distributions["binary"], + noise_std=noise_std, + root_node=root_node, + ) + + # count + if var_fte_mapper.is_var_of_type(j_node, "count"): + x_mat[:, j_idx_list[0]] = _sample_count_from_latent( + eta=_gp_index(x_mat[:, parents_idx], kernel), + zero_inflation_pct=distributions["count"], + root_node=root_node, + ) + + # categorical variable + elif var_fte_mapper.is_var_of_type(j_node, "categorical"): + x_mat[:, j_idx_list] = _sample_categories_from_latent( + latent_mean=np.concatenate( + [ + np.expand_dims(_gp_index(x_mat[:, parents_idx], kernel), axis=1) + for _ in j_idx_list + ], + axis=1, + ), + distribution=distributions["categorical"], + noise_std=noise_std, + root_node=root_node, + ) + return pd.DataFrame(x_mat, columns=var_fte_mapper.feature_list) + + +def _unconditional_sample(x, kernel): + cov_mat = kernel(x) + y = np.random.multivariate_normal(mean=np.zeros(shape=x.shape[0]), cov=cov_mat) + return y.squeeze(), cov_mat + + +def _conditional_sample( + x_new, x_old, f_old, kernel, cov_mat_old: np.ndarray = None, epsilon=0.00001 +): + + cov_mat_new = kernel(x_new) + cross_cov = kernel(x_old, x_new) + # X_no.T @ inv(X_oo): + reg_coef = np.linalg.solve( + cov_mat_old + epsilon * np.eye(x_old.shape[0]), cross_cov + ).T + + # calculate conditional mean and cov + cond_cov = (cov_mat_new - reg_coef @ cross_cov) + epsilon * np.eye(x_new.shape[0]) + cond_mean = (reg_coef @ f_old).squeeze() + + # sample + y_new = np.random.multivariate_normal(mean=cond_mean, cov=cond_cov).squeeze() + return y_new, cov_mat_new + + +def _gp_index( + x: np.ndarray, + kernel: Kernel, + max_chunk_size: int = 100, +) -> np.ndarray: + """ + Sample a Gaussian process using input data. + ``f(x) ~ GP(0, K)`` + + If the number of samples is larger than ``max_chunk_size``, the sampling is + split in sorted batches (first dimension) and sampled using a conditional + multivariate normal. + + Args: + x: + kernel: + max_chunk_size: + + Returns: + A one-dimensional numpy array with a sample of f(x) + """ + # if we dont have a parent, the input will have no columns + if x.shape[1] == 0: + return np.zeros(shape=(x.shape[0],)) + + use_batches = x.shape[0] > max_chunk_size + + if not use_batches: + y, _ = _unconditional_sample(x, kernel=kernel) + return _scale_y(y) + + # if we need batches, we sort according to the first dimension + ix_sort = np.argsort(x, axis=0)[:, 0].squeeze() + reverse_ix = np.argsort(ix_sort).squeeze() + + # split into smaller pieces + n_splits = (x.shape[0] // max_chunk_size) + 1 + x_splits = np.array_split(x[ix_sort, :], n_splits) + + outputs = [] + y, cov_mat = _unconditional_sample(x_splits[0], kernel=kernel) + outputs.append(y) + x_old = x_splits[0] + for x_subset in x_splits[1:]: + y, cov_mat = _conditional_sample( + x_new=x_subset, + x_old=x_old, + f_old=outputs[-1], + kernel=kernel, + cov_mat_old=cov_mat, + ) + outputs.append(y) + x_old = x_subset + + y_all = _scale_y(np.concatenate(outputs)) + return y_all[reverse_ix] + + +def _scale_y(y): + """Normalize variance to 1.""" + return y / y.std() diff --git a/causalnex/structure/data_generators/wrappers.py b/causalnex/structure/data_generators/wrappers.py new file mode 100644 index 0000000..94ba092 --- /dev/null +++ b/causalnex/structure/data_generators/wrappers.py @@ -0,0 +1,712 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Module of methods to sample variables of a single data type. +""" +import warnings +from typing import List, Optional, Tuple + +import networkx as nx +import numpy as np +import pandas as pd +from scipy.sparse import csr_matrix +from sklearn.gaussian_process.kernels import Kernel + +from causalnex.structure.data_generators import ( + generate_structure, + nonlinear_sem_generator, + sem_generator, +) +from causalnex.structure.structuremodel import StructureModel + + +def generate_continuous_data( + sm: nx.DiGraph, + n_samples: int, + distribution: str = "gaussian", + noise_scale: float = 1.0, + intercept: bool = False, + seed: int = None, + kernel: Optional[Kernel] = None, +) -> np.ndarray: + """ + Simulate samples from SEM with specified type of noise. + The order of the columns on the returned array is the one provided by `sm.nodes` + + Args: + sm: A DAG in form of a networkx or StructureModel. Does not require weights. + n_samples: The number of rows/observations to sample. + kernel: A kernel from sklearn.gaussian_process.kernels like RBF(1) or + Matern(1) or any combinations thereof. The kernels are used to + create the latent variable for the binary / categorical variables + and are directly used for continuous variables. + distribution: The type of distribution to use for the noise + of a variable. Options: 'gaussian'/'normal' (alias), 'student-t', + 'exponential', 'gumbel'. + noise_scale: The standard deviation of the noise. + intercept: Whether to use an intercept for each feature. + seed: Random state + Returns: + x_mat: [n_samples,d_nodes] sample matrix + Raises: + ValueError: if distribution isn't gaussian/normal/student-t/exponential/gumbel + """ + if kernel is None: + df = sem_generator( + graph=sm, + default_type="continuous", + n_samples=n_samples, + distributions={"continuous": distribution}, + noise_std=noise_scale, + intercept=intercept, + seed=seed, + ) + else: + df = nonlinear_sem_generator( + graph=sm, + kernel=kernel, + default_type="continuous", + n_samples=n_samples, + distributions={"continuous": distribution}, + noise_std=noise_scale, + seed=seed, + ) + return df[list(sm.nodes())].values + + +def generate_binary_data( + sm: nx.DiGraph, + n_samples: int, + distribution: str = "logit", + noise_scale: float = 1.0, + intercept: bool = False, + seed: int = None, + kernel: Optional[Kernel] = None, +) -> np.ndarray: + """ + Simulate samples from SEM with specified type of noise. + The order of the columns on the returned array is the one provided by `sm.nodes` + + Args: + sm: A DAG in form of a networkx or StructureModel. Does not require weights. + n_samples: The number of rows/observations to sample. + kernel: A kernel from sklearn.gaussian_process.kernels like RBF(1) or + Matern(1) or any combinations thereof. The kernels are used to + create the latent variable for the binary / categorical variables + and are directly used for continuous variables. + distribution: The type of distribution to use for the noise + of a variable. Options: 'probit'/'normal' (alias), + 'logit' (default). + noise_scale: The standard deviation of the noise. The binary and + categorical features are created using a latent variable approach. + The noise standard deviation determines how much weight the "mean" + estimate has on the feature value. + intercept: Whether to use an intercept for the latent variable of each feature. + seed: Random state + Returns: + x_mat: [n_samples,d_nodes] sample matrix + Raises: + ValueError: if distribution isn't 'probit', 'normal', 'logit' + """ + if kernel is None: + df = sem_generator( + graph=sm, + default_type="binary", + n_samples=n_samples, + distributions={"binary": distribution}, + noise_std=noise_scale, + intercept=intercept, + seed=seed, + ) + else: + df = nonlinear_sem_generator( + graph=sm, + kernel=kernel, + default_type="binary", + n_samples=n_samples, + distributions={"binary": distribution}, + noise_std=noise_scale, + seed=seed, + ) + return df[list(sm.nodes())].values + + +def generate_continuous_dataframe( + sm: nx.DiGraph, + n_samples: int, + distribution: str = "gaussian", + noise_scale: float = 1.0, + intercept: bool = False, + seed: int = None, + kernel: Optional[Kernel] = None, +) -> pd.DataFrame: + """ + Generates a dataframe with samples from SEM with specified type of noise. + Args: + sm: A DAG in form of a networkx or StructureModel. Does not require weights. + n_samples: The number of rows/observations to sample. + kernel: A kernel from sklearn.gaussian_process.kernels like RBF(1) or + Matern(1) or any combinations thereof. The kernels are used to + create the latent variable for the binary / categorical variables + and are directly used for continuous variables. + distribution: The type of distribution to use for the noise + of a variable. Options: 'gaussian'/'normal' (alias), 'student-t', + 'exponential', 'gumbel'. + noise_scale: The standard deviation of the noise. + intercept: Whether to use an intercept for each feature. + seed: Random state + Returns: + Dataframe with the node names as column names + Raises: + ValueError: if distribution is not 'gaussian', 'normal', 'student-t', + 'exponential', 'gumbel' + """ + if kernel is None: + return sem_generator( + graph=sm, + default_type="continuous", + n_samples=n_samples, + distributions={"continuous": distribution}, + noise_std=noise_scale, + intercept=intercept, + seed=seed, + ) + + return nonlinear_sem_generator( + graph=sm, + kernel=kernel, + default_type="continuous", + n_samples=n_samples, + distributions={"continuous": distribution}, + noise_std=noise_scale, + seed=seed, + ) + + +def generate_binary_dataframe( + sm: nx.DiGraph, + n_samples: int, + distribution: str = "logit", + noise_scale: float = 1.0, + intercept: bool = False, + seed: int = None, + kernel: Optional[Kernel] = None, +) -> pd.DataFrame: + """ + Generates a dataframe with samples from SEM with specified type of noise. + + Args: + sm: A DAG in form of a networkx or StructureModel. Does not require weights. + n_samples: The number of rows/observations to sample. + kernel: A kernel from sklearn.gaussian_process.kernels like RBF(1) or + Matern(1) or any combinations thereof. The kernels are used to + create the latent variable for the binary / categorical variables + and are directly used for continuous variables. + distribution: The type of distribution to use for the noise + of a variable. Options: 'probit'/'normal' (alias), + 'logit' (default). + noise_scale: The standard deviation of the noise. The binary and + categorical features are created using a latent variable approach. + The noise standard deviation determines how much weight the "mean" + estimate has on the feature value. + intercept: Whether to use an intercept for the latent variable of each feature. + seed: Random state + Returns: + x_mat: [n_samples,d_nodes] sample matrix + Raises: + ValueError: if distribution is not 'probit', 'normal', 'logit' + """ + if kernel is None: + return sem_generator( + graph=sm, + default_type="binary", + n_samples=n_samples, + distributions={"binary": distribution}, + noise_std=noise_scale, + intercept=intercept, + seed=seed, + ) + + return nonlinear_sem_generator( + graph=sm, + kernel=kernel, + default_type="binary", + n_samples=n_samples, + distributions={"binary": distribution}, + noise_std=noise_scale, + seed=seed, + ) + + +def generate_count_dataframe( + sm: nx.DiGraph, + n_samples: int, + zero_inflation_factor: float = 0.1, + intercept: bool = False, + seed: int = None, + kernel: Optional[Kernel] = None, +) -> pd.DataFrame: + """ + Generates a dataframe with samples from SEM with specified type of noise. + + Args: + sm: A DAG in form of a networkx or StructureModel. Does not require weights. + n_samples: The number of rows/observations to sample. + kernel: A kernel from sklearn.gaussian_process.kernels like RBF(1) or + Matern(1) or any combinations thereof. The kernels are used to + create the latent variable for the binary / categorical variables + and are directly used for continuous variables. + zero_inflation_factor: The probability of zero inflation for count data. + intercept: Whether to use an intercept for the latent variable of each feature. + seed: Random state + Returns: + x_mat: [n_samples, d_nodes] sample matrix + Raises: + ValueError: if ``zero_inflation_factor`` is not a float in [0, 1]. + """ + + if kernel is None: + return sem_generator( + graph=sm, + default_type="count", + n_samples=n_samples, + distributions={"count": zero_inflation_factor}, + noise_std=1, # not used for poisson + intercept=intercept, + seed=seed, + ) + + return nonlinear_sem_generator( + graph=sm, + kernel=kernel, + default_type="count", + n_samples=n_samples, + distributions={"count": zero_inflation_factor}, + noise_std=1, # not used for poisson + seed=seed, + ) + + +def generate_categorical_dataframe( + sm: nx.DiGraph, + n_samples: int, + distribution: str = "logit", + n_categories: int = 3, + noise_scale: float = 1.0, + intercept: bool = False, + seed: int = None, + kernel: Optional[Kernel] = None, +) -> pd.DataFrame: + """ + Generates a dataframe with samples from SEM with specified type of noise. + + Args: + sm: A DAG in form of a networkx or StructureModel. Does not require weights. + n_samples: The number of rows/observations to sample. + kernel: A kernel from sklearn.gaussian_process.kernels like RBF(1) or + Matern(1) or any combinations thereof. The kernels are used to + create the latent variable for the binary / categorical variables + and are directly used for continuous variables. + distribution: The type of distribution to use for the noise + of a variable. Options: 'probit'/'normal' (alias), + "logit"/"gumbel" (alias). Logit is default. + n_categories: Number of categories per variable/node. + noise_scale: The standard deviation of the noise. The categorical features + are created using a latent variable approach. The noise standard + deviation determines how much weight the "mean" estimate has on + the feature value. + intercept: Whether to use an intercept for the latent variable of each feature. + seed: Random state + Returns: + x_mat: [n_samples, d_nodes] sample matrix + Raises: + ValueError: if distribution is not 'probit', 'normal', 'logit', 'gumbel' + """ + + if kernel is None: + return sem_generator( + graph=sm, + default_type="categorical:{}".format(n_categories), + n_samples=n_samples, + distributions={"categorical": distribution}, + noise_std=noise_scale, + intercept=intercept, + seed=seed, + ) + + return nonlinear_sem_generator( + graph=sm, + kernel=kernel, + default_type="categorical:{}".format(n_categories), + n_samples=n_samples, + distributions={"categorical": distribution}, + noise_std=noise_scale, + seed=seed, + ) + + +def generate_structure_dynamic( # pylint: disable=too-many-arguments + num_nodes: int, + p: int, + degree_intra: float, + degree_inter: float, + graph_type_intra: str = "erdos-renyi", + graph_type_inter: str = "erdos-renyi", + w_min_intra: float = 0.5, + w_max_intra: float = 0.5, + w_min_inter: float = 0.5, + w_max_inter: float = 0.5, + w_decay: float = 1.0, +) -> StructureModel: + """ + Generates a dynamic DAG at random. + + Args: + num_nodes: Number of nodes + p: maximum lag to be considered in the structure + degree_intra: expected degree on nodes from the current state + degree_inter: expected degree on nodes from the lagged nodes + graph_type_intra: + - erdos-renyi: constructs a graph such that the probability of any given edge is degree / (num_nodes - 1) + - barabasi-albert: constructs a scale-free graph from an initial connected graph of (degree / 2) nodes + - full: constructs a fully-connected graph - degree has no effect + graph_type_inter: + - erdos-renyi: constructs a graph such that the probability of any given edge is degree / (num_nodes - 1) + - full: connect all past nodes to all present nodes + w_min_intra: minimum weight for intra-slice nodes + w_max_intra: maximum weight for intra-slice nodes + w_min_inter: minimum weight for inter-slice nodes + w_max_inter: maximum weight for inter-slice nodes + w_decay: exponent of weights decay for slices that are farther apart. Default is 1.0, which implies no decay + + Raises: + ValueError: if graph type unknown or `num_nodes < 2` + + Returns: + StructureModel containing all simulated nodes and edges (intra- and inter-slice) + """ + sm_intra = generate_structure( + num_nodes=num_nodes, + degree=degree_intra, + graph_type=graph_type_intra, + w_min=w_min_intra, + w_max=w_max_intra, + ) + sm_inter = _generate_inter_structure( + num_nodes=num_nodes, + p=p, + degree=degree_inter, + graph_type=graph_type_inter, + w_min=w_min_inter, + w_max=w_max_inter, + w_decay=w_decay, + ) + res = StructureModel() + res.add_nodes_from(sm_inter.nodes) + res.add_nodes_from(["{var}_lag0".format(var=u) for u in sm_intra.nodes]) + res.add_weighted_edges_from(sm_inter.edges.data("weight")) + res.add_weighted_edges_from( + [ + ("{var}_lag0".format(var=u), "{var}_lag0".format(var=v), w) + for u, v, w in sm_intra.edges.data("weight") + ] + ) + return res + + +def _generate_inter_structure( + num_nodes: int, + p: int, + degree: float, + graph_type: str, + w_min: float, + w_max: float, + w_decay: float = 1.0, + neg: float = 0.5, +) -> StructureModel: + """Simulate random DAG between two time slices. + + Args: + num_nodes: number of nodes per slice + p: number of slices that influence current slice + degree: expected in-degree of current time slice + graph_type: {'erdos-renyi' 'full'} + w_min: minimum weight for inter-slice nodes + w_max: maximum weight for inter-slice nodes + w_decay: exponent of weights decay for slices that are farther apart. Default is 1.0, which implies no decay + neg: the proportion of edge weights expected to be negative. By default, 50% of the edges are expected + to be negative weight (`neg == 0.5`). + + Returns: + G_inter: weighted, bipartite DAG for inter-slice connections + + Raises: + ValueError: if graph type not known + """ + if w_min > w_max: + raise ValueError( + "Absolute minimum weight must be less than or equal to maximum weight: {} > {}".format( + w_min, w_max + ) + ) + + if graph_type == "erdos-renyi": + prob = degree / num_nodes + b = (np.random.rand(p * num_nodes, num_nodes) < prob).astype(float) + elif graph_type == "full": # ignore degree, only for experimental use + b = np.ones([p * num_nodes, num_nodes]) + else: + raise ValueError( + "Unknown inter-slice graph type `{n}`".format(n=graph_type) + + ". Valid types are 'erdos-renyi' and 'full'" + ) + u = [] + for i in range(p): + u_i = np.random.uniform(low=w_min, high=w_max, size=[num_nodes, num_nodes]) / ( + w_decay ** i + ) + u_i[np.random.rand(num_nodes, num_nodes) < neg] *= -1 + u.append(u_i) + + u = np.concatenate(u, axis=0) if u else np.empty(b.shape) + a = (b != 0).astype(float) * u + + df = pd.DataFrame( + a, + index=[ + "{var}_lag{l_val}".format(var=var, l_val=l_val) + for l_val in range(1, p + 1) + for var in range(num_nodes) + ], + columns=[ + "{var}_lag{l_val}".format(var=var, l_val=0) for var in range(num_nodes) + ], + ) + idxs, cols = list(df.index), list(df.columns) + for i in idxs: + df[i] = 0 + for i in cols: + df.loc[i, :] = 0 + + g_inter = StructureModel(df) + return g_inter + + +def generate_dataframe_dynamic( # pylint: disable=R0914 + g: StructureModel, + n_samples: int = 1000, + burn_in: int = 100, + sem_type: str = "linear-gauss", + noise_scale: float = 1.0, + drift: np.ndarray = None, +) -> pd.DataFrame: + """Simulate samples from dynamic SEM with specified type of noise. + Args: + g: Dynamic DAG + n_samples: number of samples + burn_in: number of samples to discard + sem_type: {linear-gauss,linear-exp,linear-gumbel} + noise_scale: scale parameter of noise distribution in linear SEM + drift: array of drift terms for each node, if None then the drift is 0 + Returns: + X: [n,d] sample matrix, row t is X_t + Y: [n,d*p] sample matrix, row t is [X_{t-1}, ..., X_{t-p}] + Raises: + ValueError: if sem_type isn't linear-gauss/linear_exp/linear-gumbel + """ + s_types = ("linear-gauss", "linear-exp", "linear-gumbel") + if sem_type not in s_types: + raise ValueError( + "unknown sem type {st}. Available types are: {sts}".format( + st=sem_type, sts=s_types + ) + ) + intra_nodes = sorted(el for el in g.nodes if "_lag0" in el) + inter_nodes = sorted(el for el in g.nodes if "_lag0" not in el) + w_mat = nx.to_numpy_array(g, nodelist=intra_nodes) + a_mat = nx.to_numpy_array(g, nodelist=intra_nodes + inter_nodes)[ + len(intra_nodes) :, : len(intra_nodes) + ] + g_intra = nx.DiGraph(w_mat) + g_inter = nx.bipartite.from_biadjacency_matrix( + csr_matrix(a_mat), create_using=nx.DiGraph + ) + d = w_mat.shape[0] + p = a_mat.shape[0] // d + total_length = n_samples + burn_in + X = np.zeros([total_length, d]) + Xlags = np.zeros([total_length, p * d]) + ordered_vertices = list(nx.topological_sort(g_intra)) + if drift is None: + drift = np.zeros(d) + for t in range(total_length): + for j in ordered_vertices: + parents = list(g_intra.predecessors(j)) + parents_prev = list(g_inter.predecessors(j + p * d)) + X[t, j] = ( + drift[j] + + X[t, parents].dot(w_mat[parents, j]) + + Xlags[t, parents_prev].dot(a_mat[parents_prev, j]) + ) + if sem_type == "linear-gauss": + X[t, j] = X[t, j] + np.random.normal(scale=noise_scale) + elif sem_type == "linear-exp": + X[t, j] = X[t, j] + np.random.exponential(scale=noise_scale) + elif sem_type == "linear-gumbel": + X[t, j] = X[t, j] + np.random.gumbel(scale=noise_scale) + + if (t + 1) < total_length: + Xlags[t + 1, :] = np.concatenate([X[t, :], Xlags[t, :]])[: d * p] + return pd.concat( + [ + pd.DataFrame(X[-n_samples:], columns=intra_nodes), + pd.DataFrame(Xlags[-n_samples:], columns=inter_nodes), + ], + axis=1, + ) + + +def gen_stationary_dyn_net_and_df( # pylint: disable=R0913, R0914 + num_nodes: int = 10, + n_samples: int = 100, + p: int = 1, + degree_intra: float = 3, + degree_inter: float = 3, + graph_type_intra: str = "erdos-renyi", + graph_type_inter: str = "erdos-renyi", + w_min_intra: float = 0.5, + w_max_intra: float = 0.5, + w_min_inter: float = 0.5, + w_max_inter: float = 0.5, + w_decay: float = 1.0, + sem_type: str = "linear-gauss", + noise_scale: float = 1, + max_data_gen_trials: int = 1000, +) -> Tuple[StructureModel, pd.DataFrame, List[str], List[str]]: + """ + Generates a dynamic structure model as well a dataframe representing a time series realisation of that model. + We do checks to verify the network is stationary, and iterate until the resulting network is stationary. + Args: + num_nodes: number of nodes in the intra-slice structure + n_samples: number of points to sample from the model, as a time series + p: lag value for the dynamic structure + degree_intra: expected degree for intra_slice nodes + degree_inter: expected degree for inter_slice nodes + graph_type_intra: + - erdos-renyi: constructs a graph such that the probability of any given edge is degree / (num_nodes - 1) + - barabasi-albert: constructs a scale-free graph from an initial connected graph of (degree / 2) nodes + - full: constructs a fully-connected graph - degree has no effect + graph_type_inter: + - erdos-renyi: constructs a graph such that the probability of any given edge is degree / (num_nodes - 1) + - full: connect all past nodes to all present nodesw_min_intra: + w_min_intra: minimum weight on intra-slice adjacency matrix + w_max_intra: maximum weight on intra-slice adjacency matrix + w_min_inter: minimum weight on inter-slice adjacency matrix + w_max_inter: maximum weight on inter-slice adjacency matrix + w_decay: exponent of weights decay for slices that are farther apart. Default is 1.0, which implies no decay + sem_type: {linear-gauss,linear-exp,linear-gumbel} + noise_scale: scale parameter of noise distribution in linear SEM + max_data_gen_trials: maximun number of attempts until obtaining a seemingly stationary model + Returns: + Tuple with: + - the model created,as a Structure model + - DataFrame representing the time series created from the model + - Intra-slice nodes names + - Inter-slice nodes names + """ + + with np.errstate(over="raise", invalid="raise"): + burn_in = max(n_samples // 10, 50) + + simulate_flag = True + g, intra_nodes, inter_nodes = None, None, None + + while simulate_flag: + max_data_gen_trials -= 1 + if max_data_gen_trials <= 0: + simulate_flag = False + + try: + simulate_graphs_flag = True + while simulate_graphs_flag: + + g = generate_structure_dynamic( + num_nodes=num_nodes, + p=p, + degree_intra=degree_intra, + degree_inter=degree_inter, + graph_type_intra=graph_type_intra, + graph_type_inter=graph_type_inter, + w_min_intra=w_min_intra, + w_max_intra=w_max_intra, + w_min_inter=w_min_inter, + w_max_inter=w_max_inter, + w_decay=w_decay, + ) + intra_nodes = sorted([el for el in g.nodes if "_lag0" in el]) + inter_nodes = sorted([el for el in g.nodes if "_lag0" not in el]) + # Exclude empty graphs from consideration unless input degree is 0 + if ( + ( + [(u, v) for u, v in g.edges if u in intra_nodes] + and [(u, v) for u, v in g.edges if u in inter_nodes] + ) + or degree_intra == 0 + or degree_inter == 0 + ): + simulate_graphs_flag = False + + # generate single time series + df = ( + generate_dataframe_dynamic( + g, + n_samples=n_samples + burn_in, + sem_type=sem_type, + noise_scale=noise_scale, + ) + .loc[burn_in:, intra_nodes + inter_nodes] + .reset_index(drop=True) + ) + + if df.isna().any(axis=None): + continue + except (OverflowError, FloatingPointError): + continue + if (df.abs().max().max() < 1e3) or (max_data_gen_trials <= 0): + simulate_flag = False + if max_data_gen_trials <= 0: + warnings.warn( + "Could not simulate data, returning constant dataframe", UserWarning + ) + + df = pd.DataFrame( + np.ones((n_samples, num_nodes * (1 + p))), + columns=intra_nodes + inter_nodes, + ) + return g, df, intra_nodes, inter_nodes diff --git a/causalnex/structure/dynotears.py b/causalnex/structure/dynotears.py new file mode 100644 index 0000000..e7d1202 --- /dev/null +++ b/causalnex/structure/dynotears.py @@ -0,0 +1,494 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Tools to learn a Dynamic Bayesian Network which describe the conditional dependencies between variables in a time-series +dataset. +""" + +import warnings +from typing import Dict, List, Tuple, Union + +import numpy as np +import pandas as pd +import scipy.linalg as slin +import scipy.optimize as sopt + +from causalnex.structure import StructureModel + +from .transformers import DynamicDataTransformer + + +def from_pandas_dynamic( # pylint: disable=too-many-arguments + time_series: Union[pd.DataFrame, List[pd.DataFrame]], + p: int, + lambda_w: float = 0.1, + lambda_a: float = 0.1, + max_iter: int = 100, + h_tol: float = 1e-8, + w_threshold: float = 0.0, + tabu_edges: List[Tuple[int, int, int]] = None, + tabu_parent_nodes: List[int] = None, + tabu_child_nodes: List[int] = None, +) -> StructureModel: + """ + Learn the graph structure of a Dynamic Bayesian Network describing conditional dependencies between variables in + data. The input data is a time series or a list of realisations of a same time series. + The optimisation is to minimise a score function F(W, A) over the graph's contemporaneous (intra-slice) weighted + adjacency matrix, W, and lagged (inter-slice) weighted adjacency matrix, A, subject to the a constraint function + h(W), where h_value(W) == 0 characterises an acyclic graph. h(W) > 0 is a continuous, differentiable function that + encapsulated how acyclic the graph is (less = more acyclic). + + Based on "DYNOTEARS: Structure Learning from Time-Series Data". + https://arxiv.org/abs/2002.00498 + @inproceedings{pamfil2020dynotears, + title={DYNOTEARS: Structure Learning from Time-Series Data}, + author={Pamfil, Roxana and Sriwattanaworachai, Nisara and Desai, Shaan and Pilgerstorfer, + Philip and Georgatzis, Konstantinos and Beaumont, Paul and Aragam, Bryon}, + booktitle={International Conference on Artificial Intelligence and Statistics}, + pages={1595--1605}, + year={2020}year={2020}, + } + Args: + time_series: pd.DataFrame or List of pd.DataFrame instances. + If a list is provided each element of the list being an realisation of a time series (i.e. time series governed + by the same processes) + The columns of the data frame represent the variables in the model, and the *index represents the time index*. + Successive events, therefore, must be indexed with one integer of difference between them too. + p: Number of past interactions we allow the model to create. The state of a variable at time `t` is affected by + past variables up to a `t-p`, as well as by other variables at `t`. + lambda_w: parameter for l1 regularisation of intra-slice edges + lambda_a: parameter for l1 regularisation of inter-slice edges + max_iter: max number of dual ascent steps during optimisation. + h_tol: exit if h(W) < h_tol (as opposed to strict definition of 0). + w_threshold: fixed threshold for absolute edge weights. + tabu_edges: list of edges(lag, from, to) not to be included in the graph. `lag == 0` implies that the edge is + forbidden in the INTRA graph (W), while lag > 0 implies an INTER-slice weight equal zero. + tabu_parent_nodes: list of nodes banned from being a parent of any other nodes. + tabu_child_nodes: list of nodes banned from being a child of any other nodes. + + Returns: + StructureModel representing the model learnt. The node names are noted as `{var}_lag{l}`, where `var` is the + original variable name as in the give in the input data frames and `l`, in 0,1,2..p is the correspondent + time lag. + """ + time_series = [time_series] if not isinstance(time_series, list) else time_series + + X, Xlags = DynamicDataTransformer(p=p).fit_transform(time_series, return_df=False) + + col_idx = {c: i for i, c in enumerate(time_series[0].columns)} + idx_col = {i: c for c, i in col_idx.items()} + + if tabu_edges: + tabu_edges = [(lag, col_idx[u], col_idx[v]) for lag, u, v in tabu_edges] + if tabu_parent_nodes: + tabu_parent_nodes = [col_idx[n] for n in tabu_parent_nodes] + if tabu_child_nodes: + tabu_child_nodes = [col_idx[n] for n in tabu_child_nodes] + + g = from_numpy_dynamic( + X, + Xlags, + lambda_w, + lambda_a, + max_iter, + h_tol, + w_threshold, + tabu_edges, + tabu_parent_nodes, + tabu_child_nodes, + ) + + sm = StructureModel() + sm.add_nodes_from( + [ + "{var}_lag{l_val}".format(var=var, l_val=l_val) + for var in col_idx.keys() + for l_val in range(p + 1) + ] + ) + sm.add_weighted_edges_from( + [ + ( + _format_name_from_pandas(idx_col, u), + _format_name_from_pandas(idx_col, v), + w, + ) + for u, v, w in g.edges.data("weight") + ], + origin="learned", + ) + + return sm + + +def _format_name_from_pandas(idx_col: Dict[int, str], from_numpy_node: str) -> str: + """ + Helper function for `from_pandas_dynamic`. converts a node from the `from_numpy_dynamic` format to the `from_pandas` + format + Args: + idx_col: map from variable to intdex + from_numpy_node: nodes in the structure model output by `from_numpy_dynamic`. + Returns: + nodes in from_pandas_dynamic format + """ + idx, lag_val = from_numpy_node.split("_lag") + return "{var}_lag{l_val}".format(var=idx_col[int(idx)], l_val=lag_val) + + +def from_numpy_dynamic( # pylint: disable=too-many-arguments + X: np.ndarray, + Xlags: np.ndarray, + lambda_w: float = 0.1, + lambda_a: float = 0.1, + max_iter: int = 100, + h_tol: float = 1e-8, + w_threshold: float = 0.0, + tabu_edges: List[Tuple[int, int, int]] = None, + tabu_parent_nodes: List[int] = None, + tabu_child_nodes: List[int] = None, +) -> StructureModel: + """ + Learn the graph structure of a Dynamic Bayesian Network describing conditional dependencies between variables in + data. The input data is time series data present in numpy arrays X and Xlags. + + The optimisation is to minimise a score function F(W, A) over the graph's contemporaneous (intra-slice) weighted + adjacency matrix, W, and lagged (inter-slice) weighted adjacency matrix, A, subject to the a constraint function + h(W), where h_value(W) == 0 characterises an acyclic graph. h(W) > 0 is a continuous, differentiable function that + encapsulated how acyclic the graph is (less = more acyclic). + + Based on "DYNOTEARS: Structure Learning from Time-Series Data". + https://arxiv.org/abs/2002.00498 + @inproceedings{pamfil2020dynotears, + title={DYNOTEARS: Structure Learning from Time-Series Data}, + author={Pamfil, Roxana and Sriwattanaworachai, Nisara and Desai, Shaan and Pilgerstorfer, + Philip and Georgatzis, Konstantinos and Beaumont, Paul and Aragam, Bryon}, + booktitle={International Conference on Artificial Intelligence and Statistics}, + pages={1595--1605}, + year={2020}year={2020}, + } + + Args: + X (np.ndarray): 2d input data, axis=1 is data columns, axis=0 is data rows. Each column represents one variable, + and each row represents x(m,t) i.e. the mth time series at time t. + Xlags (np.ndarray): shifted data of X with lag orders stacking horizontally. Xlags=[shift(X,1)|...|shift(X,p)] + lambda_w (float): l1 regularization parameter of intra-weights W + lambda_a (float): l1 regularization parameter of inter-weights A + max_iter: max number of dual ascent steps during optimisation + h_tol (float): exit if h(W) < h_tol (as opposed to strict definition of 0) + w_threshold: fixed threshold for absolute edge weights. + tabu_edges: list of edges(lag, from, to) not to be included in the graph. `lag == 0` implies that the edge is + forbidden in the INTRA graph (W), while lag > 0 implies an INTER weight equal zero. + tabu_parent_nodes: list of nodes banned from being a parent of any other nodes. + tabu_child_nodes: list of nodes banned from being a child of any other nodes. + Returns: + W (np.ndarray): d x d estimated weighted adjacency matrix of intra slices + A (np.ndarray): d x pd estimated weighted adjacency matrix of inter slices + + Raises: + ValueError: If X or Xlags does not contain data, or dimensions of X and Xlags do not conform + """ + _, d_vars = X.shape + p_orders = Xlags.shape[1] // d_vars + + bnds_w = 2 * [ + (0, 0) + if i == j + else (0, 0) + if tabu_edges is not None and (0, i, j) in tabu_edges + else (0, 0) + if tabu_parent_nodes is not None and i in tabu_parent_nodes + else (0, 0) + if tabu_child_nodes is not None and j in tabu_child_nodes + else (0, None) + for i in range(d_vars) + for j in range(d_vars) + ] + + bnds_a = [] + for k in range(1, p_orders + 1): + bnds_a.extend( + 2 + * [ + (0, 0) + if tabu_edges is not None and (k, i, j) in tabu_edges + else (0, 0) + if tabu_parent_nodes is not None and i in tabu_parent_nodes + else (0, 0) + if tabu_child_nodes is not None and j in tabu_child_nodes + else (0, None) + for i in range(d_vars) + for j in range(d_vars) + ] + ) + + bnds = bnds_w + bnds_a + w_est, a_est = _learn_dynamic_structure( + X, Xlags, bnds, lambda_w, lambda_a, max_iter, h_tol + ) + + w_est[np.abs(w_est) < w_threshold] = 0 + a_est[np.abs(a_est) < w_threshold] = 0 + sm = _matrices_to_structure_model(w_est, a_est) + return sm + + +def _matrices_to_structure_model( + w_est: np.ndarray, a_est: np.ndarray +) -> StructureModel: + """ + Converts the matrices output by dynotears (W and A) into a StructureModel + We use the following convention: + - {var}_lag{l} where l is the lag value (i.e. from how many previous timestamps the edge is coming + - if we deal with a intra_slice_node, `l == 0` + Args: + w_est: Intra-slice weight matrix + a_est: Inter-slice matrix + + Returns: + StructureModel representing the structure learnt + + """ + sm = StructureModel() + lag_cols = [ + "{var}_lag{l_val}".format(var=var, l_val=l_val) + for l_val in range(1 + (a_est.shape[0] // a_est.shape[1])) + for var in range(a_est.shape[1]) + ] + sm.add_nodes_from(lag_cols) + sm.add_edges_from( + [ + (lag_cols[i], lag_cols[j], dict(weight=w_est[i, j])) + for i in range(w_est.shape[0]) + for j in range(w_est.shape[1]) + if w_est[i, j] != 0 + ] + ) + sm.add_edges_from( + [ + (lag_cols[i + w_est.shape[0]], lag_cols[j], dict(weight=a_est[i, j])) + for i in range(a_est.shape[0]) + for j in range(a_est.shape[1]) + if a_est[i, j] != 0 + ] + ) + return sm + + +def _reshape_wa( + wa_vec: np.ndarray, d_vars: int, p_orders: int +) -> Tuple[np.ndarray, np.ndarray]: + """ + Helper function for `_learn_dynamic_structure`. Transform adjacency vector to matrix form + + Args: + wa_vec (np.ndarray): current adjacency vector with intra- and inter-slice weights + d_vars (int): number of variables in the model + p_orders (int): number of past indexes we to use + Returns: + intra- and inter-slice adjacency matrices + """ + + w_tilde = wa_vec.reshape([2 * (p_orders + 1) * d_vars, d_vars]) + w_plus = w_tilde[:d_vars, :] + w_minus = w_tilde[d_vars : 2 * d_vars, :] + w_mat = w_plus - w_minus + a_plus = ( + w_tilde[2 * d_vars :] + .reshape(2 * p_orders, d_vars ** 2)[::2] + .reshape(d_vars * p_orders, d_vars) + ) + a_minus = ( + w_tilde[2 * d_vars :] + .reshape(2 * p_orders, d_vars ** 2)[1::2] + .reshape(d_vars * p_orders, d_vars) + ) + a_mat = a_plus - a_minus + return w_mat, a_mat + + +def _learn_dynamic_structure( + X: np.ndarray, + Xlags: np.ndarray, + bnds: List[Tuple[float, float]], + lambda_w: float = 0.1, + lambda_a: float = 0.1, + max_iter: int = 100, + h_tol: float = 1e-8, +) -> Tuple[np.ndarray, np.ndarray]: + """ + Learn the graph structure of a Dynamic Bayesian Network describing conditional dependencies between data variables. + + The optimisation is to minimise a score function F(W, A) over the graph's contemporaneous (intra-slice) weighted + adjacency matrix, W, and lagged (inter-slice) weighted adjacency matrix, A, subject to the a constraint function + h(W), where h_value(W) == 0 characterises an acyclic graph. h(W) > 0 is a continuous, differentiable function that + encapsulated how acyclic the graph is (less = more acyclic). + + Based on "DYNOTEARS: Structure Learning from Time-Series Data". + https://arxiv.org/abs/2002.00498 + @inproceedings{pamfil2020dynotears, + title={DYNOTEARS: Structure Learning from Time-Series Data}, + author={Pamfil, Roxana and Sriwattanaworachai, Nisara and Desai, Shaan and Pilgerstorfer, + Philip and Georgatzis, Konstantinos and Beaumont, Paul and Aragam, Bryon}, + booktitle={International Conference on Artificial Intelligence and Statistics}, + pages={1595--1605}, + year={2020}year={2020}, + } + + Args: + X (np.ndarray): 2d input data, axis=1 is data columns, axis=0 is data rows. Each column represents one variable, + and each row represents x(m,t) i.e. the mth time series at time t. + Xlags (np.ndarray): shifted data of X with lag orders stacking horizontally. Xlags=[shift(X,1)|...|shift(X,p)] + bnds: Box constraints of L-BFGS-B to ban self-loops in W, enforce non-negativity of w_plus, w_minus, a_plus, + a_minus, and help with stationarity in A + lambda_w (float): l1 regularization parameter of intra-weights W + lambda_a (float): l1 regularization parameter of inter-weights A + max_iter (int): max number of dual ascent steps during optimisation + h_tol (float): exit if h(W) < h_tol (as opposed to strict definition of 0) + + Returns: + W (np.ndarray): d x d estimated weighted adjacency matrix of intra slices + A (np.ndarray): d x pd estimated weighted adjacency matrix of inter slices + + Raises: + ValueError: If X or Xlags does not contain data, or dimensions of X and Xlags do not conform + """ + if X.size == 0: + raise ValueError("Input data X is empty, cannot learn any structure") + if Xlags.size == 0: + raise ValueError("Input data Xlags is empty, cannot learn any structure") + if X.shape[0] != Xlags.shape[0]: + raise ValueError("Input data X and Xlags must have the same number of rows") + if Xlags.shape[1] % X.shape[1] != 0: + raise ValueError( + "Number of columns of Xlags must be a multiple of number of columns of X" + ) + + n, d_vars = X.shape + p_orders = Xlags.shape[1] // d_vars + + def _h(wa_vec: np.ndarray) -> float: + """ + Constraint function of the dynotears + + Args: + wa_vec (np.ndarray): current adjacency vector with intra- and inter-slice weights + + Returns: + float: DAGness of the intra-slice adjacency matrix W (0 == DAG, >0 == cyclic) + """ + + _w_mat, _ = _reshape_wa(wa_vec, d_vars, p_orders) + return np.trace(slin.expm(_w_mat * _w_mat)) - d_vars + + def _func(wa_vec: np.ndarray) -> float: + """ + Objective function that the dynotears tries to minimise + + Args: + wa_vec (np.ndarray): current adjacency vector with intra- and inter-slice weights + + Returns: + float: objective + """ + + _w_mat, _a_mat = _reshape_wa(wa_vec, d_vars, p_orders) + loss = ( + 0.5 + / n + * np.square( + np.linalg.norm( + X.dot(np.eye(d_vars, d_vars) - _w_mat) - Xlags.dot(_a_mat), "fro" + ) + ) + ) + _h_value = _h(wa_vec) + l1_penalty = lambda_w * (wa_vec[: 2 * d_vars ** 2].sum()) + lambda_a * ( + wa_vec[2 * d_vars ** 2 :].sum() + ) + return loss + 0.5 * rho * _h_value * _h_value + alpha * _h_value + l1_penalty + + def _grad(wa_vec: np.ndarray) -> np.ndarray: + """ + Gradient function used to compute next step in dynotears + + Args: + wa_vec (np.ndarray): current adjacency vector with intra- and inter-slice weights + + Returns: + gradient vector + """ + + _w_mat, _a_mat = _reshape_wa(wa_vec, d_vars, p_orders) + e_mat = slin.expm(_w_mat * _w_mat) + loss_grad_w = ( + -1.0 + / n + * (X.T.dot(X.dot(np.eye(d_vars, d_vars) - _w_mat) - Xlags.dot(_a_mat))) + ) + obj_grad_w = ( + loss_grad_w + + (rho * (np.trace(e_mat) - d_vars) + alpha) * e_mat.T * _w_mat * 2 + ) + obj_grad_a = ( + -1.0 + / n + * (Xlags.T.dot(X.dot(np.eye(d_vars, d_vars) - _w_mat) - Xlags.dot(_a_mat))) + ) + + grad_vec_w = np.append( + obj_grad_w, -obj_grad_w, axis=0 + ).flatten() + lambda_w * np.ones(2 * d_vars ** 2) + grad_vec_a = obj_grad_a.reshape(p_orders, d_vars ** 2) + grad_vec_a = np.hstack( + (grad_vec_a, -grad_vec_a) + ).flatten() + lambda_a * np.ones(2 * p_orders * d_vars ** 2) + return np.append(grad_vec_w, grad_vec_a, axis=0) + + # initialise matrix, weights and constraints + wa_est = np.zeros(2 * (p_orders + 1) * d_vars ** 2) + wa_new = np.zeros(2 * (p_orders + 1) * d_vars ** 2) + rho, alpha, h_value, h_new = 1.0, 0.0, np.inf, np.inf + + for n_iter in range(max_iter): + while rho < 1e20: + wa_new = sopt.minimize( + _func, wa_est, method="L-BFGS-B", jac=_grad, bounds=bnds + ).x + h_new = _h(wa_new) + if h_new > 0.25 * h_value: + rho *= 10 + else: + break + wa_est = wa_new + h_value = h_new + alpha += rho * h_value + if h_value <= h_tol: + break + if h_value > h_tol and n_iter == max_iter - 1: + warnings.warn("Failed to converge. Consider increasing max_iter.") + return _reshape_wa(wa_est, d_vars, p_orders) diff --git a/causalnex/structure/notears.py b/causalnex/structure/notears.py index 9dab187..0472cc2 100644 --- a/causalnex/structure/notears.py +++ b/causalnex/structure/notears.py @@ -102,6 +102,8 @@ def from_numpy( # n examples, d properties _, d = X.shape + _assert_all_finite(X) + bnds = [ (0, 0) if i == j @@ -162,6 +164,8 @@ def from_numpy_lasso( # n examples, d properties _, d = X.shape + _assert_all_finite(X) + bnds = [ (0, 0) if i == j @@ -550,3 +554,32 @@ def _grad(w_vec: np.ndarray) -> np.ndarray: w_new = w_est[: d ** 2].reshape([d, d]) - w_est[d ** 2 :].reshape([d, d]) w_new[np.abs(w_new) < w_threshold] = 0 return StructureModel(w_new.reshape([d, d])) + + +def _assert_all_finite(X: np.ndarray): + """Throw a ValueError if X contains NaN or Infinity. + + Based on Sklearn method to handle NaN & Infinity. + @inproceedings{sklearn_api, + author = {Lars Buitinck and Gilles Louppe and Mathieu Blondel and + Fabian Pedregosa and Andreas Mueller and Olivier Grisel and + Vlad Niculae and Peter Prettenhofer and Alexandre Gramfort + and Jaques Grobler and Robert Layton and Jake VanderPlas and + Arnaud Joly and Brian Holt and Ga{\"{e}}l Varoquaux}, + title = {{API} design for machine learning software: experiences from the scikit-learn + project}, + booktitle = {ECML PKDD Workshop: Languages for Data Mining and Machine Learning}, + year = {2013}, + pages = {108--122}, + } + + Args: + X: Array to validate + + Raises: + ValueError: If X contains NaN or Infinity + """ + + msg_err = "Input contains NaN, infinity or a value too large for {!r}." + if not np.isfinite(X).all(): + raise ValueError(msg_err.format(X.dtype)) diff --git a/causalnex/structure/pytorch/__init__.py b/causalnex/structure/pytorch/__init__.py new file mode 100644 index 0000000..a17bd56 --- /dev/null +++ b/causalnex/structure/pytorch/__init__.py @@ -0,0 +1,36 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +``causalnex.structure.pytorch`` provides functionality to define or learn structure using pytorch. +""" + +__all__ = ["from_numpy", "from_pandas", "NotearsMLP"] + +from .core import NotearsMLP +from .notears import from_numpy, from_pandas diff --git a/causalnex/structure/pytorch/core.py b/causalnex/structure/pytorch/core.py new file mode 100644 index 0000000..58564d9 --- /dev/null +++ b/causalnex/structure/pytorch/core.py @@ -0,0 +1,469 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This code is modified from this git repo: https://github.com/xunzheng/notears + +@inproceedings{zheng2020learning, + author = {Zheng, Xun and Dan, Chen and Aragam, Bryon and Ravikumar, Pradeep and Xing, Eric P.}, + booktitle = {International Conference on Artificial Intelligence and Statistics}, + title = {{Learning sparse nonparametric DAGs}}, + year = {2020} +} +""" +import logging +from typing import Iterable, List, Tuple, Union + +import numpy as np +import scipy.optimize as sopt +import torch +import torch.nn as nn +from sklearn.base import BaseEstimator + +from .nonlinear import LocallyConnected + + +class NotearsMLP(nn.Module, BaseEstimator): + """ + Class for NOTEARS MLP (Multi-layer Perceptron) model. + The model weights consist of dag_layer and loc_lin_layer weights respectively. + dag_layer weight is the weight of the first fully connected layer which determines the causal structure. + loc_lin_layer weights are the weight of hidden layers after the first fully connected layer + """ + + def __init__( + self, + n_features: int, + use_bias: bool = False, + hidden_layer_units: Iterable[int] = (0,), + bounds: List[Tuple[int, int]] = None, + lasso_beta: float = 0.0, + ridge_beta: float = 0.0, + nonlinear_clamp: float = 1e-2, + ): + """ + Constructor for NOTEARS MLP class. + + Args: + n_features: number of input features + use_bias: True to add the intercept to the model + hidden_layer_units: An iterable where its length determine the number of layers used, + and the numbers determine the number of nodes used for the layer in order. + bounds: bound constraint for each parameter. + lasso_beta: Constant that multiplies the lasso term (l1 regularisation). + It only applies to dag_layer weight. + ridge_beta: Constant that multiplies the ridge term (l2 regularisation). + It applies to both dag_layer and loc_lin_layer weights. + nonlinear_clamp: Value used to soft clamp the nonlinear layer normalisation. + Prevents the weights from being scaled above 1/nonlinear_clamp. + """ + super().__init__() + self.device = torch.device("cpu") + self.lasso_beta = lasso_beta + self.ridge_beta = ridge_beta + self.nonlinear_clamp = nonlinear_clamp + + # cast to list for later concat. + self.dims = ( + [n_features] + list(hidden_layer_units) + [1] + if hidden_layer_units[0] + else [n_features, 1] + ) + + # dag_layer: initial linear layer + self.dag_layer = nn.Linear( + self.dims[0], self.dims[0] * self.dims[1], bias=use_bias + ).float() + nn.init.zeros_(self.dag_layer.weight) + if use_bias: + nn.init.zeros_(self.dag_layer.bias) + + # loc_lin_layer: local linear layers + layers = [ + LocallyConnected( + self.dims[0], input_features, output_features, bias=use_bias + ).float() + for input_features, output_features in zip(self.dims[1:-1], self.dims[2:]) + ] + self._loc_lin_layer_weights = nn.ModuleList(layers) + for layer in layers: + layer.reset_parameters() + + # set the bounds as an attribute on the weights object + self.dag_layer.weight.bounds = bounds + # type the adjacency matrix + self.adj = None + self.adj_mean_effect = None + + @property + def _logger(self): + return logging.getLogger(self.__class__.__name__) + + @property + def dag_layer_bias(self) -> Union[torch.Tensor, None]: + """ + dag_layer bias is the bias of the first fully connected layer which determines the causal structure. + Returns: + dag_layer bias if use_bias is True, otherwise None + """ + return self.dag_layer.bias + + @property + def dag_layer_weight(self) -> torch.Tensor: + """ + dag_layer weight is the weight of the first fully connected layer which determines the causal structure. + Returns: + dag_layer weight + """ + return self.dag_layer.weight + + @property + def loc_lin_layer_weights(self) -> torch.Tensor: + """ + loc_lin_layer weights are the weight of hidden layers after the first fully connected layer. + Returns: + loc_lin_layer weights + """ + return self._loc_lin_layer_weights + + # pylint: disable=arguments-differ + def forward(self, x: torch.Tensor) -> torch.Tensor: # [n, d] -> [n, d] + """ + Feed forward calculation for the model. + + Args: + x: input torch tensor + + Returns: + output tensor from the model + """ + x = self.dag_layer(x) # [n, d * m1] + x = x.view(-1, self.dims[0], self.dims[1]) # [n, d, m1] + for layer in self.loc_lin_layer_weights: + x = torch.sigmoid(x) # [n, d, m1] + # soft clamp the denominator to prevent divide by zero and prevent very large weight increases + x = (x - x.mean(dim=0).detach()) / torch.sqrt( + (self.nonlinear_clamp + x.var(dim=0).detach()) + ) + + x = layer(x) # [n, d, m2] + x = x.squeeze(dim=2) # [n, d] + return x + + @property + def bias(self) -> Union[np.ndarray, None]: + """ + Get the vector of feature biases + + Returns: + bias vector if use_bias is True, otherwise None + """ + bias = self.dag_layer_bias + return bias if bias is None else bias.cpu().detach().numpy() + + def fit( + self, + x: np.ndarray, + max_iter: int = 100, + h_tol: float = 1e-8, + rho_max: float = 1e16, + ): + """ + Fit NOTEARS MLP model using the input data x + Args: + x: 2d numpy array input data, axis=0 is data rows, axis=1 is data columns. Data must be row oriented. + max_iter: max number of dual ascent steps during optimisation. + h_tol: exit if h(w) < h_tol (as opposed to strict definition of 0). + rho_max: to be updated + """ + rho, alpha, h = 1.0, 0.0, np.inf + X_torch = torch.from_numpy(x).float().to(self.device) + + for n_iter in range(max_iter): + rho, alpha, h = self._dual_ascent_step(X_torch, rho, alpha, h, rho_max) + if h <= h_tol or rho >= rho_max: + break + if n_iter == max_iter - 1 and h > h_tol: + self._logger.warning( + "Failed to converge. Consider increasing max_iter." + ) + + # calculate the adjacency matrix after the fitting is finished + self.adj = ( + self._calculate_adj(X_torch, mean_effect=False).cpu().detach().numpy() + ) + self.adj_mean_effect = ( + self._calculate_adj(X_torch, mean_effect=True).cpu().detach().numpy() + ) + + # pylint: disable=too-many-locals + def _dual_ascent_step( + self, X: torch.Tensor, rho: float, alpha: float, h: float, rho_max: float + ) -> Tuple[float, float, float]: + """ + Perform one step of dual ascent in augmented Lagrangian. + + Args: + X: input tensor data. + rho: max number of dual ascent steps during optimisation. + alpha: exit if h(w) < h_tol (as opposed to strict definition of 0). + h: DAGness of the adjacency matrix + rho_max: to be updated + + Returns: + rho, alpha and h + """ + + def _get_flat_grad(params: List[torch.Tensor]) -> np.ndarray: + """ + Get flatten gradient vector from the parameters of the model + + Args: + params: parameters of the model + + Returns: + flatten gradient vector in numpy form + """ + views = [ + p.data.new(p.data.numel()).zero_() + if p.grad is None + else p.grad.data.to_dense().view(-1) + if p.grad.data.is_sparse + else p.grad.data.view(-1) + for p in params + ] + return torch.cat(views, 0).cpu().detach().numpy() + + def _get_flat_bounds( + params: List[torch.Tensor], + ) -> List[Tuple[Union[None, float]]]: + """ + Get bound constraint for each parameter in flatten vector form from the parameters of the model + + Args: + params: parameters of the model + + Returns: + flatten vector of bound constraints for each parameter in numpy form + """ + bounds = [] + for p in params: + try: + b = p.bounds + except AttributeError: + b = [(None, None)] * p.numel() + bounds += b + return bounds + + def _get_flat_params(params: List[torch.Tensor]) -> np.ndarray: + """ + Get parameters in flatten vector from the parameters of the model + + Args: + params: parameters of the model + + Returns: + flatten parameters vector in numpy form + """ + views = [ + p.data.to_dense().view(-1) if p.data.is_sparse else p.data.view(-1) + for p in params + ] + return torch.cat(views, 0).cpu().detach().numpy() + + def _update_params_from_flat( + params: List[torch.Tensor], flat_params: np.ndarray + ): + """ + Update parameters of the model from the parameters in the form of flatten vector + + Args: + params: parameters of the model + flat_params: parameters in the form of flatten vector + """ + offset = 0 + flat_params_torch = torch.from_numpy(flat_params).to( + torch.get_default_dtype() + ) + for p in params: + n_params = p.numel() + # view_as to avoid deprecated pointwise semantics + p.data = flat_params_torch[offset : offset + n_params].view_as(p.data) + offset += n_params + + def _func(flat_params: np.ndarray) -> Tuple[float, np.ndarray]: + """ + Objective function that the NOTEARS algorithm tries to minimise. + + Args: + flat_params: parameters to be optimised to minimise the objective function + + Returns: + Loss and gradient + """ + _update_params_from_flat(params, flat_params) + optimizer.zero_grad() + + n_features = X.shape[1] + + X_hat = self(X) + h_val = self._h_func() + + loss = (0.5 / X.shape[0]) * torch.sum((X_hat - X) ** 2) + lagrange_penalty = 0.5 * rho * h_val * h_val + alpha * h_val + # NOTE: both the l2 and l1 regularization are NOT applied to the bias parameters + l2_reg = 0.5 * self.ridge_beta * self._l2_reg(n_features) + l1_reg = self.lasso_beta * self._l1_reg(n_features) + + primal_obj = loss + lagrange_penalty + l2_reg + l1_reg + primal_obj.backward() + loss = primal_obj.item() + + flat_grad = _get_flat_grad(params) + return loss, flat_grad.astype("float64") + + optimizer = torch.optim.Optimizer(self.parameters(), dict()) + params = optimizer.param_groups[0]["params"] + + flat_params = _get_flat_params(params) + bounds = _get_flat_bounds(params) + + while rho < rho_max: + # Magic + sol = sopt.minimize( + _func, + flat_params, + method="L-BFGS-B", + jac=True, + bounds=bounds, + ) + + _update_params_from_flat(params, sol.x) + h_new = self._h_func().item() + if h_new > 0.25 * h: + rho *= 10 + else: + break + alpha += rho * h_new + return rho, alpha, h_new + + def _h_func(self) -> torch.Tensor: + """ + Constraint function of the NOTEARS algorithm. + Constrain 2-norm-squared of dag_layer weights of the model along m1 dim to be a DAG + + Returns: + DAGness of the adjacency matrix + """ + d = self.dims[0] + d_torch = torch.tensor(d).to(self.device) # pylint: disable=not-callable + + # only consider the dag_layer for h(W) for compute efficiency + dag_layer_weight = self.dag_layer_weight.view(d, -1, d) # [j, m1, i] + square_weight_mat = torch.sum( + dag_layer_weight * dag_layer_weight, dim=1 + ).t() # [i, j] + + # h = trace_expm(a) - d # (Zheng et al. 2018) + characteristic_poly_mat = ( + torch.eye(d).to(self.device) + square_weight_mat / d_torch + ) # (Yu et al. 2019) + polynomial_mat = torch.matrix_power(characteristic_poly_mat, d - 1) + h = (polynomial_mat.t() * characteristic_poly_mat).sum() - d + return h + + def _l1_reg(self, n_features: int) -> torch.Tensor: + """ + Take average l1 of all weight parameters of the model. + NOTE: regularisation needs to be scaled up by the number of features + because the loss scales with feature number. + + Returns: + l1 regularisation term. + """ + return torch.mean(torch.abs(self.dag_layer_weight)) * n_features + + def _l2_reg(self, n_features: int) -> torch.Tensor: + """ + Take average 2-norm-squared of all weight parameters of the model. + NOTE: regularisation needs to be scaled up by the number of features + because the loss scales with feature number. + + Returns: + l2 regularisation term. + """ + reg = 0.0 + reg += torch.sum(self.dag_layer_weight ** 2) + for layer in self.loc_lin_layer_weights: + reg += torch.sum(layer.weight ** 2) + + # calculate the total number of elements used in the above sums + n_elements = self.dag_layer_weight.numel() + for layer in self.loc_lin_layer_weights: + n_elements = n_elements + layer.weight.numel() + return reg / n_elements * n_features + + def _calculate_adj(self, X: torch.Tensor, mean_effect: bool) -> torch.Tensor: + """ + Calculate the adjacency matrix. + + For the linear case, this is just dag_layer_weight. + For the nonlinear case, approximate the relationship using the gradient of X_hat wrt X. + """ + + # for the linear case, save compute by just returning the dag_layer weights + if len(self.dims) <= 2: + adj = ( + self.dag_layer_weight.T + if mean_effect + else torch.abs(self.dag_layer_weight.T) + ) + return adj + + _, n_features = X.shape + # get the data X and reconstruction X_hat + X = X.clone().requires_grad_() + X_hat = self(X).sum(dim=0) # shape = (n_features,) + + adj = [] + # iterate over sums of reconstructed features + for j in range(n_features): + + # calculate the gradient of X_hat wrt X + ddx = torch.autograd.grad(X_hat[j], X, create_graph=True)[0] + + if mean_effect: + # get the average effect + adj.append(ddx.mean(axis=0).unsqueeze(0)) + else: + # otherwise, use the average L1 of the gradient as the W + adj.append(torch.abs(ddx).mean(dim=0).unsqueeze(0)) + adj = torch.cat(adj, dim=0) + + # transpose to get the adjacency matrix + return adj.T diff --git a/causalnex/structure/pytorch/nonlinear.py b/causalnex/structure/pytorch/nonlinear.py new file mode 100644 index 0000000..e3da828 --- /dev/null +++ b/causalnex/structure/pytorch/nonlinear.py @@ -0,0 +1,111 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This code is modified from this git repo: https://github.com/xunzheng/notears +@inproceedings{zheng2020learning, + author = {Zheng, Xun and Dan, Chen and Aragam, Bryon and Ravikumar, Pradeep and Xing, Eric P.}, + booktitle = {International Conference on Artificial Intelligence and Statistics}, + title = {{Learning sparse nonparametric DAGs}}, + year = {2020} +} +""" +import math + +import torch +import torch.nn as nn + + +class LocallyConnected(nn.Module): + """ + Local linear layer, i.e. Conv1dLocal() with filter size 1. + """ + + def __init__( + self, + num_linear: int, + input_features: int, + output_features: int, + bias: bool = True, + ): + """ + Create local linear layers. + Transformations of the feature are independent of each other, + each feature is expanded to several hidden units. + + Args: + num_linear: num of local linear layers. + input_features: m1. + output_features: m2. + bias: whether to include bias or not. + """ + super().__init__() + self.num_linear = num_linear + self.input_features = input_features + self.output_features = output_features + + self.weight = nn.Parameter( + torch.Tensor(num_linear, input_features, output_features) + ) + if bias: + self.bias = nn.Parameter(torch.Tensor(num_linear, output_features)) + else: + # You should always register all possible parameters, but the + # optional ones can be None if you want. + self.register_parameter("bias", None) + + self.reset_parameters() + + @torch.no_grad() + def reset_parameters(self): + """ + Reset parameters + """ + k = 1.0 / self.input_features + bound = math.sqrt(k) + nn.init.uniform_(self.weight, -bound, bound) + if self.bias is not None: + nn.init.uniform_(self.bias, -bound, bound) + + # pylint: disable=arguments-differ + def forward(self, x: torch.Tensor) -> torch.Tensor: + """ + Forward output calculation # [n, d, 1, m2] = [n, d, 1, m1] @ [1, d, m1, m2] + + Args: + x: torch tensor + + Returns: + output calculation + """ + out = torch.matmul(x.unsqueeze(dim=2), self.weight.unsqueeze(dim=0)) + out = out.squeeze(dim=2) + if self.bias is not None: + # [n, d, m2] += [d, m2] + out += self.bias + return out diff --git a/causalnex/structure/pytorch/notears.py b/causalnex/structure/pytorch/notears.py new file mode 100644 index 0000000..dd88ca6 --- /dev/null +++ b/causalnex/structure/pytorch/notears.py @@ -0,0 +1,306 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Tools to learn a ``StructureModel`` which describes the conditional dependencies between variables in a dataset. +""" + +import logging +from copy import deepcopy +from typing import Iterable, List, Tuple + +import numpy as np +import pandas as pd +from sklearn.utils import check_array + +from causalnex.structure.pytorch.core import NotearsMLP +from causalnex.structure.structuremodel import StructureModel + +__all__ = ["from_numpy", "from_pandas"] + + +# pylint: disable=too-many-locals +# pylint: disable=too-many-arguments +def from_numpy( + X: np.ndarray, + lasso_beta: float = 0.0, + ridge_beta: float = 0.0, + use_bias: bool = False, + hidden_layer_units: Iterable[int] = None, + w_threshold: float = None, + max_iter: int = 100, + tabu_edges: List[Tuple[int, int]] = None, + tabu_parent_nodes: List[int] = None, + tabu_child_nodes: List[int] = None, + **kwargs +) -> StructureModel: + """ + Learn the `StructureModel`, the graph structure with lasso regularisation + describing conditional dependencies between variables in data presented as a numpy array. + + Based on DAGs with NO TEARS. + @inproceedings{zheng2018dags, + author = {Zheng, Xun and Aragam, Bryon and Ravikumar, Pradeep and Xing, Eric P.}, + booktitle = {Advances in Neural Information Processing Systems}, + title = {{DAGs with NO TEARS: Continuous Optimization for Structure Learning}}, + year = {2018}, + codebase = {https://github.com/xunzheng/notears} + } + + Args: + X: 2d input data, axis=0 is data rows, axis=1 is data columns. Data must be row oriented. + + lasso_beta: Constant that multiplies the lasso term (l1 regularisation). + NOTE when using nonlinearities, the l1 loss only applies to the dag_layer. + + use_bias: Whether to fit a bias parameter in the NOTEARS algorithm. + + ridge_beta: Constant that multiplies the ridge term (l2 regularisation). + When using nonlinear layers use of this parameter is recommended. + + hidden_layer_units: An iterable where its length determine the number of layers used, + and the numbers determine the number of nodes used for the layer in order. + + w_threshold: fixed threshold for absolute edge weights. + + max_iter: max number of dual ascent steps during optimisation. + + tabu_edges: list of edges(from, to) not to be included in the graph. + + tabu_parent_nodes: list of nodes banned from being a parent of any other nodes. + + tabu_child_nodes: list of nodes banned from being a child of any other nodes. + + **kwargs: additional arguments for NOTEARS MLP model + + Returns: + StructureModel: a graph of conditional dependencies between data variables. + + Raises: + ValueError: If X does not contain data. + """ + # n examples, d properties + if not X.size: + raise ValueError("Input data X is empty, cannot learn any structure") + logging.info("Learning structure using 'NOTEARS' optimisation.") + # Check array for NaN or inf values + check_array(X) + + _, d = X.shape + + # if None or empty, convert into a list with single item + if hidden_layer_units is None: + hidden_layer_units = [0] + elif isinstance(hidden_layer_units, list) and not hidden_layer_units: + hidden_layer_units = [0] + + # if no hidden layer units, still take 1 iteration step with bounds + hidden_layer_bnds = hidden_layer_units[0] if hidden_layer_units[0] else 1 + + # Flip i and j because Pytorch flattens the vector in another direction + bnds = [ + (0, 0) + if i == j + else (0, 0) + if tabu_edges is not None and (i, j) in tabu_edges + else (0, 0) + if tabu_parent_nodes is not None and i in tabu_parent_nodes + else (0, 0) + if tabu_child_nodes is not None and j in tabu_child_nodes + else (None, None) + for j in range(d) + for _ in range(hidden_layer_bnds) + for i in range(d) + ] + + model = NotearsMLP( + n_features=d, + hidden_layer_units=hidden_layer_units, + lasso_beta=lasso_beta, + ridge_beta=ridge_beta, + bounds=bnds, + use_bias=use_bias, + **kwargs + ) + + model.fit(X, max_iter=max_iter) + sm = StructureModel(model.adj) + if w_threshold: + sm.remove_edges_below_threshold(w_threshold) + + mean_effect = model.adj_mean_effect + # extract the mean effect and add as edge attribute + for u, v, edge_dict in sm.edges.data(True): + sm.add_edge( + u, + v, + origin="learned", + weight=edge_dict["weight"], + mean_effect=mean_effect[u, v], + ) + + # set bias as node attribute + bias = model.bias + for node in sm.nodes(): + value = None + if bias is not None: + value = bias[node] + sm.nodes[node]["bias"] = value + + # preserve the structure_learner as a graph attribute + sm.graph["structure_learner"] = model + + return sm + + +# pylint: disable=too-many-locals +# pylint: disable=too-many-arguments +def from_pandas( + X: pd.DataFrame, + lasso_beta: float = 0.0, + ridge_beta: float = 0.0, + hidden_layer_units: Iterable[int] = None, + max_iter: int = 100, + w_threshold: float = None, + tabu_edges: List[Tuple[str, str]] = None, + tabu_parent_nodes: List[str] = None, + tabu_child_nodes: List[str] = None, + use_bias: bool = False, + **kwargs +) -> StructureModel: + """ + Learn the `StructureModel`, the graph structure describing conditional dependencies between variables + in data presented as a pandas dataframe. + + The optimisation is to minimise a score function :math:`F(W)` over the graph's + weighted adjacency matrix, :math:`W`, subject to the a constraint function :math:`h(W)`, + where :math:`h(W) == 0` characterises an acyclic graph. + :math:`h(W) > 0` is a continuous, differentiable function that encapsulated how acyclic the graph is + (less == more acyclic). + Full details of this approach to structure learning are provided in the publication: + + Based on DAGs with NO TEARS. + @inproceedings{zheng2018dags, + author = {Zheng, Xun and Aragam, Bryon and Ravikumar, Pradeep and Xing, Eric P.}, + booktitle = {Advances in Neural Information Processing Systems}, + title = {{DAGs with NO TEARS: Continuous Optimization for Structure Learning}}, + year = {2018}, + codebase = {https://github.com/xunzheng/notears} + } + + Args: + X: 2d input data, axis=0 is data rows, axis=1 is data columns. Data must be row oriented. + + lasso_beta: Constant that multiplies the lasso term (l1 regularisation). + NOTE when using nonlinearities, the l1 loss only applies to the dag_layer. + + use_bias: Whether to fit a bias parameter in the NOTEARS algorithm. + + ridge_beta: Constant that multiplies the ridge term (l2 regularisation). + When using nonlinear layers use of this parameter is recommended. + + hidden_layer_units: An iterable where its length determine the number of layers used, + and the numbers determine the number of nodes used for the layer in order. + + w_threshold: fixed threshold for absolute edge weights. + + max_iter: max number of dual ascent steps during optimisation. + + tabu_edges: list of edges(from, to) not to be included in the graph. + + tabu_parent_nodes: list of nodes banned from being a parent of any other nodes. + + tabu_child_nodes: list of nodes banned from being a child of any other nodes. + + **kwargs: additional arguments for NOTEARS MLP model + + Returns: + StructureModel: graph of conditional dependencies between data variables. + + Raises: + ValueError: If X does not contain data. + """ + + data = deepcopy(X) + + non_numeric_cols = data.select_dtypes(exclude="number").columns + + if len(non_numeric_cols) > 0: + raise ValueError( + "All columns must have numeric data. " + "Consider mapping the following columns to int {non_numeric_cols}".format( + non_numeric_cols=non_numeric_cols + ) + ) + + col_idx = {c: i for i, c in enumerate(data.columns)} + idx_col = {i: c for c, i in col_idx.items()} + + if tabu_edges: + tabu_edges = [(col_idx[u], col_idx[v]) for u, v in tabu_edges] + if tabu_parent_nodes: + tabu_parent_nodes = [col_idx[n] for n in tabu_parent_nodes] + if tabu_child_nodes: + tabu_child_nodes = [col_idx[n] for n in tabu_child_nodes] + + g = from_numpy( + X=data.values, + lasso_beta=lasso_beta, + ridge_beta=ridge_beta, + use_bias=use_bias, + hidden_layer_units=hidden_layer_units, + w_threshold=w_threshold, + max_iter=max_iter, + tabu_edges=tabu_edges, + tabu_parent_nodes=tabu_parent_nodes, + tabu_child_nodes=tabu_child_nodes, + **kwargs + ) + + sm = StructureModel() + sm.add_nodes_from(data.columns) + + # recover the edge weights from g + for u, v, edge_dict in g.edges.data(True): + sm.add_edge( + idx_col[u], + idx_col[v], + origin="learned", + weight=edge_dict["weight"], + mean_effect=edge_dict["mean_effect"], + ) + + # retrieve dtype information from graph attribute + for key, val in g.graph.items(): + sm.graph[key] = val + + # recover the node biases from g + for node in g.nodes(data=True): + node_name = idx_col[node[0]] + sm.nodes[node_name]["bias"] = node[1]["bias"] + + return sm diff --git a/causalnex/structure/sklearn.py b/causalnex/structure/sklearn.py new file mode 100644 index 0000000..cabe896 --- /dev/null +++ b/causalnex/structure/sklearn.py @@ -0,0 +1,347 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This module contains the implementation of ``DAGRegressor``. + +``DAGRegressor`` is a class which wraps the StructureModel in an sklearn interface for regression. +""" + +import copy +import warnings +from typing import Iterable, List, Union + +import numpy as np +import pandas as pd +import torch +from sklearn.base import BaseEstimator, RegressorMixin +from sklearn.preprocessing import StandardScaler +from sklearn.utils.validation import check_is_fitted, check_X_y + +from causalnex.plots import EDGE_STYLE, NODE_STYLE, plot_structure +from causalnex.structure.pytorch import notears + + +class DAGRegressor( + BaseEstimator, RegressorMixin +): # pylint: disable=too-many-instance-attributes + """ + Regressor wrapper of the StructureModel. + Implements the sklearn .fit and .predict interface. + Currently only supports linear NOTEARS fitting by the DAG. + + Example: + :: + >>> from causalnex.sklearn import DAGRegressor + >>> + >>> smr = DAGRegressor(threshold=0.1) + >>> smr.fit(X_train, y_train) + >>> + >>> y_preds = smr.predict(X_test) + >>> type(y_preds) + np.ndarray + >>> + >>> type(smr.feature_importances_) + np.ndarray + :: + + Attributes: + feature_importances_ (np.ndarray): An array of edge weights corresponding + positionally to the feature X. + """ + + # pylint: disable=too-many-arguments + def __init__( + self, + alpha: float = 0.0, + beta: float = 0.0, + fit_intercept: bool = True, + hidden_layer_units: Iterable[int] = None, + threshold: float = 0.0, + tabu_edges: List = None, + tabu_parent_nodes: List = None, + tabu_child_nodes: List = None, + dependent_target: bool = True, + enforce_dag: bool = False, + standardize: bool = False, + **kwargs + ): + """ + Args: + alpha: l1 loss weighting. When using nonlinear layers this is only applied + to the first layer. + + beta: l2 loss weighting. Applied across all layers. Reccomended to use this + when fitting nonlinearities. + + fit_intercept: Whether to fit an intercept in the structure model + equation. Use this if variables are offset. + + hidden_layer_units: An iterable where its length determine the number of layers used, + and the numbers determine the number of nodes used for the layer in order. + + threshold: The thresholding to apply to the DAG weights. + If 0.0, does not apply any threshold. + + tabu_edges: Tabu edges passed directly to the NOTEARS algorithm. + + tabu_parent_nodes: Tabu nodes passed directly to the NOTEARS algorithm. + + tabu_child_nodes: Tabu nodes passed directly to the NOTEARS algorithm. + + dependent_target: If True, constrains NOTEARS so that y can only + be dependent (i.e. cannot have children) and imputes from parent nodes. + + enforce_dag: If True, thresholds the graph until it is a DAG. + NOTE a properly trained model should be a DAG, and failure + indicates other issues. Use of this is only recommended if + features have similar units, otherwise comparing edge weight + magnitude has limited meaning. + + standardize: Whether to standardize the X and y variables before fitting. + The L-BFGS algorithm used to fit the underlying NOTEARS works best on data + all of the same scale so this parameter is reccomended. + + kwargs: Extra arguments passed to the NOTEARS from_pandas function. + + Raises: + TypeError: if alpha is not numeric. + TypeError: if beta is not numeric. + TypeError: if fit_intercept is not a bool. + TypeError: if threshold is not numeric. + """ + + # core causalnex parameters + self.alpha = alpha + self.beta = beta + self.fit_intercept = fit_intercept + self.hidden_layer_units = hidden_layer_units + self.threshold = threshold + self.tabu_edges = tabu_edges + self.tabu_parent_nodes = tabu_parent_nodes + self.tabu_child_nodes = tabu_child_nodes + self.kwargs = kwargs + + if not isinstance(alpha, (int, float)): + raise TypeError("alpha should be numeric") + if not isinstance(beta, (int, float)): + raise TypeError("beta should be numeric") + if not isinstance(fit_intercept, bool): + raise TypeError("fit_intercept should be a bool") + if not isinstance(threshold, (int, float)): + raise TypeError("threshold should be numeric") + + # sklearn wrapper paramters + self.dependent_target = dependent_target + self.enforce_dag = enforce_dag + self.standardize = standardize + + def fit( + self, X: Union[pd.DataFrame, np.ndarray], y: Union[pd.Series, np.ndarray] + ) -> "DAGRegressor": + """ + Fits the sm model using the concat of X and y. + """ + + # defensive X, y checks + check_X_y(X, y, y_numeric=True) + + # force as DataFrame and Series (for later calculations) + X = pd.DataFrame(X) + y = pd.Series(y) + # force name so that name != None (causes errors in notears) + y.name = y.name or "__target" + + if self.standardize: + self.ss_X = StandardScaler() + self.ss_y = StandardScaler() + X = pd.DataFrame(self.ss_X.fit_transform(X), columns=X.columns) + y = pd.Series( + self.ss_y.fit_transform(y.values.reshape(-1, 1)).reshape(-1), + name=y.name, + ) + + # preserve the feature and target colnames + self._features = tuple(X.columns) + self._target = y.name + + # concat X and y along column axis + X = pd.concat([X, y], axis=1) + + # make copy to prevent mutability + tabu_parent_nodes = copy.deepcopy(self.tabu_parent_nodes) + if self.dependent_target: + if tabu_parent_nodes is None: + tabu_parent_nodes = [self._target] + elif self._target not in tabu_parent_nodes: + tabu_parent_nodes.append(self._target) + + # fit the structured model + self.graph_ = notears.from_pandas( + X, + lasso_beta=self.alpha, + ridge_beta=self.beta, + hidden_layer_units=self.hidden_layer_units, + w_threshold=self.threshold, + tabu_edges=self.tabu_edges, + tabu_parent_nodes=tabu_parent_nodes, + tabu_child_nodes=self.tabu_child_nodes, + use_bias=self.fit_intercept, + **self.kwargs + ) + + # keep thresholding until the DAG constraint is enforced + if self.enforce_dag: + self.graph_.threshold_till_dag() + + return self + + def _predict_from_parents(self, X: Union[pd.DataFrame, np.ndarray]): + + # extract the base solver + structure_learner = self.graph_.graph["structure_learner"] + + # convert the predict data to pytorch tensor + X = torch.from_numpy(X).float().to(structure_learner.device) + # need to concat y onto X so that the dimensions are the same + y = torch.zeros(X.shape[0], 1).float().to(structure_learner.device) + X = torch.cat([X, y], dim=1) + + # perform forward reconstruction + X_hat = structure_learner(X) + + # FUTURE NOTE: with dtypes the projection from latent -> dtype goes here + + # extract the desired y column, return as array + y_pred = X_hat[:, -1] + return y_pred.cpu().detach().numpy() + + def predict(self, X: Union[pd.DataFrame, np.ndarray]) -> np.ndarray: + """ + Get the predictions of the structured model. + This is done by multiplying the edge weights with the feature i.e. X @ W + """ + # force convert to ndarray + X = np.asarray(X) + if self.standardize: + X = self.ss_X.transform(X) + + # check that the model has been fit + check_is_fitted(self, "graph_") + + y_pred = np.asarray(self._predict_from_parents(X)) + if self.standardize: + y_pred = self.ss_y.inverse_transform(y_pred.reshape(-1, 1)).reshape(-1) + return y_pred + + def get_edges_to_node(self, name: str, data: str = "weight") -> pd.Series: + """ + Get the edges to a specific node. + Args: + name: The name of the node which to get weights towards. + + data: The edge parameter to get. Default is "weight" to return + the adjacency matrix. Set to "mean_effect" to return the + signed average effect of features on the target node. + + Returns: + The specified edge data. + """ + check_is_fitted(self, "graph_") + + # build base data series + edges = pd.Series(index=self._features) + + # iterate over all edges + for (i, j, w) in self.graph_.edges(data=data): + # for edges directed towards the "name" node + if j == name: + # insert the weight into the series + edges[i] = w + + # fill edges not present in the iteration with zeros + edges = edges.fillna(0) + + return edges + + @property + def feature_importances_(self) -> np.ndarray: + """ + Unsigned importances of the features wrt to the target. + NOTE: these are used as the graph adjacency matrix. + Returns: + the L2 relationship between nodes. + """ + return self.get_edges_to_node(self._target).values + + @property + def coef_(self) -> np.ndarray: + """ + Signed relationship between features and the target. + For this linear case this equivalent to linear regression coefficients. + Returns: + the mean effect relationship between nodes. + """ + return self.get_edges_to_node(self._target, data="mean_effect").values + + @property + def intercept_(self) -> float: + """ The bias term from the target node """ + bias = self.graph_.nodes[self._target]["bias"] + return 0.0 if bias is None else float(bias) + + def plot_dag(self, enforce_dag: bool = False, filename: str = "./graph.png"): + """ Util function used to plot the fitted graph """ + + try: + # pylint: disable=import-outside-toplevel + from IPython.display import Image + except ImportError as e: + raise ImportError( + "DAGRegressor.plot_dag method requires IPython installed." + ) from e + + check_is_fitted(self, "graph_") + + graph = copy.deepcopy(self.graph_) + if enforce_dag: + graph.threshold_till_dag() + + # silence annoying plotting warning + warnings.filterwarnings("ignore") + + viz = plot_structure( + graph, + graph_attributes={"scale": "0.5"}, + all_node_attributes=NODE_STYLE.WEAK, + all_edge_attributes=EDGE_STYLE.WEAK, + ) + viz.draw(filename) + + # reset warnings to always show + warnings.simplefilter("always") + return Image(filename) diff --git a/causalnex/structure/structuremodel.py b/causalnex/structure/structuremodel.py index 259bf99..4d84fe9 100644 --- a/causalnex/structure/structuremodel.py +++ b/causalnex/structure/structuremodel.py @@ -292,3 +292,12 @@ def get_target_subgraph(self, node: Hashable) -> "StructureModel": return subgraph raise NodeNotFound("Node {node} not found in the graph.".format(node=node)) + + def threshold_till_dag(self): + """ + Remove edges with smallest weight until the graph is a DAG. + Not recommended if the weights have different units. + """ + while not nx.algorithms.is_directed_acyclic_graph(self): + i, j, _ = min(self.edges(data="weight"), key=lambda x: abs(x[2])) + self.remove_edge(i, j) diff --git a/causalnex/structure/transformers.py b/causalnex/structure/transformers.py new file mode 100644 index 0000000..09d5aa8 --- /dev/null +++ b/causalnex/structure/transformers.py @@ -0,0 +1,290 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Collection of sklearn style transformers designed to assist with causal structure learning. +""" + +from copy import deepcopy +from typing import List, Tuple, Union + +import numpy as np +import pandas as pd +from sklearn.base import BaseEstimator, TransformerMixin +from sklearn.exceptions import NotFittedError + + +class DynamicDataTransformer(BaseEstimator, TransformerMixin): + """ + Format a time series dataframe or list of dataframes into the a format that matches the structure learned by + `from_pandas_dynamic`. This is done to allow for bayesian network probability fitting. + + Example of utilisation: + >>> ddt = DynamicDataTransformer(p=p).fit(time_series, return_df=False) + >>> X, Xlags = ddt.transform(time_series) + + >>> ddt = DynamicDataTransformer(p=p).fit(time_series, return_df=True) + >>> df = ddt.transform(time_series) + """ + + def __init__(self, p: int): + """ + Initialise Transformer + Args: + p: Number of past interactions we allow the model to create. The state of a variable at time `t` is + affected by the variables at the time stamp + the variables at `t-1`, `t-2`,... `t-p`. + """ + self.p = p + self.columns = None + self.return_df = None + + def fit( + self, + time_series: Union[pd.DataFrame, List[pd.DataFrame]], + return_df: bool = True, + ) -> "DynamicDataTransformer": + """ + Fits the time series. This consists memorizing: + - Column names and column positions + - whether a dataframe or a tuple of arrays should be returned by `transform` (details below) + Args: + time_series: pd.DataFrame or List of pd.DataFrame instances. + If a list is provided each element of the list being an realisation of a time series + (i.e. time series governed by the same processes) + The columns of the data frame represent the variables in the model, and the *index represents + the time index*. + Successive events, therefore, must be indexed with one integer of difference between them too. + + return_df: Whether the `transform` method should return a pandas.DataFrame or a tuple with (X,Xlags) + (Details on the documentation of the `transform` method) + + Returns: + self + + """ + time_series = time_series if isinstance(time_series, list) else [time_series] + self._check_input_from_pandas(time_series) + self.columns = list(time_series[0].columns) + self.return_df = return_df + return self + + def transform( + self, time_series: Union[pd.DataFrame, List[pd.DataFrame]] + ) -> Union[pd.DataFrame, Tuple[np.ndarray, np.ndarray]]: + """ + Applies transformation to format the dataframe properly + Args: + time_series: time_series: pd.DataFrame or List of pd.DataFrame instances. Details on `fit` documentation + + Returns: + - If `self.return_df=True`, returns a pandas.DataFrame on the following format: + + A_lag0 B_lag0 C_lag0 ... A_lag1 B_lag1 C_lag1 ... A_lag`p` B_lag`p` C_lag`p` + X X X X X X X X X + X X X X X X X X X + X X X X X X X X X + `lag0` denotes the current variable state and lag`k` denotes the states `k` time stamps in the past. + + - If `self.return_df=False`, returns a tuple of two numpy.ndarrayy: X and Xlags + X (np.ndarray): 2d input data, axis=1 is data columns, axis=0 is data rows. + Each column represents one variable, + and each row represents x(m,t) i.e. the mth time series at time t. + Xlags (np.ndarray): + Shifted data of X with lag orders stacking horizontally. Xlags=[shift(X,1)|...|shift(X,p)] + Raises: + NotFittedError: if `transform` called before `fit` + """ + if self.columns is None: + raise NotFittedError( + "This DynamicDataTransformer is not fitted yet. " + "Call `fit` before using this method" + ) + + time_series = time_series if isinstance(time_series, list) else [time_series] + + self._check_input_from_pandas(time_series) + + time_series = [t[self.columns] for t in time_series] + ts_realisations = self._cut_dataframes_on_discontinuity_points(time_series) + X, Xlags = self._convert_realisations_into_dynotears_format( + ts_realisations, self.p + ) + + if self.return_df: + res = self._concat_lags(X, Xlags) + return res + return X, Xlags + + def _concat_lags(self, X: np.ndarray, Xlags: np.ndarray) -> pd.DataFrame: + df_x = pd.DataFrame( + X, columns=["{col}_lag0".format(col=col) for col in self.columns] + ) + df_xlags = pd.DataFrame( + Xlags, + columns=[ + "{col}_lag{l_}".format(col=col, l_=l_) + for l_ in range(1, self.p + 1) + for col in self.columns + ], + ) + return pd.concat([df_x, df_xlags], axis=1) + + def _check_input_from_pandas(self, time_series: List[pd.DataFrame]): + """ + Check if the input of function `from_pandas_dynamic` is valid + Args: + time_series: List of pd.DataFrame instances. + each element of the list being an realisation of a same time series + + Raises: + ValueError: if empty list of time_series is provided + ValueError: if dataframes contain non numeric data + TypeError: if elements provided are not pandas dataframes + ValueError: if dataframes contain different columns + ValueError: if dataframes index is not in increasing order + TypeError: if dataframes index are not index + """ + if not time_series: + raise ValueError( + "Provided empty list of time_series. At least one DataFrame must be provided" + ) + + df = deepcopy(time_series[0]) + + for t in time_series: + if not isinstance(t, pd.DataFrame): + raise TypeError( + "Time series entries must be instances of `pd.DataFrame`" + ) + + non_numeric_cols = t.select_dtypes(exclude="number").columns + + if not non_numeric_cols.empty: + raise ValueError( + "All columns must have numeric data. Consider mapping the " + "following columns to int: {non_numeric_cols}".format( + non_numeric_cols=list(non_numeric_cols) + ) + ) + + if (not np.all(df.columns == t.columns)) or ( + not np.all(df.dtypes == t.dtypes) + ): + raise ValueError("All inputs must have the same columns and same types") + + if not np.all(t.index == t.index.sort_values()): + raise ValueError( + "Index for dataframe must be provided in increasing order" + ) + + if t.index.dtype != int: + raise TypeError("Index must be integers") + + if self.columns is not None: + missing_cols = [c for c in self.columns if c not in t.columns] + if missing_cols: + raise ValueError( + "We should provide all necessary columns in the time series." + " Columns not provided: {col}".format(col=missing_cols) + ) + + @staticmethod + def _cut_dataframes_on_discontinuity_points( + time_series: List[pd.DataFrame], + ) -> List[np.ndarray]: + """ + Helper function for `from_pandas_dynamic` + Receive a list of dataframes. For each dataframe, cut the points of discontinuity as two different dataframes. + Discontinuities are determined by the indexes. + + For Example: + If the following is a dataframe: + index variable_1 variable_2 + 1 X X + 2 X X + 3 X X + 4 X X + 8 X X <- discontinuity point + 9 X X + 10 X X + + We cut this dataset in two: + + index variable_1 variable_2 + 1 X X + 2 X X + 3 X X + 4 X X + + and: + index variable_1 variable_2 + 8 X X + 9 X X + 10 X X + + + Args: + time_series: list of dataframes representing various realisations of a same time series + + Returns: + List of np.ndarrays representing the pieces of the input datasets with no discontinuity + + """ + time_series_realisations = [] + for t in time_series: + cutting_points = np.where(np.diff(t.index) > 1)[0] + cutting_points = [0] + list(cutting_points + 1) + [len(t)] + for start, end in zip(cutting_points[:-1], cutting_points[1:]): + time_series_realisations.append(t.iloc[start:end, :].values) + return time_series_realisations + + @staticmethod + def _convert_realisations_into_dynotears_format( + realisations: List[np.ndarray], p: int + ) -> Tuple[np.ndarray, np.ndarray]: + """ + Given a list of realisations of a time series, convert it to the format received by the dynotears algorithm. + Each realisation on `realisations` is a realisation of the time series, + where the time dimension is represented by the rows. + - The higher the row, the higher the time index + - The data is complete, meaning that the difference between two time stamps is equal one + Args: + realisations: a list of realisations of a time series + p: the number of lagged columns to create + + Returns: + X and Y as in the SVAR model and DYNOTEARS paper. I.e. X being representing X(m,t) and Y the concatenated + differences [X(m,t-1) | X(m,t-2) | ... | X(m,t-p)] + """ + X = np.concatenate([realisation[p:] for realisation in realisations], axis=0) + y_lag_list = [ + np.concatenate([realisation[p - i - 1 : -i - 1] for i in range(p)], axis=1) + for realisation in realisations + ] + y_lag = np.concatenate(y_lag_list, axis=0) + + return X, y_lag diff --git a/doc_requirements.txt b/doc_requirements.txt index f60e9a6..744f4c3 100644 --- a/doc_requirements.txt +++ b/doc_requirements.txt @@ -5,7 +5,7 @@ nbsphinx==0.4.2 nbstripout==0.3.3 patchy>=1.5, <2.0 recommonmark==0.5.0 -sphinx-autodoc-typehints>=1.6.0, < 2.0 +sphinx-autodoc-typehints>=1.6.0, < 1.11.0 sphinx-markdown-tables==0.0.9 sphinx>=1.8.4, <2.0 sphinx_copybutton==0.2.5 diff --git a/docs/conf.py b/docs/conf.py index 506e091..750a508 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -390,7 +390,9 @@ def _prepare_build_dir(app, config): shutil.rmtree(str(build_root / "api_docs")) shutil.rmtree(str(build_out), ignore_errors=True) copy_tree(str(build_root / "css"), str(build_out / "_static" / "css")) - copy_tree(str(build_root / "04_user_guide/images"), str(build_out / "04_user_guide")) + copy_tree( + str(build_root / "04_user_guide/images"), str(build_out / "04_user_guide") + ) shutil.rmtree(str(build_root / "css")) @@ -407,7 +409,7 @@ def setup(app): app.add_stylesheet("css/causalnex.css") # when using nbsphinx, to allow mathjax render properly - app.config._raw_config.pop('mathjax_config') + app.config._raw_config.pop("mathjax_config") def fix_module_paths(): diff --git a/docs/source/03_tutorial/regressor_tutorial.ipynb b/docs/source/03_tutorial/regressor_tutorial.ipynb new file mode 100644 index 0000000..648653c --- /dev/null +++ b/docs/source/03_tutorial/regressor_tutorial.ipynb @@ -0,0 +1,644 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import os\n", + "import sys\n", + "module_path = os.path.abspath(os.path.join(\"../../..\"))\n", + "if module_path not in sys.path:\n", + " sys.path.append(module_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Contents\n", + "\n", + "This notebook walks through using the DAGRegressor model.\n", + "\n", + "The material covered here is as follows:\n", + "- Linear Interface\n", + "- Nonlinear Interface" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "___\n", + "## Real Data (boston housing)\n", + "\n", + "This section demonstrates the performance of the algorithm on a real-world dataset. The main things to note in this section are:\n", + "- The scale sensitivity of the algorithm\n", + "- Interpretability of nonlinear `.coef_`\n", + "\n", + "The boston housing dataset is a classic benchmark regression task. The objective is to predict a set of house prices given a small set of features.\n", + "\n", + "The meaning of the set of avaliable features is shown below." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ".. _boston_dataset:\n", + "\n", + "Boston house prices dataset\n", + "---------------------------\n", + "\n", + "**Data Set Characteristics:** \n", + "\n", + " :Number of Instances: 506 \n", + "\n", + " :Number of Attributes: 13 numeric/categorical predictive. Median Value (attribute 14) is usually the target.\n", + "\n", + " :Attribute Information (in order):\n", + " - CRIM per capita crime rate by town\n", + " - ZN proportion of residential land zoned for lots over 25,000 sq.ft.\n", + " - INDUS proportion of non-retail business acres per town\n", + " - CHAS Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)\n", + " - NOX nitric oxides concentration (parts per 10 million)\n", + " - RM average number of rooms per dwelling\n", + " - AGE proportion of owner-occupied units built prior to 1940\n", + " - DIS weighted distances to five Boston employment centres\n", + " - RAD index of accessibility to radial highways\n", + " - TAX full-value property-tax rate per $10,000\n", + " - PTRATIO pupil-teacher ratio by town\n", + " - B 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town\n", + " - LSTAT % lower status of the population\n", + " - MEDV Median value of owner-occupied homes in $1000's\n", + "\n", + " :Missing Attribute Values: None\n", + "\n", + " :Creator: Harrison, D. and Rubinfeld, D.L.\n", + "\n", + "This is a copy of UCI ML housing dataset.\n", + "https://archive.ics.uci.edu/ml/machine-learning-databases/housing/\n", + "\n", + "\n", + "This dataset was taken from the StatLib library which is maintained at Carnegie Mellon University.\n", + "\n", + "The Boston house-price data of Harrison, D. and Rubinfeld, D.L. 'Hedonic\n", + "prices and the demand for clean air', J. Environ. Economics & Management,\n", + "vol.5, 81-102, 1978. Used in Belsley, Kuh & Welsch, 'Regression diagnostics\n", + "...', Wiley, 1980. N.B. Various transformations are used in the table on\n", + "pages 244-261 of the latter.\n", + "\n", + "The Boston house-price data has been used in many machine learning papers that address regression\n", + "problems. \n", + " \n", + ".. topic:: References\n", + "\n", + " - Belsley, Kuh & Welsch, 'Regression diagnostics: Identifying Influential Data and Sources of Collinearity', Wiley, 1980. 244-261.\n", + " - Quinlan,R. (1993). Combining Instance-Based and Model-Based Learning. In Proceedings on the Tenth International Conference of Machine Learning, 236-243, University of Massachusetts, Amherst. Morgan Kaufmann.\n", + "\n" + ] + } + ], + "source": [ + "from sklearn.datasets import load_boston\n", + "print(load_boston(return_X_y=False)[\"DESCR\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Lets initially benchmark the performance of an `ElasticNetCV` fitted across the entire dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MEAN R2: 0.700\n" + ] + } + ], + "source": [ + "from sklearn.linear_model import ElasticNetCV\n", + "from sklearn.model_selection import cross_val_score\n", + "\n", + "from sklearn.datasets import load_boston\n", + "X, y = load_boston(return_X_y=True)\n", + "\n", + "from sklearn.preprocessing import StandardScaler\n", + "ss = StandardScaler()\n", + "X = ss.fit_transform(X)\n", + "y = (y - y.mean()) / y.std()\n", + "\n", + "\n", + "reg = ElasticNetCV(l1_ratio=[.1, .5, .7, .9, .95, .99, 1], fit_intercept=True)\n", + "\n", + "from sklearn.model_selection import KFold\n", + "scores = cross_val_score(reg, X, y, cv=KFold(shuffle=True, random_state=42))\n", + "print(f'MEAN R2: {np.mean(scores).mean():.3f}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Linear DAGRegressor" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The DAGRegressor has several parameters which can be used to better fit a more complicated noisy DAG:\n", + "- `alpha`: The l1 (lasso) regularisation parameter. Increasing this creates a sparser DAG.\n", + "- `beta`: The l2 (ridge) regularisation parameter.\n", + "It was decided to use `alpha` and `beta` rather than `alpha` and `l1_ratio` like in sklearn elasticnet to uncouple the parameters during optimisation.\n", + "\n", + "There are several parameters which are also of interest which have good defaults, but we highlight here:\n", + "- `dependent_target`: This forces the target variable y to be only a child node. This is important for performance because in some cases `X -> y` is indistinguishable from `y -> X`. Enabling this (default enabled) ensures that the regressor performance at least matches linear regression. The trade-off is that the learned structure might be less accurate if y does cause other features.\n", + "- `enforce_dag`: This thresholds the learned structure model until the system is a DAG. This is useful for removing the small straggler connections which enables the DAG to be visualised easier. It does not impact performance, because the regressor still uses those connections under the hood." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MEAN R2: 0.706\n", + "CRIM 0.000000\n", + "ZN 0.000000\n", + "INDUS 0.000000\n", + "CHAS 0.000000\n", + "NOX 0.000000\n", + "RM 0.310324\n", + "AGE 0.000000\n", + "DIS -0.225455\n", + "RAD 0.000000\n", + "TAX 0.000000\n", + "PTRATIO 0.000000\n", + "B 0.000000\n", + "LSTAT -0.372211\n", + "dtype: float64\n" + ] + }, + { + "data": { + "image/png": "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AAAAAADkURYBAAAAAAAgj7IIAAAAAAAAeZRFAAAAAAAAyKMsAgAAAAAAQB5lEQAAAAAAAPIoiwAAAAAAAJBHWQQAAAAAAIA8yiIAAAAAAADkURYBAAAAAAAgj7IIAAAAAAAAeZRFAAAAAAAAyKMsAgAAAAAAQB5lEQAAAAAAAPIoiwAAAAAAAJBHWQQAAAAAAIA8yiIAAAAAAADkURYBAAAAAAAgj7IIAAAAAAAAeZRFAAAAAAAAyKMsAgAAAAAAQB5lEQAAAAAAAPIoiwAAAAAAAJBHWQQAAAAAAIA8yiIAAAAAAADkURYBAAAAAAAgj7IIAAAAAAAAeZRFAAAAAAAAyKMsAgAAAAAAQB5lEQAAAAAAAPIoiwAAAAAAAJBHWQQAAAAAAIA8yiIAAAAAAADkURYBAAAAAAAgj7IIAAAAAAAAeZRFAAAAAAAAyKMsAgAAAAAAQB5lEQAAAAAAAPIoiwAAAAAAAJBHWQQAAAAAAIA8yiIAAAAAAADkURYBAAAAAAAgj7IIAAAAAAAAeZRFAAAAAAAAyKMsAgAAAAAAQB5lEQAAAAAAAPIoiwAAAAAAAJBHWQQAAAAAAIA8yiIAAAAAAADkURYBAAAAAAAgj7IIAAAAAAAAeZRFAAAAAAAAyKMsAgAAAAAAQB5lEQAAAAAAAPIoiwAAAAAAAJBHWQQAAAAAAIA8yiIAAAAAAADkURYBAAAAAAAgj7IIAAAAAAAAeZRFAAAAAAAAyKMsAgAAAAAAQB5lEQAAAAAAAPIoiwAAAAAAAJBHWQQAAAAAAIC8/x+67pUDEjBjNgAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from causalnex.structure.sklearn import DAGRegressor\n", + "from sklearn.model_selection import cross_val_score\n", + "\n", + "from sklearn.datasets import load_boston\n", + "X, y = load_boston(return_X_y=True)\n", + "\n", + "from sklearn.preprocessing import StandardScaler\n", + "ss = StandardScaler()\n", + "X = ss.fit_transform(X)\n", + "y = (y - y.mean()) / y.std()\n", + "\n", + "reg = DAGRegressor(\n", + " alpha=0.1,\n", + " beta=0.9,\n", + " fit_intercept=True,\n", + " hidden_layer_units=None,\n", + " dependent_target=True,\n", + " enforce_dag=True,\n", + " )\n", + "\n", + "from sklearn.model_selection import KFold\n", + "scores = cross_val_score(reg, X, y, cv=KFold(shuffle=True, random_state=42))\n", + "print(f'MEAN R2: {np.mean(scores).mean():.3f}')\n", + "\n", + "X_pd = pd.DataFrame(X, columns=load_boston(return_X_y=False)[\"feature_names\"])\n", + "y_pd = pd.Series(y, name=\"price\")\n", + "reg.fit(X_pd, y_pd)\n", + "print(pd.Series(reg.coef_, index=load_boston(return_X_y=False)[\"feature_names\"]))\n", + "reg.plot_dag(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### NonLinear DAGRegressor" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Specifying a nonlinear model is extremely simple, only a single parameter needs to be altered: `hidden_layer_units`\n", + "\n", + "`hidden_layer_units` takes _any_ **iterable** of **integers**: \n", + "- The value specifies the number of perceptrons to use in each nonlinear MLP layer:\n", + "- The number of elements in the iterable determines the number of hidden layers. \n", + "The more layers and more perceptrons per layer, the more complicated the function which can be fit. The trade off is a greater tendency to overfit, and slower fitting.\n", + "\n", + "A good default starting argument is ~[5]. This is unlikely to overfit, and usually demonstrates immidiately whether the DAG has nonlinear components.\n", + "\n", + "The setting of the `alpha` and `beta` parameters is very important.\n", + "Typically `beta` is more important than `alpha` when using nonlinear layers. This is because l2 is applied across all layers, whereas l1 is only applied to the first layer.\n", + "A good starting point is `~beta=0.5`.\n", + "\n", + "**NOTE it is very important to scale your data!**\n", + "\n", + "The nonlinear layers contain sigmoid nonlinearities which can become saturated with unscaled data. Also, unscaled data means that regularisation parameters do not impact weights across features equally.\n", + "\n", + "For convnenience, setting `standardize=True` scales both the X and y data during fit. It also inverse transforms the y on predict similar to the sklearn `TransformedTargetRegressor`." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MEAN R2: 0.833\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA+kAAANyCAYAAAAenIr/AAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nOzddXic15k+/vsdkgbEDJYsWRaDLTI7tuPYMSUOY5sUA4Vt+922v20X0u5ud7vbJs1umqZp2tCG7TimxMxMssUsi5mlGQ2+vz+UjP1aDNaMpPtzXbkunTPnnPcZK07mmUMC/CJEEBEREREREZGjNcscHQERERERERER9WOSTkREREREROQkmKQTEREREREROQkm6UREREREREROgkk6ERERERERkZNgkk5ERERERETkJJikExERERERETkJJulEREREREREToJJOhEREREREZGTYJJORERERERE5CSYpBMRERERERE5CSbpRERERERERE6CSToRERERERGRk2CSTkREREREROQkmKQTEREREREROQkm6UREREREREROgkk6ERERERERkZNgkk5ERERERETkJJikExERERERETkJJulEREREREREToJJOhEREREREZGTYJJORERERERE5CSYpBMRERERERE5CSbpRERERERERE6CSToRERERERGRk2CSTkREREREROQkmKQTEREREREROQkm6UREREREREROgkk6ERERERERkZNQODoAIiIiIqKp4KJSYX7kXPh4e8FNp4W7mw5ymRw9vXr09Paiu6cXFVXVaGxucXSoRDSLMUknIiIih8pMTcG6VSvG3E9vMKCpuRVNLa2orW9AfnEpRFEctG1cdBQe2Hz3REMdkqGvD79/9Q17OT5mPu7ftH7M44iiCL3BgF69AV3dPSituI7CkjL09OonLdboeRF4+N5NkrqsnDzsPXh02H5T/WcIAD9+9pvQajT28uvvfICmltYxj50QMx9pKUmIi46CUjHyx9/W9nbkF5Xi1PlLaG1vH/PziIgmgkk6EREROZSLSgVPd/cx9/N0d0dwQIC9XFVbhx2f70d1bf2AtkqFYlzPGC2VUjlpz/Py8LD/nJ6SBBEiruUWYOe+g+ju6Z1QnACwKHXBgNgyF6Zg35ETsFqtQ/ab6j9DAPBwc4NOq7WX5XL5mMb0cHfDg1s2IG5+1Jj6+Xh5YcXiDCzJWIhjp8/j8MnTMJstYxqDiGi8uCediIiIZoSwkGD88DtPY82KJY4OZVIJELAgMR4/+/4zmBMSNKGxNGo1EmLn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VIRORMTl7kRERETOrrunR1JeuThzQJv0BdIT2Me71P0rZrMF+UUlkrq0aXDKO9B/v/xnXxzA9j37Bp0xFyDATatFoL8f5s4JhZ+P96AJendvL97bvhOf7t0/FWETEQHgTDoRERGR06usqYW3p6e9vCAxHuWVVThzsf8qsJCgQAT5+9tf79XrB1yjNh5Xc/Ml+7KjIsLh4e6Gzq7uCY89Fc5euoKrufm4c+VSZC5MgUatHlW/rp4eZOXk4dDx0zD09d3mKImIpAT4RYiODoKIiIiI6HYSBAHhoSGInT8Pvt5e0Gm10Gn7T8Lv7u1FV3c32ju6UFBciqraOogiPyITkUM0M0knIiIiIiIicg7N3JNORERERERE5CSYpBMRERERERE5CSbpRERERERERE6CSToRERERERGRk2CSTkREREREROQkmKQTEREREREROQkm6UREREREREROgkk6ERERERERkZNgkk5ERERERETkJJikExERERERETkJJulEREREREREToJJOhEREREREZGTYJJORERERERE5CSYpBMRERERERE5CSbpRERERERERE6CSToRERERERGRk2CSTkREREREROQkmKQTEREREREROQkm6UREREREREROgkk6ERERERERkZNgkk5ERERERETkJJikExERERERETkJJulEREREREREToJJOhEREREREZGTYJJORERENAO56bSQyfhRj4houlE4OgAiIiIimrgFifHYdNdqrFySieDAAKiUSlitVjS1tOLspSwcOHYSx06fgyiKjg6ViIiGIcAvgv+lJiIiIpqmwkND8IsfPY9VyxaP2DavqBj//tIfcfla7hRERkRE49DMJJ2IiIhomlq+KB0v/es/wd1NBwgCFEpXyJWuEORKCIIMomiDaLPCau6D1aSHKIqwWq34t5dewfvbdzk6fCIiGqhZDq3XC46OgoiIiIjGJjU5EW/84T+hUashU6jgovOGXKWBIFdAEGSAIEAQZBBkcsiVLlC4aCCKNkC0YtXSxWhtb0duQbGj3wYREUnpOZNORERENM34enth93tvwNvTE3KlK1QaT0AQ0NZjwsFrLcit7kaX3gyNixzRwTqsTfLFHF81AMDS1w1zXw8sFgsef/ZHuJZX4OB3Q0REN+FMOhEREdF089PvP4PMhSmQyZVw0XoDgoAzhW347WdlKKjt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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from causalnex.structure.sklearn import DAGRegressor\n", + "from sklearn.model_selection import cross_val_score\n", + "\n", + "from sklearn.datasets import load_boston\n", + "X, y = load_boston(return_X_y=True)\n", + "\n", + "reg = DAGRegressor(threshold=0.0,\n", + " alpha=0.0,\n", + " beta=0.5,\n", + " fit_intercept=True,\n", + " hidden_layer_units=[5],\n", + " standardize=True,\n", + " )\n", + "\n", + "from sklearn.model_selection import KFold\n", + "scores = cross_val_score(reg, X, y, cv=KFold(shuffle=True, random_state=42))\n", + "print(f'MEAN R2: {np.mean(scores).mean():.3f}')\n", + "\n", + "X_pd = pd.DataFrame(X, columns=load_boston(return_X_y=False)[\"feature_names\"])\n", + "y_pd = pd.Series(y, name=\"price\")\n", + "reg.fit(X_pd, y_pd)\n", + "\n", + "reg.plot_dag(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Interpereting the Nonlinear DAG\n", + "\n", + "For nonlinear analysis, understanding the impact of one feature on another is not as simple as taking the mean effect as in the linear case.\n", + "Instead, a combination of `reg.coef_` and `reg.feature_importances` should be used:\n", + "\n", + "- `reg.coef_` provides the mean **directional** effect of all the features on the target. This gives average directional information, but can be misleading in terms of magnitude if the feature has a positive _and_ negative effect on the target.\n", + "\n", + "- `reg.feature_importances_` provides the mean **magnitude** effect of the features on the target. These values will be _strictly larger_ than the `reg.coef_` because there are no cancellation effects due to sign differences. \n", + "\n", + "The magnitude difference between the `reg.coef_` and `reg.feature_importances_` values can give insight into the _degree of directional variability_ of the parameter:\n", + "- A large difference means that the parameter has **large positive and negative effects** on the target. \n", + "- A zero difference means that the parameter always has the same directional impact on the target." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MEAN R2: 0.859\n", + "MEAN EFFECT DIRECTIONAL:\n", + "CRIM -0.455048\n", + "ZN -0.081971\n", + "INDUS 0.031664\n", + "CHAS 0.033811\n", + "NOX -0.226108\n", + "RM 0.328084\n", + "AGE -0.160502\n", + "DIS -0.479593\n", + "RAD 0.265122\n", + "TAX -0.230414\n", + "PTRATIO -0.089173\n", + "B 0.098137\n", + "LSTAT -0.344907\n", + "dtype: float64\n", + "MEAN EFFECT MAGNITUDE:\n", + "CRIM 0.455280\n", + "ZN 0.083515\n", + "INDUS 0.109172\n", + "CHAS 0.048846\n", + "NOX 0.226108\n", + "RM 0.364239\n", + "AGE 0.176076\n", + "DIS 0.479593\n", + "RAD 0.265122\n", + "TAX 0.238738\n", + "PTRATIO 0.118665\n", + "B 0.150867\n", + "LSTAT 0.347829\n", + "dtype: float64\n" + ] + }, + { + "data": { + "image/png": "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z4ZIkHgsgtfl4D9O789vn9tOe7D7uqLZY/I5YXQ+YCV/7V547S2tiomIfEUZFPT/iGWRIiJfLU6ngzNOOoETj53OUePGkpGeBiQCTmV1DXMXLOb5V99k7cbNX/CVfnlNGjeaqupaqqprPvZz/PrH3+OcU+dgmDbcGflgmNS3hnjkvSqWb285qCgfirJcnDu1mKOH5gIQDbYRCbYTCAaZc+FV1NTVf+J7EhGRL51FCi8iIofwuN2kp3lpbG7p9cqBfHKZGek88c876NenHMO04UrLxbAlNgj4Q1H21AcJhGMUZbso2V+gDxbRYDuRYDsA//Xz/+P5V9/8gu5AREQ+YwovIiLy5dGvTzn3/eU2SouLwDBwuNKwOb3J+S1JlkU8GiISbCcei2BZFn+66z7+/q+Hv5gLFxGRz4PCi4iIfLnk5eTw+1/8kGmTJiS/Z9ociQBjGBCPE4+FsazEX18trW385Dd/5NW33/2iLllERD4fCi8iIvLlNGvmNK6/7GLGjx6B2cXQydr6Bp575Q3ufvARWtvav4ArFBGRz5nCi4iIfLnl5eQwdtQwigsLcDmd1Dc2sX3nbjZs2ZpcfRERkSOCwouIiIiIiKSERZ3X4UVERERERL6EFF5ERERERCQlKLyIiIiIiEhKUHgREREREZGUoPAiIiIiIiIpQeFFRERERERSgsKLiIiIiIikBIUXERERERFJCQovIiIiIiKSEhReREREREQkJSi8iIiIiIhISlB4ERERERGRlKDwIiIiIiIiKUHhRUREREREUoLCi4iIiIiIpASFFxER1j31kAAAAopJREFUERERSQkKLyIiIiIikhIUXkREREREJCUovIiIiIiISEpQeBERERERkZSg8CIiIiIiIilB4UVERERERFKCwouIiIiIiKQEhRcREREREUkJCi8iIiIiIpISFF5ERERERCQlKLyIiIiIiEhKUHgREREREZGUoPAiIiIiIiIpQeFFRERERERSgsKLiIiIiIikBIUXERERERFJCQovIiIiIiKSEhReREREREQkJSi8iIiIiIhISlB4ERERERGRlKDwIiIiIiIiKUHhRUREREREUoLCi4iIiIiIpASFFxERERERSQkKLyIiIiIikhIUXkREREREJCUovIiIiIiISEpQeBERERERkZSg8CIiIiIiIilB4UVERERERFKCwouIiIiIiKQEhRcREREREUkJCi8iIiIiIpISFF5ERERERCQlKLyIiIiIiEhKUHgREREREZGUoPAiIiIiIiIpQeFFRERERERSgsKLiIiIiIikBIUXERERERFJCQovIiIiIiKSEhReREREREQkJSi8iIiIiIhISlB4ERERERGRlKDwIiIiIiIiKUHhRUREREREUoLCi4iIiIiIpASFFxERERERSQkKLyIiIiIikhIUXkREREREJCUovIiIiIiISEpQeBERERERkZSg8CIiIiIiIilB4UVERERERFKCwouIiIiIiKQEhRcREREREUkJCi8iIiIiIpISFF5ERERERCQlKLyIiIiIiEhKUHgREREREZGUoPAiIiIiIiIpQeFFRERERERSgsKLiIiIiIikBIUXERERERFJCQovIiIiIiKSEhReREREREQkJSi8iIiIiIhISlB4ERERERGRlKDwIiIiIiIiKUHhRUREREREUsL/B1sHB7wmxuVSAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from causalnex.structure.sklearn import DAGRegressor\n", + "from sklearn.model_selection import cross_val_score\n", + "\n", + "from sklearn.datasets import load_boston\n", + "X, y = load_boston(return_X_y=True)\n", + "\n", + "reg = DAGRegressor( alpha=0.0,\n", + " beta=1.0,\n", + " fit_intercept=True,\n", + " hidden_layer_units=[8, 8, 8],\n", + " standardize=True,\n", + " )\n", + "\n", + "from sklearn.model_selection import KFold\n", + "scores = cross_val_score(reg, X_pd.values, y_pd.values, cv=KFold(shuffle=True, random_state=42))\n", + "print(f'MEAN R2: {np.mean(scores).mean():.3f}')\n", + "\n", + "reg.fit(X_pd, y_pd)\n", + "print(\"MEAN EFFECT DIRECTIONAL:\")\n", + "print(pd.Series(reg.coef_, index=load_boston(return_X_y=False)[\"feature_names\"]))\n", + "print(\"MEAN EFFECT MAGNITUDE:\")\n", + "print(pd.Series(reg.feature_importances_, index=load_boston(return_X_y=False)[\"feature_names\"]))\n", + "\n", + "reg.plot_dag(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `reg.get_edges_to_node` method allows for analysis of other edges in the graph easily.\n", + "\n", + "Passing in `data=\"weight\"` returns the mean effect magnitude of the variables on the requested node. It is equivalent to the `reg.feature_importances` return for the target node.\n", + "\n", + "Passing in `data=\"mean_effect\"` returns the mean directional effect.\n", + "\n", + "Below is a good example of a large difference between the magnitude and directional effects: \n", + "- The feature RAD has overall a large effect on the presence of NOX. \n", + "- However, the _directional_ effect of this feature is highly variable, which leads the mean_effect to be an order of magnitude smaller than the mean effect magnitude!" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "CRIM 0.663028\n", + "ZN 0.000000\n", + "INDUS 0.000000\n", + "CHAS 0.000000\n", + "NOX 0.000000\n", + "RM 0.000000\n", + "AGE 0.000000\n", + "DIS 0.723618\n", + "RAD 0.429126\n", + "TAX 0.858995\n", + "PTRATIO 0.000000\n", + "B 0.000000\n", + "LSTAT 0.000000\n", + "dtype: float64" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vals = reg.get_edges_to_node(\"NOX\", data=\"weight\").copy()\n", + "vals[vals.abs() < 0.01] = 0\n", + "vals" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "CRIM 0.644507\n", + "ZN 0.000000\n", + "INDUS 0.000000\n", + "CHAS 0.000000\n", + "NOX 0.000000\n", + "RM 0.000000\n", + "AGE 0.000000\n", + "DIS -0.615096\n", + "RAD 0.063520\n", + "TAX -0.746946\n", + "PTRATIO 0.000000\n", + "B 0.000000\n", + "LSTAT 0.000000\n", + "dtype: float64" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "vals = reg.get_edges_to_node(\"NOX\", data=\"mean_effect\")\n", + "vals[vals.abs() < 0.01] = 0\n", + "vals" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Dependent Target\n", + "\n", + "Setting the `dependent_target=False` has an impact on performance as shown below, but can give better insight into the overall nonlinear structure of the data.\n", + "\n", + "This is effectively the same as fitting causalnex on the data using from_pandas, but using the sklearn interface provides a set of useful convenience functions not present in the base causalnex implementation." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MEAN R2: 0.838\n", + "MEAN EFFECT DIRECTIONAL:\n", + "CRIM -0.152426\n", + "ZN 0.006836\n", + "INDUS 0.024601\n", + "CHAS 0.030994\n", + "NOX -0.182232\n", + "RM 0.273697\n", + "AGE -0.071418\n", + "DIS -0.296711\n", + "RAD 0.111622\n", + "TAX -0.223531\n", + "PTRATIO -0.151172\n", + "B 0.071595\n", + "LSTAT -0.412829\n", + "dtype: float64\n", + "MEAN EFFECT MAGNITUDE:\n", + "CRIM 0.152426\n", + "ZN 0.006899\n", + "INDUS 0.053876\n", + "CHAS 0.048723\n", + "NOX 0.182232\n", + "RM 0.283586\n", + "AGE 0.089832\n", + "DIS 0.296711\n", + "RAD 0.123725\n", + "TAX 0.228080\n", + "PTRATIO 0.151172\n", + "B 0.071595\n", + "LSTAT 0.413056\n", + "dtype: float64\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from causalnex.structure.sklearn import DAGRegressor\n", + "from sklearn.model_selection import cross_val_score\n", + "\n", + "from sklearn.datasets import load_boston\n", + "X, y = load_boston(return_X_y=True)\n", + "\n", + "reg = DAGRegressor( alpha=0.0,\n", + " beta=1.0,\n", + " fit_intercept=True,\n", + " hidden_layer_units=[5],\n", + " standardize=True,\n", + " dependent_target=True,\n", + " )\n", + "\n", + "from sklearn.model_selection import KFold\n", + "scores = cross_val_score(reg, X_pd.values, y_pd.values, cv=KFold(shuffle=True, random_state=42))\n", + "print(f'MEAN R2: {np.mean(scores).mean():.3f}')\n", + "\n", + "reg.fit(X_pd, y_pd)\n", + "print(\"MEAN EFFECT DIRECTIONAL:\")\n", + "print(pd.Series(reg.coef_, index=load_boston(return_X_y=False)[\"feature_names\"]))\n", + "print(\"MEAN EFFECT MAGNITUDE:\")\n", + "print(pd.Series(reg.feature_importances_, index=load_boston(return_X_y=False)[\"feature_names\"]))\n", + "\n", + "reg.plot_dag(True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/source/api_docs/index.rst b/docs/source/api_docs/index.rst index d3e3c07..61e55e4 100644 --- a/docs/source/api_docs/index.rst +++ b/docs/source/api_docs/index.rst @@ -61,6 +61,7 @@ Welcome to CausalNex's API docs and tutorials! 03_tutorial/03_tutorial.md 03_tutorial/plotting_tutorial.md + 03_tutorial/regressor_tutorial.md .. toctree:: :maxdepth: 2 diff --git a/requirements.txt b/requirements.txt index d01734f..7d32c47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,5 @@ pandas>=0.24.0, <1.0 pgmpy==0.1.6 prettytable>=0.7.2, <0.8 scikit-learn>=0.20.2, <0.23.0, !=0.22.2.post1 -scipy>=1.2.0, <1.3 +scipy>=1.2.0, <1.6 wrapt>=1.11.0, <1.12 diff --git a/setup.py b/setup.py index 7ee5ef9..04fae47 100644 --- a/setup.py +++ b/setup.py @@ -58,6 +58,7 @@ "plot": [ "pygraphviz>=1.5, <2.0", ], + "pytorch": ["torch>=1.4.0, <2.0"], } extras_require["all"] = sorted(chain.from_iterable(extras_require.values())) diff --git a/test_requirements.txt b/test_requirements.txt index 087dc41..2124d06 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1,10 +1,12 @@ -r requirements.txt -flake8>=3.5,<4.0 +flake8>=3.5, <4.0 +ipython>=7.0, <7.17 isort>=4.3.16, <5.0 -mock>=2.0.0,<3.0 +mock>=2.0.0, <3.0 pre-commit>=1.17.0, <2.0.0 pygraphviz>=1.5, <2.0 pylint>=2.5.2, <3.0 pytest-cov>=2.5, <3.0 -pytest-mock>=1.7.1,<2.0 -pytest>=4.3.0,<5.0 +pytest-mock>=1.7.1, <2.0 +pytest>=4.3.0, <5.0 +torch>=1.4.0, <1.6 diff --git a/tests/conftest.py b/tests/conftest.py index 3da7a2b..307495c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,6 +38,8 @@ from causalnex.structure.notears import from_pandas +# Ignoring limit of 1000 lines per module, since this module contains test sets. +# pylint: disable=C0302 @pytest.fixture def train_model() -> StructureModel: """ @@ -264,7 +266,6 @@ def train_data_discrete_cpds_k2(train_data_discrete) -> Dict[str, np.ndarray]: def create_cpds(data, pc=0): - df = data.copy(deep=True) # type: pd.DataFrame df_vals = {col: list(df[col].unique()) for col in df.columns} @@ -344,7 +345,6 @@ def create_cpds(data, pc=0): @pytest.fixture def train_data_idx_marginals(train_data_idx_cpds): - return create_marginals( train_data_idx_cpds, { @@ -359,7 +359,6 @@ def train_data_idx_marginals(train_data_idx_cpds): @pytest.fixture def train_data_discrete_marginals(train_data_discrete_cpds): - return create_marginals( train_data_discrete_cpds, { @@ -489,6 +488,534 @@ def bn(train_data_idx, train_data_discrete) -> BayesianNetwork: ).fit_node_states_and_cpds(train_data_discrete) +@pytest.fixture() +def data_dynotears_p1() -> Dict[str, np.ndarray]: + """ + Training data for testing Dynamic Bayesian Networks. Return a time series with 50 time points, with 5 columns + This data was simulated with te following configurations + Configurations: + - data points 50, + - num. variables: 5, + - p (lag amount): 1, + - graph type (intra-slice graph): 'erdos-renyi', + - graph type (inter-slice graph): 'erdos-renyi', + - SEM type: 'linear-gauss', + - weight range, intra-slice graph: (0.5, 2.0), + - weight range, inter-slice graph: (0.3, 0.5), + - expected degree, inter-slice graph: 3, + - noise scale (gaussian noise): 1.0, + - w decay: 1.1 + Returns: + dictionary with keys W (intra-weights), A (inter-weights), X and Y (inputs of from_numpy_dynamic) + """ + data = { + "W": np.array( + [ + [0.0, -0.55, 0.0, 1.48, 0.0], + [0.0, 0.0, -0.99, 0.0, 0.0], + [0.0, 0.0, 0.0, -1.13, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, -1.91, -0.64, -1.31, 0.0], + ] + ), + "A": np.array( + [ + [-0.35, -0.32, -0.33, 0.37, 0.0], + [-0.41, -0.42, -0.36, 0.33, -0.35], + [0.46, 0.0, 0.44, -0.36, -0.38], + [0.0, 0.0, -0.45, 0.0, 0.43], + [0.31, 0.4, 0.0, 0.0, -0.44], + ] + ), + "X": np.array( + [ + [-8.7, -3.1, -5.1, 2.8, -2.0], + [0.3, -5.8, 2.9, -12.6, 5.1], + [4.8, 17.4, -4.2, 17.6, -6.8], + [-12.5, -20.6, -1.9, -18.5, 8.3], + [14.4, 14.1, 7.7, 6.8, -3.0], + [-8.1, 0.1, -8.6, 9.0, -3.3], + [-3.3, -11.4, 1.3, -18.4, 8.0], + [9.4, 14.4, 3.7, 11.4, -6.5], + [-9.7, -7.0, -3.5, -3.2, -0.0], + [1.7, -2.7, 5.8, -14.8, 4.5], + [3.9, 17.2, -2.4, 19.2, -9.8], + [-12.5, -22.2, 1.2, -23.2, 7.8], + [17.2, 18.2, 9.2, 11.6, -6.4], + [-12.2, -7.9, -2.8, -2.6, -1.2], + [5.8, -4.9, 9.0, -12.6, 3.9], + [6.0, 13.5, 2.3, 15.1, -8.4], + [-10.1, -12.8, -1.4, -13.4, 4.5], + [9.7, 10.3, 5.5, 7.1, -3.5], + [-7.2, -1.4, -4.5, 0.2, -1.3], + [1.8, -3.7, 1.6, -4.8, 3.2], + [2.6, 6.7, -2.1, 9.7, -2.7], + [-4.6, -7.6, -1.7, -4.4, 2.6], + [4.6, -0.1, 4.5, -4.1, 1.9], + [3.3, 5.1, -0.7, 11.0, -4.1], + [-4.3, -12.7, -0.7, -9.5, 5.9], + [8.0, 6.0, 5.6, 0.8, -1.1], + [-2.4, 4.9, -3.9, 11.3, -4.7], + [-4.6, -12.0, -0.3, -13.4, 6.5], + [8.1, 15.9, -0.8, 16.0, -6.5], + [-11.5, -14.8, -5.0, -7.5, 4.4], + [10.0, 3.3, 7.1, -2.8, 1.7], + [-0.7, 3.8, -1.8, 9.2, -4.0], + [-1.6, -13.7, 5.3, -13.0, 4.7], + [11.0, 8.2, 7.7, 6.7, -4.0], + [-4.0, -2.4, -3.0, 1.8, -1.7], + [1.1, -4.8, 2.9, -5.9, 3.0], + [3.4, 7.5, -1.5, 9.0, -2.4], + [-6.2, -3.4, -4.8, -2.2, 1.0], + [0.8, -0.3, 1.8, -2.5, 1.0], + [0.4, 1.7, -1.6, 1.9, -0.5], + [-2.3, -4.5, 2.9, -7.4, 2.3], + [3.9, 9.3, 2.4, 4.1, -3.8], + [-4.8, -0.6, -3.6, 3.0, -2.1], + [-1.8, -7.5, 2.9, -13.2, 4.7], + [5.6, 19.0, -3.7, 18.1, -7.8], + [-13.5, -19.3, -2.7, -17.7, 7.5], + [15.5, 10.6, 9.9, 5.7, -3.1], + [-6.8, 3.3, -6.8, 9.6, -4.8], + [-5.2, -15.3, 3.9, -21.6, 7.9], + [11.1, 22.0, -0.3, 20.7, -8.4], + ] + ), + "Y": np.array( + [ + [10.8, 14.6, 5.8, 8.1, -4.5], + [-8.7, -3.1, -5.1, 2.8, -2.0], + [0.3, -5.8, 2.9, -12.6, 5.1], + [4.8, 17.4, -4.2, 17.6, -6.8], + [-12.5, -20.6, -1.9, -18.5, 8.3], + [14.4, 14.1, 7.7, 6.8, -3.0], + [-8.1, 0.1, -8.6, 9.0, -3.3], + [-3.3, -11.4, 1.3, -18.4, 8.0], + [9.4, 14.4, 3.7, 11.4, -6.5], + [-9.7, -7.0, -3.5, -3.2, -0.0], + [1.7, -2.7, 5.8, -14.8, 4.5], + [3.9, 17.2, -2.4, 19.2, -9.8], + [-12.5, -22.2, 1.2, -23.2, 7.8], + [17.2, 18.2, 9.2, 11.6, -6.4], + [-12.2, -7.9, -2.8, -2.6, -1.2], + [5.8, -4.9, 9.0, -12.6, 3.9], + [6.0, 13.5, 2.3, 15.1, -8.4], + [-10.1, -12.8, -1.4, -13.4, 4.5], + [9.7, 10.3, 5.5, 7.1, -3.5], + [-7.2, -1.4, -4.5, 0.2, -1.3], + [1.8, -3.7, 1.6, -4.8, 3.2], + [2.6, 6.7, -2.1, 9.7, -2.7], + [-4.6, -7.6, -1.7, -4.4, 2.6], + [4.6, -0.1, 4.5, -4.1, 1.9], + [3.3, 5.1, -0.7, 11.0, -4.1], + [-4.3, -12.7, -0.7, -9.5, 5.9], + [8.0, 6.0, 5.6, 0.8, -1.1], + [-2.4, 4.9, -3.9, 11.3, -4.7], + [-4.6, -12.0, -0.3, -13.4, 6.5], + [8.1, 15.9, -0.8, 16.0, -6.5], + [-11.5, -14.8, -5.0, -7.5, 4.4], + [10.0, 3.3, 7.1, -2.8, 1.7], + [-0.7, 3.8, -1.8, 9.2, -4.0], + [-1.6, -13.7, 5.3, -13.0, 4.7], + [11.0, 8.2, 7.7, 6.7, -4.0], + [-4.0, -2.4, -3.0, 1.8, -1.7], + [1.1, -4.8, 2.9, -5.9, 3.0], + [3.4, 7.5, -1.5, 9.0, -2.4], + [-6.2, -3.4, -4.8, -2.2, 1.0], + [0.8, -0.3, 1.8, -2.5, 1.0], + [0.4, 1.7, -1.6, 1.9, -0.5], + [-2.3, -4.5, 2.9, -7.4, 2.3], + [3.9, 9.3, 2.4, 4.1, -3.8], + [-4.8, -0.6, -3.6, 3.0, -2.1], + [-1.8, -7.5, 2.9, -13.2, 4.7], + [5.6, 19.0, -3.7, 18.1, -7.8], + [-13.5, -19.3, -2.7, -17.7, 7.5], + [15.5, 10.6, 9.9, 5.7, -3.1], + [-6.8, 3.3, -6.8, 9.6, -4.8], + [-5.2, -15.3, 3.9, -21.6, 7.9], + ] + ), + } + return data + + +@pytest.fixture() +def data_dynotears_p2() -> Dict[str, np.ndarray]: + """ + Training data for testing Dynamic Bayesian Networks. Return a time series with 50 time points, with 5 columns + This data was simulated with te following configurations + Configurations: + - data points 50, + - num. variables: 5, + - p (lag amount): 2, + - graph type (intra-slice graph): 'erdos-renyi', + - graph type (inter-slice graph): 'erdos-renyi', + - SEM type: 'linear-gauss', + - weight range, intra-slice graph: (0.5, 2.0), + - weight range, inter-slice graph: (0.3, 0.5), + - expected degree, inter-slice graph: 3, + - noise scale (gaussian noise): 1.0, + - w decay: 1.1 + Returns: + dictionary with keys W (intra-weights) ,A (inter-weights), X and Y (inputs of from_numpy_dynamic) + """ + data = { + "W": np.array( + [ + [0.0, 0.0, 0.0, 0.0, -1.08], + [1.16, 0.0, 0.0, -0.81, 0.89], + [-1.83, 0.58, 0.0, 0.61, -1.31], + [1.03, 0.0, 0.0, 0.0, -0.97], + [0.0, 0.0, 0.0, 0.0, 0.0], + ] + ), + "A": np.array( + [ + [0.31, 0.0, 0.0, 0.32, 0.0], + [0.0, 0.36, 0.0, 0.0, 0.5], + [-0.43, 0.0, 0.0, -0.49, -0.39], + [0.39, 0.0, 0.39, 0.0, 0.38], + [0.0, 0.37, 0.0, 0.43, 0.0], + [0.0, -0.37, 0.34, 0.0, 0.0], + [-0.3, 0.0, -0.42, 0.0, 0.45], + [0.0, -0.34, 0.0, 0.0, 0.0], + [0.0, 0.31, 0.0, 0.29, 0.36], + [-0.43, 0.37, 0.0, 0.0, -0.34], + ] + ), + "X": np.array( + [ + [3.1, 0.9, 1.6, 2.9, -6.5], + [-5.6, -4.1, 3.9, 4.7, -3.4], + [-6.3, -3.5, 2.2, 1.4, 0.4], + [-0.3, 0.2, -0.5, -1.5, 2.0], + [2.4, 1.6, -0.9, -0.7, -0.9], + [0.6, -0.1, 0.2, 1.5, -2.7], + [-2.4, -1.6, 2.2, 1.3, -1.6], + [-4.6, 0.5, 1.4, -2.6, 6.9], + [-1.3, -0.1, -1.3, -1.7, 3.7], + [0.5, 4.7, -2.0, -4.1, 6.8], + [8.8, 4.9, -2.7, -1.1, -0.3], + [3.8, 3.1, -0.4, 0.1, 1.5], + [2.0, -0.9, 0.1, 3.9, -1.6], + [-3.8, -0.9, 2.7, 1.3, 2.4], + [-4.4, 3.2, 2.1, -3.3, 9.9], + [0.2, 4.8, -1.6, -3.7, 9.7], + [5.1, 2.8, -6.4, -1.5, 3.7], + [8.1, 4.3, -2.4, -0.1, -1.6], + [2.1, 0.9, 1.5, 2.9, -4.1], + [-4.1, -3.9, 0.2, 1.1, 4.1], + [0.3, -3.0, 0.2, 3.0, -4.7], + [-1.2, 2.8, 2.7, -0.9, -0.9], + [-0.8, 1.8, 1.7, -0.7, 2.4], + [-0.2, -2.2, -1.8, 2.6, -0.3], + [-0.9, 2.1, 0.8, -2.3, 5.9], + [1.5, 3.6, 0.4, -0.1, -0.1], + [-3.0, 1.3, -1.6, -3.2, 9.5], + [6.8, -0.1, -3.5, 2.5, -3.2], + [3.5, 1.8, -1.7, -2.1, 0.5], + [5.1, 1.0, 1.6, 3.6, -6.5], + [-2.8, -3.0, 1.4, 1.8, -3.5], + [-5.9, -4.8, 3.2, 4.0, -3.6], + [-6.7, -2.2, 1.3, -2.7, 4.8], + [0.4, 0.0, -1.8, -0.9, 0.4], + [3.2, 1.7, -2.9, -3.6, 3.1], + [5.7, 0.9, -2.9, -0.3, -1.4], + [3.4, -2.0, -0.5, 3.5, -9.1], + [-4.3, -4.4, 2.9, 2.6, -5.2], + [-9.7, -6.4, 3.4, 1.9, -1.4], + [-5.2, -1.4, 1.6, -2.3, 1.0], + [-1.1, 1.1, -1.6, -3.5, 4.1], + [0.9, 2.0, -1.6, -2.6, 3.3], + [4.3, 4.8, -0.7, -0.7, 0.3], + [4.9, -0.4, -1.3, 1.8, -3.9], + [1.3, -2.9, -0.8, 2.0, -1.3], + [-0.1, -0.8, 2.9, 3.1, -6.7], + [-5.8, 0.5, 4.0, -1.2, 2.6], + [-1.8, -2.4, -2.0, -2.4, 3.5], + [3.3, 0.7, -3.0, -0.6, 0.0], + [4.5, -0.1, 0.4, 2.9, -10.8], + ] + ), + "Y": np.array( + [ + [6.1, 0.5, -2.0, 0.9, -3.0, 2.9, 1.5, -0.9, -0.2, 0.8], + [3.1, 0.9, 1.6, 2.9, -6.5, 6.1, 0.5, -2.0, 0.9, -3.0], + [-5.6, -4.1, 3.9, 4.7, -3.4, 3.1, 0.9, 1.6, 2.9, -6.5], + [-6.3, -3.5, 2.2, 1.4, 0.4, -5.6, -4.1, 3.9, 4.7, -3.4], + [-0.3, 0.2, -0.5, -1.5, 2.0, -6.3, -3.5, 2.2, 1.4, 0.4], + [2.4, 1.6, -0.9, -0.7, -0.9, -0.3, 0.2, -0.5, -1.5, 2.0], + [0.6, -0.1, 0.2, 1.5, -2.7, 2.4, 1.6, -0.9, -0.7, -0.9], + [-2.4, -1.6, 2.2, 1.3, -1.6, 0.6, -0.1, 0.2, 1.5, -2.7], + [-4.6, 0.5, 1.4, -2.6, 6.9, -2.4, -1.6, 2.2, 1.3, -1.6], + [-1.3, -0.1, -1.3, -1.7, 3.7, -4.6, 0.5, 1.4, -2.6, 6.9], + [0.5, 4.7, -2.0, -4.1, 6.8, -1.3, -0.1, -1.3, -1.7, 3.7], + [8.8, 4.9, -2.7, -1.1, -0.3, 0.5, 4.7, -2.0, -4.1, 6.8], + [3.8, 3.1, -0.4, 0.1, 1.5, 8.8, 4.9, -2.7, -1.1, -0.3], + [2.0, -0.9, 0.1, 3.9, -1.6, 3.8, 3.1, -0.4, 0.1, 1.5], + [-3.8, -0.9, 2.7, 1.3, 2.4, 2.0, -0.9, 0.1, 3.9, -1.6], + [-4.4, 3.2, 2.1, -3.3, 9.9, -3.8, -0.9, 2.7, 1.3, 2.4], + [0.2, 4.8, -1.6, -3.7, 9.7, -4.4, 3.2, 2.1, -3.3, 9.9], + [5.1, 2.8, -6.4, -1.5, 3.7, 0.2, 4.8, -1.6, -3.7, 9.7], + [8.1, 4.3, -2.4, -0.1, -1.6, 5.1, 2.8, -6.4, -1.5, 3.7], + [2.1, 0.9, 1.5, 2.9, -4.1, 8.1, 4.3, -2.4, -0.1, -1.6], + [-4.1, -3.9, 0.2, 1.1, 4.1, 2.1, 0.9, 1.5, 2.9, -4.1], + [0.3, -3.0, 0.2, 3.0, -4.7, -4.1, -3.9, 0.2, 1.1, 4.1], + [-1.2, 2.8, 2.7, -0.9, -0.9, 0.3, -3.0, 0.2, 3.0, -4.7], + [-0.8, 1.8, 1.7, -0.7, 2.4, -1.2, 2.8, 2.7, -0.9, -0.9], + [-0.2, -2.2, -1.8, 2.6, -0.3, -0.8, 1.8, 1.7, -0.7, 2.4], + [-0.9, 2.1, 0.8, -2.3, 5.9, -0.2, -2.2, -1.8, 2.6, -0.3], + [1.5, 3.6, 0.4, -0.1, -0.1, -0.9, 2.1, 0.8, -2.3, 5.9], + [-3.0, 1.3, -1.6, -3.2, 9.5, 1.5, 3.6, 0.4, -0.1, -0.1], + [6.8, -0.1, -3.5, 2.5, -3.2, -3.0, 1.3, -1.6, -3.2, 9.5], + [3.5, 1.8, -1.7, -2.1, 0.5, 6.8, -0.1, -3.5, 2.5, -3.2], + [5.1, 1.0, 1.6, 3.6, -6.5, 3.5, 1.8, -1.7, -2.1, 0.5], + [-2.8, -3.0, 1.4, 1.8, -3.5, 5.1, 1.0, 1.6, 3.6, -6.5], + [-5.9, -4.8, 3.2, 4.0, -3.6, -2.8, -3.0, 1.4, 1.8, -3.5], + [-6.7, -2.2, 1.3, -2.7, 4.8, -5.9, -4.8, 3.2, 4.0, -3.6], + [0.4, 0.0, -1.8, -0.9, 0.4, -6.7, -2.2, 1.3, -2.7, 4.8], + [3.2, 1.7, -2.9, -3.6, 3.1, 0.4, 0.0, -1.8, -0.9, 0.4], + [5.7, 0.9, -2.9, -0.3, -1.4, 3.2, 1.7, -2.9, -3.6, 3.1], + [3.4, -2.0, -0.5, 3.5, -9.1, 5.7, 0.9, -2.9, -0.3, -1.4], + [-4.3, -4.4, 2.9, 2.6, -5.2, 3.4, -2.0, -0.5, 3.5, -9.1], + [-9.7, -6.4, 3.4, 1.9, -1.4, -4.3, -4.4, 2.9, 2.6, -5.2], + [-5.2, -1.4, 1.6, -2.3, 1.0, -9.7, -6.4, 3.4, 1.9, -1.4], + [-1.1, 1.1, -1.6, -3.5, 4.1, -5.2, -1.4, 1.6, -2.3, 1.0], + [0.9, 2.0, -1.6, -2.6, 3.3, -1.1, 1.1, -1.6, -3.5, 4.1], + [4.3, 4.8, -0.7, -0.7, 0.3, 0.9, 2.0, -1.6, -2.6, 3.3], + [4.9, -0.4, -1.3, 1.8, -3.9, 4.3, 4.8, -0.7, -0.7, 0.3], + [1.3, -2.9, -0.8, 2.0, -1.3, 4.9, -0.4, -1.3, 1.8, -3.9], + [-0.1, -0.8, 2.9, 3.1, -6.7, 1.3, -2.9, -0.8, 2.0, -1.3], + [-5.8, 0.5, 4.0, -1.2, 2.6, -0.1, -0.8, 2.9, 3.1, -6.7], + [-1.8, -2.4, -2.0, -2.4, 3.5, -5.8, 0.5, 4.0, -1.2, 2.6], + [3.3, 0.7, -3.0, -0.6, 0.0, -1.8, -2.4, -2.0, -2.4, 3.5], + ] + ), + } + return data + + +@pytest.fixture() +def data_dynotears_p3() -> Dict[str, np.ndarray]: + """ + Training data for testing Dynamic Bayesian Networks. Return a time series with 50 time points, with 5 columns + This data was simulated with te following configurations. + Configurations: + - data points 50, + - num. variables: 5, + - p (lag amount): 3, + - graph type (intra-slice graph): 'erdos-renyi', + - graph type (inter-slice graph): 'erdos-renyi', + - SEM type: 'linear-gauss', + - weight range, intra-slice graph: (0.5, 2.0), + - weight range, inter-slice graph: (0.3, 0.5), + - expected degree, inter-slice graph: 3, + - noise scale (gaussian noise): 1.0, + - w decay: 1.1 + Returns: + dictionary with keys W (intra-weights), A (inter-weights), X and Y (inputs of from_numpy_dynamic) + """ + data = { + "W": np.array( + [ + [0.0, 0.0, -1.18, 0.0, -0.92], + [0.0, 0.0, -1.71, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, -1.15], + [0.0, 0.8, 0.0, 0.0, -1.65], + [0.0, 0.0, 0.0, 0.0, 0.0], + ] + ), + "A": np.array( + [ + [-0.42, -0.4, 0.35, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0], + [-0.38, 0.0, -0.48, -0.48, 0.44], + [0.0, -0.36, -0.31, 0.35, 0.0], + [0.0, 0.0, -0.35, 0.0, -0.45], + [0.27, 0.0, 0.0, -0.28, -0.43], + [-0.44, 0.35, 0.0, 0.0, -0.44], + [0.0, 0.0, 0.0, 0.0, 0.0], + [-0.42, -0.39, -0.27, 0.3, -0.29], + [0.0, -0.43, 0.0, -0.41, 0.4], + [0.0, 0.32, 0.38, -0.37, 0.37], + [0.0, 0.37, 0.0, 0.0, 0.41], + [0.0, 0.34, 0.0, 0.27, -0.28], + [-0.31, 0.0, 0.0, -0.34, 0.0], + [0.27, -0.38, 0.0, 0.37, 0.36], + ] + ), + "X": np.array( + [ + [-21.5, 26.1, -56.8, -15.4, 82.4], + [14.4, 20.3, -51.3, -18.8, -4.6], + [16.5, -17.0, 48.2, 18.7, -100.2], + [5.1, -38.8, 70.5, 5.1, 8.6], + [-19.4, 20.5, -47.8, -4.3, 86.9], + [9.8, 28.7, -69.5, -18.3, 8.2], + [9.5, -7.1, 42.4, 13.3, -107.0], + [2.8, -42.5, 83.7, 1.5, 3.1], + [-25.7, 18.6, -42.4, -8.5, 112.6], + [11.5, 34.3, -94.2, -20.5, 33.3], + [19.0, -9.7, 39.5, 18.2, -137.5], + [7.9, -60.4, 122.3, 8.1, -33.3], + [-32.5, 6.0, -19.4, -11.0, 130.2], + [3.5, 48.0, -127.1, -33.7, 81.8], + [28.4, 7.6, 1.0, 16.3, -146.2], + [20.0, -71.8, 153.1, 17.9, -93.3], + [-34.3, -13.0, 20.5, -4.4, 130.2], + [-9.4, 58.4, -150.8, -41.9, 141.6], + [26.7, 32.9, -49.8, 7.9, -134.1], + [34.3, -74.1, 161.6, 26.1, -152.1], + [-34.2, -35.1, 75.5, 8.0, 105.1], + [-21.9, 54.5, -151.3, -49.8, 199.9], + [19.2, 54.9, -98.1, -8.1, -95.3], + [47.4, -72.0, 158.9, 22.4, -192.6], + [-25.7, -60.3, 129.5, 21.9, 54.6], + [-26.9, 42.2, -136.6, -48.4, 243.1], + [12.6, 79.6, -153.1, -11.6, -57.4], + [54.1, -66.7, 153.3, 17.7, -241.5], + [-26.1, -79.5, 185.0, 25.9, 19.5], + [-34.8, 36.3, -133.8, -54.2, 311.0], + [10.6, 105.3, -219.7, -19.1, -15.3], + [73.9, -58.5, 138.7, 25.1, -307.4], + [-17.7, -107.1, 257.4, 47.9, -58.4], + [-51.3, 17.0, -93.4, -59.1, 354.7], + [-13.4, 140.4, -287.4, -43.0, 83.7], + [85.8, -28.4, 71.1, 11.6, -310.8], + [4.5, -130.3, 310.9, 68.7, -171.8], + [-51.1, -25.3, -12.3, -46.8, 361.3], + [-34.2, 158.2, -341.9, -48.7, 191.1], + [92.6, 7.7, -9.7, -2.9, -314.2], + [20.2, -143.1, 363.2, 83.7, -294.6], + [-54.6, -69.6, 82.3, -36.4, 356.7], + [-60.4, 176.2, -390.3, -61.8, 319.4], + [97.2, 53.8, -116.7, -18.9, -289.0], + [46.0, -152.8, 400.1, 103.7, -440.8], + [-52.1, -124.2, 206.7, -16.7, 307.3], + [-93.8, 183.7, -415.9, -74.5, 465.6], + [92.4, 118.4, -265.0, -47.7, -202.0], + [76.5, -140.2, 396.2, 116.8, -592.8], + [-36.6, -195.8, 366.6, 8.3, 202.5], + ] + ), + "Y_1": np.array( + [ + [-4.9, -31.2, 50.6, -5.4, 42.7, 11.0, -26.3, 66.7, 21.5, -82.3], + [-21.5, 26.1, -56.8, -15.4, 82.4, -4.9, -31.2, 50.6, -5.4, 42.7], + [14.4, 20.3, -51.3, -18.8, -4.6, -21.5, 26.1, -56.8, -15.4, 82.4], + [16.5, -17.0, 48.2, 18.7, -100.2, 14.4, 20.3, -51.3, -18.8, -4.6], + [5.1, -38.8, 70.5, 5.1, 8.6, 16.5, -17.0, 48.2, 18.7, -100.2], + [-19.4, 20.5, -47.8, -4.3, 86.9, 5.1, -38.8, 70.5, 5.1, 8.6], + [9.8, 28.7, -69.5, -18.3, 8.2, -19.4, 20.5, -47.8, -4.3, 86.9], + [9.5, -7.1, 42.4, 13.3, -107.0, 9.8, 28.7, -69.5, -18.3, 8.2], + [2.8, -42.5, 83.7, 1.5, 3.1, 9.5, -7.1, 42.4, 13.3, -107.0], + [-25.7, 18.6, -42.4, -8.5, 112.6, 2.8, -42.5, 83.7, 1.5, 3.1], + [11.5, 34.3, -94.2, -20.5, 33.3, -25.7, 18.6, -42.4, -8.5, 112.6], + [19.0, -9.7, 39.5, 18.2, -137.5, 11.5, 34.3, -94.2, -20.5, 33.3], + [7.9, -60.4, 122.3, 8.1, -33.3, 19.0, -9.7, 39.5, 18.2, -137.5], + [-32.5, 6.0, -19.4, -11.0, 130.2, 7.9, -60.4, 122.3, 8.1, -33.3], + [3.5, 48.0, -127.1, -33.7, 81.8, -32.5, 6.0, -19.4, -11.0, 130.2], + [28.4, 7.6, 1.0, 16.3, -146.2, 3.5, 48.0, -127.1, -33.7, 81.8], + [20.0, -71.8, 153.1, 17.9, -93.3, 28.4, 7.6, 1.0, 16.3, -146.2], + [-34.3, -13.0, 20.5, -4.4, 130.2, 20.0, -71.8, 153.1, 17.9, -93.3], + [-9.4, 58.4, -150.8, -41.9, 141.6, -34.3, -13.0, 20.5, -4.4, 130.2], + [26.7, 32.9, -49.8, 7.9, -134.1, -9.4, 58.4, -150.8, -41.9, 141.6], + [34.3, -74.1, 161.6, 26.1, -152.1, 26.7, 32.9, -49.8, 7.9, -134.1], + [-34.2, -35.1, 75.5, 8.0, 105.1, 34.3, -74.1, 161.6, 26.1, -152.1], + [-21.9, 54.5, -151.3, -49.8, 199.9, -34.2, -35.1, 75.5, 8.0, 105.1], + [19.2, 54.9, -98.1, -8.1, -95.3, -21.9, 54.5, -151.3, -49.8, 199.9], + [47.4, -72.0, 158.9, 22.4, -192.6, 19.2, 54.9, -98.1, -8.1, -95.3], + [-25.7, -60.3, 129.5, 21.9, 54.6, 47.4, -72.0, 158.9, 22.4, -192.6], + [-26.9, 42.2, -136.6, -48.4, 243.1, -25.7, -60.3, 129.5, 21.9, 54.6], + [12.6, 79.6, -153.1, -11.6, -57.4, -26.9, 42.2, -136.6, -48.4, 243.1], + [54.1, -66.7, 153.3, 17.7, -241.5, 12.6, 79.6, -153.1, -11.6, -57.4], + [-26.1, -79.5, 185.0, 25.9, 19.5, 54.1, -66.7, 153.3, 17.7, -241.5], + [-34.8, 36.3, -133.8, -54.2, 311.0, -26.1, -79.5, 185.0, 25.9, 19.5], + [10.6, 105.3, -219.7, -19.1, -15.3, -34.8, 36.3, -133.8, -54.2, 311.0], + [73.9, -58.5, 138.7, 25.1, -307.4, 10.6, 105.3, -219.7, -19.1, -15.3], + [-17.7, -107.1, 257.4, 47.9, -58.4, 73.9, -58.5, 138.7, 25.1, -307.4], + [-51.3, 17.0, -93.4, -59.1, 354.7, -17.7, -107.1, 257.4, 47.9, -58.4], + [-13.4, 140.4, -287.4, -43.0, 83.7, -51.3, 17.0, -93.4, -59.1, 354.7], + [85.8, -28.4, 71.1, 11.6, -310.8, -13.4, 140.4, -287.4, -43.0, 83.7], + [4.5, -130.3, 310.9, 68.7, -171.8, 85.8, -28.4, 71.1, 11.6, -310.8], + [-51.1, -25.3, -12.3, -46.8, 361.3, 4.5, -130.3, 310.9, 68.7, -171.8], + [-34.2, 158.2, -341.9, -48.7, 191.1, -51.1, -25.3, -12.3, -46.8, 361.3], + [92.6, 7.7, -9.7, -2.9, -314.2, -34.2, 158.2, -341.9, -48.7, 191.1], + [20.2, -143.1, 363.2, 83.7, -294.6, 92.6, 7.7, -9.7, -2.9, -314.2], + [-54.6, -69.6, 82.3, -36.4, 356.7, 20.2, -143.1, 363.2, 83.7, -294.6], + [-60.4, 176.2, -390.3, -61.8, 319.4, -54.6, -69.6, 82.3, -36.4, 356.7], + [97.2, 53.8, -116.7, -18.9, -289.0, -60.4, 176.2, -390.3, -61.8, 319.4], + [46.0, -152.8, 400.1, 103.7, -440.8, 97.2, 53.8, -116.7, -18.9, -289.0], + [-52.1, -124.2, 206.7, -16.7, 307.3, 46, -152.8, 400.1, 103.7, -440.8], + [-93.8, 183.7, -415.9, -74.5, 465.6, -52, -124.2, 206.7, -16.7, 307.3], + [92.4, 118.4, -265.0, -47.7, -202, -93.8, 183.7, -415.9, -74.5, 465.6], + [76.5, -140.2, 396.2, 116.8, -592.8, 92.4, 118.4, -265, -47.7, -202], + ] + ), + "Y_2": np.array( + [ + [22.4, 6.9, -22.7, -6.3, -40.2], + [11.0, -26.3, 66.7, 21.5, -82.3], + [-4.9, -31.2, 50.6, -5.4, 42.7], + [-21.5, 26.1, -56.8, -15.4, 82.4], + [14.4, 20.3, -51.3, -18.8, -4.6], + [16.5, -17.0, 48.2, 18.7, -100.2], + [5.1, -38.8, 70.5, 5.1, 8.6], + [-19.4, 20.5, -47.8, -4.3, 86.9], + [9.8, 28.7, -69.5, -18.3, 8.2], + [9.5, -7.1, 42.4, 13.3, -107.0], + [2.8, -42.5, 83.7, 1.5, 3.1], + [-25.7, 18.6, -42.4, -8.5, 112.6], + [11.5, 34.3, -94.2, -20.5, 33.3], + [19.0, -9.7, 39.5, 18.2, -137.5], + [7.9, -60.4, 122.3, 8.1, -33.3], + [-32.5, 6.0, -19.4, -11.0, 130.2], + [3.5, 48.0, -127.1, -33.7, 81.8], + [28.4, 7.6, 1.0, 16.3, -146.2], + [20.0, -71.8, 153.1, 17.9, -93.3], + [-34.3, -13.0, 20.5, -4.4, 130.2], + [-9.4, 58.4, -150.8, -41.9, 141.6], + [26.7, 32.9, -49.8, 7.9, -134.1], + [34.3, -74.1, 161.6, 26.1, -152.1], + [-34.2, -35.1, 75.5, 8.0, 105.1], + [-21.9, 54.5, -151.3, -49.8, 199.9], + [19.2, 54.9, -98.1, -8.1, -95.3], + [47.4, -72.0, 158.9, 22.4, -192.6], + [-25.7, -60.3, 129.5, 21.9, 54.6], + [-26.9, 42.2, -136.6, -48.4, 243.1], + [12.6, 79.6, -153.1, -11.6, -57.4], + [54.1, -66.7, 153.3, 17.7, -241.5], + [-26.1, -79.5, 185.0, 25.9, 19.5], + [-34.8, 36.3, -133.8, -54.2, 311.0], + [10.6, 105.3, -219.7, -19.1, -15.3], + [73.9, -58.5, 138.7, 25.1, -307.4], + [-17.7, -107.1, 257.4, 47.9, -58.4], + [-51.3, 17.0, -93.4, -59.1, 354.7], + [-13.4, 140.4, -287.4, -43.0, 83.7], + [85.8, -28.4, 71.1, 11.6, -310.8], + [4.5, -130.3, 310.9, 68.7, -171.8], + [-51.1, -25.3, -12.3, -46.8, 361.3], + [-34.2, 158.2, -341.9, -48.7, 191.1], + [92.6, 7.7, -9.7, -2.9, -314.2], + [20.2, -143.1, 363.2, 83.7, -294.6], + [-54.6, -69.6, 82.3, -36.4, 356.7], + [-60.4, 176.2, -390.3, -61.8, 319.4], + [97.2, 53.8, -116.7, -18.9, -289.0], + [46.0, -152.8, 400.1, 103.7, -440.8], + [-52.1, -124.2, 206.7, -16.7, 307.3], + [-93.8, 183.7, -415.9, -74.5, 465.6], + ] + ), + } + + data["Y"] = np.array( + [list(y1) + list(y2) for y1, y2 in zip(data["Y_1"], data["Y_2"])] + ) + del data["Y_1"] + del data["Y_2"] + return data + + @pytest.fixture def adjacency_mat_num_stability() -> np.ndarray: """ diff --git a/tests/structure/data_generators/__init__.py b/tests/structure/data_generators/__init__.py new file mode 100644 index 0000000..5da8261 --- /dev/null +++ b/tests/structure/data_generators/__init__.py @@ -0,0 +1,27 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tests/structure/data_generators/test_core.py b/tests/structure/data_generators/test_core.py new file mode 100644 index 0000000..e1dad0d --- /dev/null +++ b/tests/structure/data_generators/test_core.py @@ -0,0 +1,543 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. +import operator +from typing import Hashable, Tuple, Union + +import networkx as nx +import numpy as np +import pandas as pd +import pytest +from networkx.algorithms.dag import is_directed_acyclic_graph +from sklearn.gaussian_process.kernels import RBF + +from causalnex.structure.data_generators.core import ( + _sample_binary_from_latent, + _sample_count_from_latent, + _sample_poisson, + generate_structure, + nonlinear_sem_generator, + sem_generator, +) +from causalnex.structure.structuremodel import StructureModel + + +@pytest.fixture +def graph(): + graph = StructureModel() + edges = [(n, n + 1, 1) for n in range(5)] + graph.add_weighted_edges_from(edges) + return graph + + +@pytest.fixture +def schema(): + # use the default schema for 3 + schema = { + 0: "binary", + 1: "categorical:3", + 2: "binary", + 4: "continuous", + 5: "categorical:5", + } + return schema + + +class TestGenerateStructure: + @pytest.mark.parametrize("graph_type", ["erdos-renyi", "barabasi-albert", "full"]) + def test_is_dag_graph_type(self, graph_type): + """ Tests that the generated graph is a dag for all graph types. """ + degree, d_nodes = 4, 10 + sm = generate_structure(d_nodes, degree, graph_type) + assert is_directed_acyclic_graph(sm) + + @pytest.mark.parametrize("num_nodes,degree", [(5, 2), (10, 3), (15, 5)]) + def test_is_dag_nodes_degrees(self, num_nodes, degree): + """Tests that generated graph is dag for different numbers of nodes and degrees""" + sm = generate_structure(num_nodes, degree) + assert nx.is_directed_acyclic_graph(sm) + + def test_bad_graph_type(self): + """ Test that a value other than "erdos-renyi", "barabasi-albert", "full" throws ValueError """ + graph_type = "invalid" + degree, d_nodes = 4, 10 + with pytest.raises( + ValueError, + match="Unknown graph type invalid. Available types" + r" are \['erdos-renyi', 'barabasi-albert', 'full'\]", + ): + generate_structure(d_nodes, degree, graph_type) + + @pytest.mark.parametrize("num_nodes,degree", [(5, 2), (10, 3), (15, 5)]) + def test_expected_num_nodes(self, num_nodes, degree): + """ Test that generated structure has expected number of nodes = num_nodes """ + sm = generate_structure(num_nodes, degree) + assert len(sm.nodes) == num_nodes + + @pytest.mark.parametrize( + "num_nodes,degree,w_range", + [(5, 2, (1, 2)), (10, 3, (100, 200)), (15, 5, (1.0, 1.0))], + ) + def test_weight_range(self, num_nodes, degree, w_range): + """ Test that w_range is respected in output """ + w_min = w_range[0] + w_max = w_range[1] + sm = generate_structure(num_nodes, degree, w_min=w_min, w_max=w_max) + assert all(abs(sm[u][v]["weight"]) >= w_min for u, v in sm.edges) + assert all(abs(sm[u][v]["weight"]) <= w_max for u, v in sm.edges) + + @pytest.mark.parametrize("num_nodes", [-1, 0, 1]) + def test_num_nodes_exception(self, num_nodes): + """ Check a single node graph can't be generated """ + with pytest.raises(ValueError, match="DAG must have at least 2 nodes"): + generate_structure(num_nodes, 1) + + def test_min_max_weights_exception(self): + """ Check that w_range is valid """ + with pytest.raises( + ValueError, + match="Absolute minimum weight must be less than or equal to maximum weight", + ): + generate_structure(4, 1, w_min=0.5, w_max=0) + + def test_min_max_weights_equal(self): + """ If w_range (w, w) has w=w, check abs value of all weights respect this """ + w = 1 + sm = generate_structure(4, 1, w_min=w, w_max=w) + w_mat = nx.to_numpy_array(sm) + assert np.all((w_mat == 0) | (w_mat == w) | (w_mat == -w)) + + def test_erdos_renyi_degree_increases_edges(self): + """ Erdos-Renyi degree increases edges """ + edge_counts = [ + max( + [ + len(generate_structure(100, degree, "erdos-renyi").edges) + for _ in range(10) + ] + ) + for degree in [10, 90] + ] + + assert edge_counts == sorted(edge_counts) + + def test_barabasi_albert_degree_increases_edges(self): + """ Barabasi-Albert degree increases edges """ + edge_counts = [ + max( + [ + len(generate_structure(100, degree, "barabasi-albert").edges) + for _ in range(10) + ] + ) + for degree in [10, 90] + ] + + assert edge_counts == sorted(edge_counts) + + def test_full_network(self): + """ Fully connected network has expected edge counts """ + sm = generate_structure(40, degree=0, graph_type="full") + + assert len(sm.edges) == (40 * 39) / 2 + + +class TestMixedDataGen: + def test_run(self, graph, schema): + df = sem_generator( + graph=graph, + schema=schema, + default_type="continuous", + noise_std=1.0, + n_samples=1000, + intercept=False, + seed=12, + ) + + # test binary: + assert df[0].nunique() == 2 + assert df[2].nunique() == 2 + + # test categorical: + for col in ["1_{}".format(i) for i in range(3)]: + assert df[col].nunique() == 2 + assert len([x for x in df.columns if isinstance(x, str) and "1_" in x]) == 3 + + for col in ["5_{}".format(i) for i in range(5)]: + assert df[col].nunique() == 2 + assert len([x for x in df.columns if isinstance(x, str) and "5_" in x]) == 5 + + # test continuous + assert df[3].nunique() == 1000 + assert df[4].nunique() == 1000 + + def test_graph_not_a_dag(self): + graph = StructureModel() + graph.add_edges_from([(0, 1), (1, 2), (2, 0)]) + + with pytest.raises(ValueError, match="Provided graph is not a DAG"): + _ = sem_generator(graph=graph, seed=42) + + def test_not_permissible_type(self, graph): + schema = { + 0: "unknown data type", + } + with pytest.raises(ValueError, match="Unknown data type"): + _ = sem_generator( + graph=graph, + schema=schema, + default_type="continuous", + noise_std=1.0, + n_samples=1000, + intercept=False, + seed=12, + ) + + def test_missing_cardinality(self, graph): + schema = { + 0: "categorical", + 1: "categorical:3", + 5: "categorical:5", + } + with pytest.raises(ValueError, match="Missing cardinality for categorical"): + _ = sem_generator( + graph=graph, + schema=schema, + default_type="continuous", + noise_std=1.0, + n_samples=1000, + intercept=False, + seed=12, + ) + + def test_missing_default_type(self, graph): + with pytest.raises(ValueError, match="Unknown default data type"): + _ = sem_generator( + graph=graph, + schema=schema, + default_type="unknown", + noise_std=1.0, + n_samples=1000, + intercept=False, + seed=12, + ) + + def test_incorrect_weight_dist(self): + sm = StructureModel() + nodes = list(str(x) for x in range(6)) + np.random.shuffle(nodes) + sm.add_nodes_from(nodes) + + sm.add_weighted_edges_from([("0", "1", None), ("2", "4", None)]) + + with pytest.raises(ValueError, match="Unknown weight distribution"): + _ = sem_generator( + graph=sm, + schema=None, + default_type="continuous", + distributions={"weight": "unknown"}, + noise_std=2.0, + n_samples=1000, + intercept=False, + seed=10, + ) + + def test_incorrect_intercept_dist(self, graph): + with pytest.raises(ValueError, match="Unknown intercept distribution"): + _ = sem_generator( + graph=graph, + schema=None, + default_type="continuous", + distributions={"intercept": "unknown"}, + noise_std=2.0, + n_samples=10, + intercept=True, + seed=10, + ) + + # Seed 20 is an unlucky seed and fails the assertion. All other seeds tested + # pass the assertion. Similar issue to the categorical intercept test? + @pytest.mark.parametrize("seed", (10, 17)) + @pytest.mark.parametrize( + "n_categories", + ( + 2, + 5, + ), + ) + @pytest.mark.parametrize("weight_distribution", ["uniform", "gaussian"]) + @pytest.mark.parametrize("intercept_distribution", ["uniform", "gaussian"]) + def test_mixed_type_independence( + self, seed, n_categories, weight_distribution, intercept_distribution + ): + """ + Test whether the relation is accurate, implicitly tests sequence of + nodes. + """ + np.random.seed(seed) + + sm = StructureModel() + nodes = list(str(x) for x in range(6)) + np.random.shuffle(nodes) + sm.add_nodes_from(nodes) + # binary -> categorical + sm.add_weighted_edges_from([("0", "1", 10)]) + # binary -> continuous + sm.add_weighted_edges_from([("2", "4", None)]) + # binary -> count + sm.add_weighted_edges_from([("2", "6", 100)]) + + schema = { + "0": "binary", + "1": "categorical:{}".format(n_categories), + "2": "binary", + "4": "continuous", + "5": "categorical:{}".format(n_categories), + "6": "count", + } + + df = sem_generator( + graph=sm, + schema=schema, + default_type="continuous", + distributions={ + "weight": weight_distribution, + "intercept": intercept_distribution, + "count": 0.05, + }, + noise_std=2, + n_samples=100000, + intercept=True, + seed=seed, + ) + + atol = 0.05 # 5% difference bewteen joint & factored! + # 1. dependent links + # 0 -> 1 (we look at the class with the highest deviation from uniform + # to avoid small values) + c, _ = max( + [ + (c, np.abs(df["1_{}".format(c)].mean() - 1 / n_categories)) + for c in range(n_categories) + ], + key=operator.itemgetter(1), + ) + joint_proba, factored_proba = calculate_proba(df, "0", "1_{}".format(c)) + assert not np.isclose(joint_proba, factored_proba, rtol=0, atol=atol) + # 2 -> 4 + assert not np.isclose( + df["4"].mean(), df["4"][df["2"] == 1].mean(), rtol=0, atol=atol + ) + # binary on count + assert not np.isclose( + df.loc[df["2"] == 0, "6"].mean(), + df.loc[df["2"] == 1, "6"].mean(), + rtol=0, + atol=atol, + ) + + tol = 0.15 # relative tolerance of +- 15% of the + # 2. independent links + # categorical + c, _ = max( + [ + (c, np.abs(df["1_{}".format(c)].mean() - 1 / n_categories)) + for c in range(n_categories) + ], + key=operator.itemgetter(1), + ) + joint_proba, factored_proba = calculate_proba(df, "0", "5_{}".format(c)) + assert np.isclose(joint_proba, factored_proba, rtol=tol, atol=0) + + # binary + joint_proba, factored_proba = calculate_proba(df, "0", "2") + assert np.isclose(joint_proba, factored_proba, rtol=tol, atol=0) + + # categorical + c, _ = max( + [ + (c, np.abs(df["1_{}".format(c)].mean() - 1 / n_categories)) + for c in range(n_categories) + ], + key=operator.itemgetter(1), + ) + d, _ = max( + [ + (d, np.abs(df["5_{}".format(d)].mean() - 1 / n_categories)) + for d in range(n_categories) + ], + key=operator.itemgetter(1), + ) + joint_proba, factored_proba = calculate_proba( + df, "1_{}".format(d), "5_{}".format(c) + ) + assert np.isclose(joint_proba, factored_proba, rtol=tol, atol=0) + + # continuous + # for gaussian distributions, zero variance is equivalent to independence + assert np.isclose(df[["3", "4"]].corr().values[0, 1], 0, atol=tol) + + +def calculate_proba( + df: Union[pd.DataFrame, np.ndarray], col_0: Hashable, col_1: Hashable +) -> Tuple[float, float]: + if isinstance(df, pd.DataFrame): + marginal_0 = df[col_0].mean() + marginal_1 = df[col_1].mean() + joint_proba = (df[col_0] * df[col_1]).mean() + else: + marginal_0 = df[:, col_0].mean() + marginal_1 = df[:, col_1].mean() + joint_proba = (df[:, col_0] * df[:, col_1]).mean() + + factored_proba = marginal_0 * marginal_1 + return joint_proba, factored_proba + + +@pytest.mark.parametrize("distribution", ["probit", "logit"]) +@pytest.mark.parametrize("max_imbalance", [0.01, 0.05, 0.1, 0.5]) +def test_sample_binary_from_latent_imbalance(max_imbalance, distribution): + """ + Tests max imbalance argument to sample the binary variable. + This way we are guaranteed to always have some positives/negatives. + """ + # corner case: + eta = np.ones(1000) * 1000 + + sample = _sample_binary_from_latent( + latent_mean=eta, + distribution=distribution, + noise_std=0.1, + root_node=False, + max_imbalance=max_imbalance, + ) + tol = 0.01 + assert np.isclose(sample.mean(), max_imbalance, atol=0, rtol=tol) + + +@pytest.mark.parametrize("poisson_lambda", [0.1, 1, 10, 100]) +def test_sample_poisson(poisson_lambda): + """ + We test whether the first two moments match a Poisson distribution + """ + sample = _sample_poisson(np.ones(shape=10000) * poisson_lambda) + tol = 0.05 + assert np.isclose(sample.mean(), poisson_lambda, atol=0, rtol=tol) + assert np.isclose(sample.var(), poisson_lambda, atol=0, rtol=tol) + + +@pytest.mark.parametrize("poisson_lambda", [-0.5, 0.1, 1, 10, 100]) +@pytest.mark.parametrize("zero_inflation_pct", [0.0, 0.01, 0.1, 0.5, 1.0]) +def test_sample_count_from_latent_zero_inflation(poisson_lambda, zero_inflation_pct): + """ + We test whether the zero-inflation is functional using the first two moments. + """ + sample = _sample_count_from_latent( + np.ones(shape=10000) * poisson_lambda, + zero_inflation_pct=zero_inflation_pct, + root_node=False, + ) + if poisson_lambda < 0: + poisson_lambda = np.exp(poisson_lambda) + + tol = 0.1 + assert np.isclose( + sample.mean(), (1 - zero_inflation_pct) * poisson_lambda, atol=0, rtol=tol + ) + assert np.isclose( + sample.var(), + (1 + zero_inflation_pct * poisson_lambda) + * (1 - zero_inflation_pct) + * poisson_lambda, + atol=0, + rtol=tol, + ) + + +class TestCountGenerator: + @pytest.mark.parametrize( + "zero_inflation_pct", [int(0), 0.0, 0.01, 0.1, 0.5, 1.0, int(1)] + ) + def test_only_count(self, graph, zero_inflation_pct): + df = sem_generator( + graph, + default_type="count", + n_samples=1000, + distributions={"count": zero_inflation_pct}, + seed=43, + ) + # count puts a lower bound on the output: + assert np.all(df.min() >= 0) + + # zero inflation puts a lower bound on the zero-share + assert np.all((df == 0).mean() >= zero_inflation_pct) + + # How to test dependence/independence for Poisson? + + @pytest.mark.parametrize("wrong_count_zif", ["text", (0.1,), {0.1}, -0.1, 1.01]) + def test_zif_value_error(self, graph, wrong_count_zif): + """ + Test if ValueError raised for unsupported Zero-Inflation Factor for the + count data type. + """ + with pytest.raises(ValueError, match="Unsupported zero-inflation factor"): + sem_generator( + graph, + default_type="count", + distributions={"count": wrong_count_zif}, + seed=42, + ) + + +class TestNonlinearGenerator: + def test_run(self, graph, schema): + df = nonlinear_sem_generator( + graph=graph, + schema=schema, + kernel=RBF(1), + default_type="continuous", + noise_std=1.0, + n_samples=1000, + seed=13, + ) + + # test binary: + assert df[0].nunique() == 2 + assert df[2].nunique() == 2 + + # test categorical: + for col in ["1_{}".format(i) for i in range(3)]: + assert df[col].nunique() == 2 + assert len([x for x in df.columns if isinstance(x, str) and "1_" in x]) == 3 + + for col in ["5_{}".format(i) for i in range(5)]: + assert df[col].nunique() == 2 + assert len([x for x in df.columns if isinstance(x, str) and "5_" in x]) == 5 + + # test continuous + assert df[3].nunique() == 1000 + assert df[4].nunique() == 1000 diff --git a/tests/structure/test_data_generators.py b/tests/structure/data_generators/test_wrappers.py similarity index 63% rename from tests/structure/test_data_generators.py rename to tests/structure/data_generators/test_wrappers.py index 807a0ea..486db8c 100644 --- a/tests/structure/test_data_generators.py +++ b/tests/structure/data_generators/test_wrappers.py @@ -1,3 +1,4 @@ +# pylint: disable=too-many-lines # Copyright 2019-2020 QuantumBlack Visual Analytics Limited # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,28 +26,30 @@ # # See the License for the specific language governing permissions and # limitations under the License. -import operator +import re import string from itertools import product -from typing import Hashable, Tuple, Union -import networkx as nx import numpy as np import pandas as pd import pytest -from networkx.algorithms.dag import is_directed_acyclic_graph from scipy.stats import anderson, stats +from sklearn.gaussian_process.kernels import RBF +from causalnex.structure import StructureModel from causalnex.structure.data_generators import ( + gen_stationary_dyn_net_and_df, generate_binary_data, generate_binary_dataframe, generate_categorical_dataframe, generate_continuous_data, generate_continuous_dataframe, + generate_count_dataframe, + generate_dataframe_dynamic, generate_structure, - sem_generator, + generate_structure_dynamic, ) -from causalnex.structure.structuremodel import StructureModel +from tests.structure.data_generators.test_core import calculate_proba @pytest.fixture @@ -58,19 +61,6 @@ def graph(): @pytest.fixture -def schema(): - # use the default schema for 3 - schema = { - 0: "binary", - 1: "categorical:3", - 2: "binary", - 4: "continuous", - 5: "categorical:5", - } - return schema - - -@pytest.fixture() def graph_gen(): def generator(num_nodes, seed, weight=None): np.random.seed(seed) @@ -89,102 +79,6 @@ def generator(num_nodes, seed, weight=None): return generator -class TestGenerateStructure: - @pytest.mark.parametrize("graph_type", ["erdos-renyi", "barabasi-albert", "full"]) - def test_is_dag_graph_type(self, graph_type): - """ Tests that the generated graph is a dag for all graph types. """ - degree, d_nodes = 4, 10 - sm = generate_structure(d_nodes, degree, graph_type) - assert is_directed_acyclic_graph(sm) - - @pytest.mark.parametrize("num_nodes,degree", [(5, 2), (10, 3), (15, 5)]) - def test_is_dag_nodes_degrees(self, num_nodes, degree): - """ Tests that generated graph is dag for different numbers of nodes and degrees - """ - sm = generate_structure(num_nodes, degree) - assert nx.is_directed_acyclic_graph(sm) - - def test_bad_graph_type(self): - """ Test that a value other than "erdos-renyi", "barabasi-albert", "full" throws ValueError """ - graph_type = "invalid" - degree, d_nodes = 4, 10 - with pytest.raises(ValueError, match="unknown graph type"): - generate_structure(d_nodes, degree, graph_type) - - @pytest.mark.parametrize("num_nodes,degree", [(5, 2), (10, 3), (15, 5)]) - def test_expected_num_nodes(self, num_nodes, degree): - """ Test that generated structure has expected number of nodes = num_nodes """ - sm = generate_structure(num_nodes, degree) - assert len(sm.nodes) == num_nodes - - @pytest.mark.parametrize( - "num_nodes,degree,w_range", - [(5, 2, (1, 2)), (10, 3, (100, 200)), (15, 5, (1.0, 1.0))], - ) - def test_weight_range(self, num_nodes, degree, w_range): - """ Test that w_range is respected in output """ - w_min = w_range[0] - w_max = w_range[1] - sm = generate_structure(num_nodes, degree, w_min=w_min, w_max=w_max) - assert all(abs(sm[u][v]["weight"]) >= w_min for u, v in sm.edges) - assert all(abs(sm[u][v]["weight"]) <= w_max for u, v in sm.edges) - - @pytest.mark.parametrize("num_nodes", [-1, 0, 1]) - def test_num_nodes_exception(self, num_nodes): - """ Check a single node graph can't be generated """ - with pytest.raises(ValueError, match="DAG must have at least 2 nodes"): - generate_structure(num_nodes, 1) - - def test_min_max_weights_exception(self): - """ Check that w_range is valid """ - with pytest.raises( - ValueError, - match="Absolute minimum weight must be less than or equal to maximum weight", - ): - generate_structure(4, 1, w_min=0.5, w_max=0) - - def test_min_max_weights_equal(self): - """ If w_range (w, w) has w=w, check abs value of all weights respect this """ - w = 1 - sm = generate_structure(4, 1, w_min=w, w_max=w) - w_mat = nx.to_numpy_array(sm) - assert np.all((w_mat == 0) | (w_mat == w) | (w_mat == -w)) - - def test_erdos_renyi_degree_increases_edges(self): - """ Erdos-Renyi degree increases edges """ - edge_counts = [ - max( - [ - len(generate_structure(100, degree, "erdos-renyi").edges) - for _ in range(10) - ] - ) - for degree in [10, 90] - ] - - assert edge_counts == sorted(edge_counts) - - def test_barabasi_albert_degree_increases_edges(self): - """ Barabasi-Albert degree increases edges """ - edge_counts = [ - max( - [ - len(generate_structure(100, degree, "barabasi-albert").edges) - for _ in range(10) - ] - ) - for degree in [10, 90] - ] - - assert edge_counts == sorted(edge_counts) - - def test_full_network(self): - """ Fully connected network has expected edge counts """ - sm = generate_structure(40, degree=0, graph_type="full") - - assert len(sm.edges) == (40 * 39) / 2 - - class TestGenerateContinuousData: @pytest.mark.parametrize( "distribution", ["gaussian", "normal", "student-t", "exponential", "gumbel"] @@ -197,8 +91,8 @@ def test_returns_ndarray(self, distribution): assert isinstance(ndarray, np.ndarray) def test_bad_distribution_type(self): - """ Test that invalid sem-type other than "gaussian", "normal", "student-t", - "exponential", "gumbel" is not accepted """ + """Test that invalid sem-type other than "gaussian", "normal", "student-t", + "exponential", "gumbel" is not accepted""" graph_type, degree, d_nodes = "erdos-renyi", 4, 10 sm = generate_structure(d_nodes, degree, graph_type) with pytest.raises(ValueError, match="Unknown continuous distribution"): @@ -271,7 +165,8 @@ def test_linear_gumbel_parent_dist(self, graph): @pytest.mark.parametrize( "distribution", ["gaussian", "normal", "student-t", "exponential", "gumbel"] ) - def test_intercept(self, distribution): + @pytest.mark.parametrize("noise_scale", [0.0, 0.1]) + def test_intercept(self, distribution, noise_scale): graph = StructureModel() graph.add_node("123") @@ -279,7 +174,7 @@ def test_intercept(self, distribution): graph, n_samples=100000, distribution=distribution, - noise_scale=0, + noise_scale=noise_scale, seed=10, intercept=False, ) @@ -287,12 +182,12 @@ def test_intercept(self, distribution): graph, n_samples=100000, distribution=distribution, - noise_scale=0, + noise_scale=noise_scale, seed=10, intercept=True, ) assert not np.isclose(data_noint[:, 0].mean(), data_intercept[:, 0].mean()) - assert np.isclose(data_noint[:, 0].std(), data_intercept[:, 0].std()) + assert np.isclose(data_noint[:, 0].std(), data_intercept[:, 0].std(), rtol=0.01) @pytest.mark.parametrize("num_nodes", (10, 20, 30)) @pytest.mark.parametrize("seed", (10, 20, 30)) @@ -344,7 +239,8 @@ def test_order_is_correct(self, graph_gen, num_nodes, seed): @pytest.mark.parametrize("noise_std", [0.1, 1, 2]) @pytest.mark.parametrize("intercept", [True, False]) @pytest.mark.parametrize("seed", [10, 12]) - def test_dataframe(self, graph, distribution, noise_std, intercept, seed): + @pytest.mark.parametrize("kernel", [None, RBF(1)]) + def test_dataframe(self, graph, distribution, noise_std, intercept, seed, kernel): """ Tests equivalence of dataframe wrapper """ @@ -355,6 +251,7 @@ def test_dataframe(self, graph, distribution, noise_std, intercept, seed): noise_scale=noise_std, seed=seed, intercept=intercept, + kernel=kernel, ) df = generate_continuous_dataframe( graph, @@ -363,6 +260,7 @@ def test_dataframe(self, graph, distribution, noise_std, intercept, seed): noise_scale=noise_std, seed=seed, intercept=intercept, + kernel=kernel, ) assert np.array_equal(data, df[list(graph.nodes())].values) @@ -432,15 +330,26 @@ def test_intercept_probability_logit(self, graph, distribution): assert not np.isclose(mean_prob, 0.5, atol=0.05) @pytest.mark.parametrize("distribution", ["logit", "probit", "normal"]) - def test_intercept(self, distribution): + @pytest.mark.parametrize("noise_scale", [0.0, 0.1]) + def test_intercept(self, distribution, noise_scale): graph = StructureModel() graph.add_node("123") data_noint = generate_binary_data( - graph, 100000, distribution, noise_scale=0, seed=10, intercept=False + graph, + 100000, + distribution, + noise_scale=noise_scale, + seed=10, + intercept=False, ) data_intercept = generate_binary_data( - graph, 100000, distribution, noise_scale=0, seed=10, intercept=True + graph, + 100000, + distribution, + noise_scale=noise_scale, + seed=10, + intercept=True, ) assert not np.isclose(data_noint[:, 0].mean(), data_intercept[:, 0].mean()) @@ -489,7 +398,8 @@ def test_order_is_correct(self, graph_gen, num_nodes, seed): @pytest.mark.parametrize("noise_std", [0.1, 1, 2]) @pytest.mark.parametrize("intercept", [True, False]) @pytest.mark.parametrize("seed", [10, 12]) - def test_dataframe(self, graph, distribution, noise_std, intercept, seed): + @pytest.mark.parametrize("kernel", [None, RBF(1)]) + def test_dataframe(self, graph, distribution, noise_std, intercept, seed, kernel): """ Tests equivalence of dataframe wrapper """ @@ -500,6 +410,7 @@ def test_dataframe(self, graph, distribution, noise_std, intercept, seed): noise_scale=noise_std, seed=seed, intercept=intercept, + kernel=kernel, ) df = generate_binary_dataframe( graph, @@ -508,6 +419,7 @@ def test_dataframe(self, graph, distribution, noise_std, intercept, seed): noise_scale=noise_std, seed=seed, intercept=intercept, + kernel=kernel, ) assert np.array_equal(data, df[list(graph.nodes())].values) @@ -607,6 +519,51 @@ def test_baseline_probability(self, graph, distribution, n_categories): # without intercept, the probabilities should be fairly uniform assert np.allclose(data.mean(axis=0), 1 / n_categories, atol=0.01, rtol=0) + @pytest.mark.parametrize("distribution", ["logit", "probit", "normal", "gumbel"]) + @pytest.mark.parametrize("noise_std", [0.1, 1, 2]) + @pytest.mark.parametrize("intercept", [True, False]) + @pytest.mark.parametrize("seed", [10, 42]) + @pytest.mark.parametrize("kernel", [None, RBF(1)]) + @pytest.mark.parametrize( + "n_categories", + ( + 2, + 10, + ), + ) + def test_dataframe( + self, graph, distribution, noise_std, intercept, seed, kernel, n_categories + ): + """ + Tests equivalence of dataframe wrapper + """ + data = generate_categorical_dataframe( + graph, + 100, + distribution, + noise_scale=noise_std, + seed=seed, + intercept=intercept, + kernel=kernel, + n_categories=n_categories, + ) + df = generate_categorical_dataframe( + graph, + 100, + distribution, + noise_scale=noise_std, + seed=seed, + intercept=intercept, + kernel=kernel, + n_categories=n_categories, + ) + + cols = [] + for node in graph.nodes(): + for cat in range(n_categories): + cols.append("{}_{}".format(node, cat)) + assert np.array_equal(data, df[cols].values) + @pytest.mark.parametrize( "distribution,n_categories", list(product(["logit", "probit", "normal", "gumbel"], [3, 5, 7])), @@ -626,9 +583,16 @@ def test_intercept_probability(self, graph, distribution, n_categories): ) assert not np.allclose(data.mean(axis=0), 1 / n_categories, atol=0.01, rtol=0) - @pytest.mark.parametrize("n_categories", (2, 10,)) + @pytest.mark.parametrize( + "n_categories", + ( + 2, + 10, + ), + ) @pytest.mark.parametrize("distribution", ["probit", "logit"]) - def test_intercept(self, distribution, n_categories): + @pytest.mark.parametrize("noise_scale", [0.0, 0.1]) + def test_intercept(self, distribution, n_categories, noise_scale): graph = StructureModel() graph.add_node("A") @@ -636,7 +600,7 @@ def test_intercept(self, distribution, n_categories): graph, 100000, distribution, - noise_scale=0.1, + noise_scale=noise_scale, n_categories=n_categories, seed=10, intercept=False, @@ -645,21 +609,28 @@ def test_intercept(self, distribution, n_categories): graph, 100000, distribution, - noise_scale=0.1, + noise_scale=noise_scale, n_categories=n_categories, seed=10, intercept=True, ) - assert np.all( - ~np.isclose( - data_intercept.mean(axis=0), data_noint.mean(axis=0), atol=0.05, rtol=0 - ) - ) + # NOTE: as n_categories increases, the probability that at least one category with + # intercept=True will be the same as intercept=False -> 1.0 + num_similar = np.isclose( + data_intercept.mean(axis=0), data_noint.mean(axis=0), atol=0.05, rtol=0 + ).sum() + assert num_similar < n_categories / 2 @pytest.mark.parametrize("num_nodes", (3, 6)) @pytest.mark.parametrize("seed", (10, 20)) - @pytest.mark.parametrize("n_categories", (2, 6,)) + @pytest.mark.parametrize( + "n_categories", + ( + 2, + 6, + ), + ) @pytest.mark.parametrize("distribution", ["probit", "logit"]) def test_independence(self, graph_gen, seed, num_nodes, n_categories, distribution): """ @@ -692,235 +663,212 @@ def test_independence(self, graph_gen, seed, num_nodes, n_categories, distributi assert np.isclose(joint_proba, factored_proba, rtol=tol, atol=0) -class TestMixedDataGen: - def test_run(self, graph, schema): - df = sem_generator( - graph=graph, - schema=schema, - default_type="continuous", - noise_std=1.0, - n_samples=1000, - intercept=False, - seed=12, +class TestGenerateCountData: + def test_zero_lambda(self): + """ + A wrong initialisation could lead to counts always being zero if they dont + have parents. + """ + graph = StructureModel() + graph.add_nodes_from(list(range(20))) + df = generate_count_dataframe(graph, 10000) + assert not np.any(df.mean() == 0) + + @pytest.mark.parametrize("intercept", [True, False]) + @pytest.mark.parametrize("seed", [10, 12]) + @pytest.mark.parametrize("kernel", [None, RBF(1)]) + @pytest.mark.parametrize( + "zero_inflation_factor", [int(0), 0.0, 0.01, 0.1, 0.5, 1.0, int(1)] + ) + def test_dataframe(self, graph, intercept, seed, kernel, zero_inflation_factor): + """ + Tests equivalence of dataframe wrapper + """ + data = generate_count_dataframe( + graph, + 100, + zero_inflation_factor=zero_inflation_factor, + seed=seed, + intercept=intercept, + kernel=kernel, + ) + df = generate_count_dataframe( + graph, + 100, + zero_inflation_factor=zero_inflation_factor, + seed=seed, + intercept=intercept, + kernel=kernel, ) - # test binary: - assert df[0].nunique() == 2 - assert df[0].nunique() == 2 + assert np.array_equal(data, df[list(graph.nodes())].values) - # test categorical: - for col in ["1_{}".format(i) for i in range(3)]: - assert df[col].nunique() == 2 - assert len([x for x in df.columns if isinstance(x, str) and "1_" in x]) == 3 - for col in ["5_{}".format(i) for i in range(5)]: - assert df[col].nunique() == 2 - assert len([x for x in df.columns if isinstance(x, str) and "5_" in x]) == 5 +class TestGenerateStructureDynamic: + @pytest.mark.parametrize("num_nodes", (10, 20)) + @pytest.mark.parametrize("p", [1, 10]) + @pytest.mark.parametrize("degree_intra, degree_inter", [(3, 0), (0, 3), (1, 1)]) + def test_all_nodes_in_structure(self, num_nodes, p, degree_intra, degree_inter): + """both intra- and iter-slice nodes should be in the structure""" + g = generate_structure_dynamic(num_nodes, p, degree_intra, degree_inter) + assert np.all( + [ + "{var}_lag{l_val}".format(var=var, l_val=l_val) in g.nodes + for l_val in range(p + 1) + for var in range(num_nodes) + ] + ) + + def test_naming_nodes(self): + """Nodes should have the format {var}_lag{l}""" + g = generate_structure_dynamic(5, 3, 3, 4) + pattern = re.compile(r"[0-5]_lag[0-3]") + for node in g.nodes: + match = pattern.match(node) + assert match and (match.group() == node) + + def test_degree_zero_implies_no_edges(self): + """If the degree is zero, zero edges are generated. + We test this is true for intra edges (ending in 'lag0') and inter edges + """ + g = generate_structure_dynamic(15, 3, 0, 4) # No intra edges + lags = [(u.split("_lag")[1], v.split("_lag")[1]) for u, v in g.edges] + assert np.all([el[0] != "0" for el in lags]) + g = generate_structure_dynamic(15, 3, 4, 0) + lags = [(u.split("_lag")[1], v.split("_lag")[1]) for u, v in g.edges] + assert np.all([el == ("0", "0") for el in lags]) # only Intra edges + g = generate_structure_dynamic(15, 3, 0, 0) # no edges + assert len(g.edges) == 0 + + def test_edges_have_weights(self): + """all edges must have weight values as floats or int""" + g = generate_structure_dynamic(10, 3, 4, 4) # No intra edges + ws = [w for _, _, w in g.edges(data="weight")] + assert np.all([isinstance(w, (float, int)) for w in ws]) + + def test_raise_error_if_wrong_graph_type(self): + """if the graph_type chosen is not among the options available, raise error""" + with pytest.raises( + ValueError, + match=r"Unknown graph type some_type\. " + r"Available types are \['erdos-renyi', 'barabasi-albert', 'full'\]", + ): + generate_structure_dynamic(10, 10, 10, 10, graph_type_intra="some_type") + with pytest.raises( + ValueError, + match=r"Unknown inter-slice graph type `some_type`\. " + "Valid types are 'erdos-renyi' and 'full'", + ): + generate_structure_dynamic(10, 10, 10, 10, graph_type_inter="some_type") - # test continuous - assert df[3].nunique() == 1000 - assert df[4].nunique() == 1000 + def test_raise_error_if_min_greater_than_max(self): + """if min > max,raise error""" + with pytest.raises( + ValueError, + match="Absolute minimum weight must be " + r"less than or equal to maximum weight\: 3 \> 2", + ): + generate_structure_dynamic(10, 10, 10, 10, w_min_inter=3, w_max_inter=2) - def test_graph_not_a_dag(self): - graph = StructureModel() - graph.add_edges_from([(0, 1), (1, 2), (2, 0)]) - - with pytest.raises(ValueError, match="Provided graph is not a DAG"): - _ = sem_generator(graph=graph) - - def test_not_permissible_type(self, graph): - schema = { - 0: "unknown data type", - } - with pytest.raises(ValueError, match="Unknown data type"): - _ = sem_generator( - graph=graph, - schema=schema, - default_type="continuous", - noise_std=1.0, - n_samples=1000, - intercept=False, - seed=12, - ) + @pytest.mark.parametrize("num_nodes", (10, 20)) + @pytest.mark.parametrize("p", [1, 10]) + def test_full_graph_type(self, num_nodes, p): + """all the connections from past variables to current variables should be there if using `full` graph_type""" + g = generate_structure_dynamic(num_nodes, p, 4, 4, graph_type_inter="full") + lagged_edges = sorted((u, v) for u, v in g.edges if int(u.split("_lag")[1]) > 0) + assert lagged_edges == sorted( + ("{v}_lag{l_}".format(v=v_f, l_=l_), "{v}_lag0".format(v=v_t)) + for l_ in range(1, p + 1) + for v_f in range(num_nodes) # var from + for v_t in range(num_nodes) # var to + ) - def test_missing_cardinality(self, graph): - schema = { - 0: "categorical", - 1: "categorical:3", - 5: "categorical:5", - } - with pytest.raises(ValueError, match="Missing cardinality for categorical"): - _ = sem_generator( - graph=graph, - schema=schema, - default_type="continuous", - noise_std=1.0, - n_samples=1000, - intercept=False, - seed=12, - ) - def test_missing_default_type(self, graph): - with pytest.raises(ValueError, match="Unknown default data type"): - _ = sem_generator( - graph=graph, - schema=schema, - default_type="unknown", - noise_std=1.0, - n_samples=1000, - intercept=False, - seed=12, - ) +class TestGenerateDataframeDynamic: + @pytest.mark.parametrize( + "sem_type", ["linear-gauss", "linear-exp", "linear-gumbel"] + ) + def test_returns_dateframe(self, sem_type): + """ Return value is an ndarray - test over all sem_types """ + graph_type, degree, d_nodes = "erdos-renyi", 4, 10 + sm = generate_structure_dynamic(d_nodes, 2, degree, degree, graph_type) + data = generate_dataframe_dynamic(sm, sem_type=sem_type, n_samples=10) + assert isinstance(data, pd.DataFrame) - def test_incorrect_weight_dist(self): - sm = StructureModel() - nodes = list(str(x) for x in range(6)) - np.random.shuffle(nodes) - sm.add_nodes_from(nodes) + def test_bad_sem_type(self): + """ Test that invalid sem-type other than "linear-gauss", "linear-exp", "linear-gumbel" is not accepted """ + graph_type, degree, d_nodes = "erdos-renyi", 4, 10 + sm = generate_structure_dynamic(d_nodes, 2, degree, degree, graph_type) + with pytest.raises( + ValueError, + match="unknown sem type invalid. Available types are:" + r" \('linear-gauss', 'linear-exp', 'linear-gumbel'\)", + ): + generate_dataframe_dynamic(sm, sem_type="invalid", n_samples=10) - sm.add_weighted_edges_from([("0", "1", None), ("2", "4", None)]) - - with pytest.raises(ValueError, match="Unknown weight distribution"): - _ = sem_generator( - graph=sm, - schema=None, - default_type="continuous", - distributions={"weight": "unknown"}, - noise_std=2.0, - n_samples=1000, - intercept=False, - seed=10, - ) + @pytest.mark.parametrize("p", [0, 1, 2]) + def test_labels_correct(self, p): + graph_type, degree, d_nodes = "erdos-renyi", 4, 10 + sm = generate_structure_dynamic(d_nodes, p, degree, degree, graph_type) + data = generate_dataframe_dynamic(sm, sem_type="linear-gauss", n_samples=10) + intra_nodes = sorted([el for el in sm.nodes if "_lag0" in el]) + inter_nodes = sorted([el for el in sm.nodes if "_lag0" not in el]) + assert sorted(data.columns) == sorted(list(inter_nodes) + list(intra_nodes)) - def test_incorrect_intercept_dist(self, graph): - with pytest.raises(ValueError, match="Unknown intercept distribution"): - _ = sem_generator( - graph=graph, - schema=None, - default_type="continuous", - distributions={"intercept": "unknown"}, - noise_std=2.0, - n_samples=10, - intercept=True, - seed=10, + +class TestGenerateStationaryDynamicStructureAndSamples: + def test_wta(self): + with pytest.warns( + UserWarning, match="Could not simulate data, returning constant dataframe" + ): + gen_stationary_dyn_net_and_df( + w_min_inter=1, w_max_inter=2, max_data_gen_trials=2 ) - # def test_mixed_type_independence(self): - @pytest.mark.parametrize("seed", (10, 20)) - @pytest.mark.parametrize("n_categories", (2, 5,)) - @pytest.mark.parametrize("weight_distribution", ["uniform", "gaussian"]) - @pytest.mark.parametrize("intercept_distribution", ["uniform", "gaussian"]) - def test_mixed_type_independence( - self, seed, n_categories, weight_distribution, intercept_distribution - ): - """ - Test whether the relation is accurate, implicitly tests sequence of - nodes. - """ + @pytest.mark.parametrize("seed", [2, 3, 5]) + def test_seems_stationary(self, seed): np.random.seed(seed) - - sm = StructureModel() - nodes = list(str(x) for x in range(6)) - np.random.shuffle(nodes) - sm.add_nodes_from(nodes) - # binary -> categorical - sm.add_weighted_edges_from([("0", "1", 10)]) - # binary -> continuous - sm.add_weighted_edges_from([("2", "4", None)]) - - schema = { - "0": "binary", - "1": "categorical:{}".format(n_categories), - "2": "binary", - "4": "continuous", - "5": "categorical:{}".format(n_categories), - } - - df = sem_generator( - graph=sm, - schema=schema, - default_type="continuous", - distributions={ - "weight": weight_distribution, - "intercept": intercept_distribution, - }, - noise_std=2, - n_samples=100000, - intercept=True, - seed=seed, + _, df, _, _ = gen_stationary_dyn_net_and_df( + w_min_inter=0.1, w_max_inter=0.2, max_data_gen_trials=2 ) + assert np.all(df.max() - df.min() < 10) - atol = 0.05 # 5% difference bewteen joint & factored! - # 1. dependent links - # 0 -> 1 (we look at the class with the highest deviation from uniform - # to avoid small values) - c, _ = max( - [ - (c, np.abs(df["1_{}".format(c)].mean() - 1 / n_categories)) - for c in range(n_categories) - ], - key=operator.itemgetter(1), - ) - joint_proba, factored_proba = calculate_proba(df, "0", "1_{}".format(c)) - assert not np.isclose(joint_proba, factored_proba, rtol=0, atol=atol) - # 2 -> 4 - assert not np.isclose( - df["4"].mean(), df["4"][df["2"] == 1].mean(), rtol=0, atol=atol - ) - - tol = 0.15 # relative tolerance of +- 15% of the - # 2. independent links - # categorical - c, _ = max( - [ - (c, np.abs(df["1_{}".format(c)].mean() - 1 / n_categories)) - for c in range(n_categories) - ], - key=operator.itemgetter(1), - ) - joint_proba, factored_proba = calculate_proba(df, "0", "5_{}".format(c)) - assert np.isclose(joint_proba, factored_proba, rtol=tol, atol=0) + def test_error_if_wmin_less_wmax(self): + with pytest.raises( + ValueError, + match="Absolute minimum weight must be less than or equal to maximum weight: 2 > 1", + ): + gen_stationary_dyn_net_and_df( + w_min_inter=2, w_max_inter=1, max_data_gen_trials=2 + ) - # binary - joint_proba, factored_proba = calculate_proba(df, "0", "2") - assert np.isclose(joint_proba, factored_proba, rtol=tol, atol=0) + def test_dense_networks(self): + """dense network are more likely to be non stationary. we check that the simulator is still able to provide a + stationary time-deries in that case. - # categorical - c, _ = max( - [ - (c, np.abs(df["1_{}".format(c)].mean() - 1 / n_categories)) - for c in range(n_categories) - ], - key=operator.itemgetter(1), - ) - d, _ = max( - [ - (d, np.abs(df["5_{}".format(d)].mean() - 1 / n_categories)) - for d in range(n_categories) - ], - key=operator.itemgetter(1), - ) - joint_proba, factored_proba = calculate_proba( - df, "1_{}".format(d), "5_{}".format(c) - ) - assert np.isclose(joint_proba, factored_proba, rtol=tol, atol=0) - - # continuous - # for gaussian distributions, zero variance is equivalent to independence - assert np.isclose(df[["3", "4"]].corr().values[0, 1], 0, atol=tol) - - -def calculate_proba( - df: Union[pd.DataFrame, np.ndarray], col_0: Hashable, col_1: Hashable -) -> Tuple[float, float]: - if isinstance(df, pd.DataFrame): - marginal_0 = df[col_0].mean() - marginal_1 = df[col_1].mean() - joint_proba = (df[col_0] * df[col_1]).mean() - else: - marginal_0 = df[:, col_0].mean() - marginal_1 = df[:, col_1].mean() - joint_proba = (df[:, col_0] * df[:, col_1]).mean() - - factored_proba = marginal_0 * marginal_1 - return joint_proba, factored_proba + If df contain only ones it means that the generator failed to obtain a stationary structure""" + np.random.seed(4) + _, df, _, _ = gen_stationary_dyn_net_and_df( + n_samples=1000, + p=1, + w_min_inter=0.2, + w_max_inter=0.5, + max_data_gen_trials=10, + degree_intra=4, + degree_inter=7, + ) + assert np.any(np.ones(df.shape) != df) + + def test_fail_to_find_stationary_network(self): + """if fails to find suitable network, returns dataset of ones""" + np.random.seed(5) + _, df, _, _ = gen_stationary_dyn_net_and_df( + n_samples=1000, + p=1, + w_min_inter=0.6, + w_max_inter=0.6, + max_data_gen_trials=20, + degree_intra=4, + degree_inter=7, + ) + assert np.any(np.ones(df.shape) == df) diff --git a/tests/structure/test_dynotears.py b/tests/structure/test_dynotears.py new file mode 100644 index 0000000..bfb8ceb --- /dev/null +++ b/tests/structure/test_dynotears.py @@ -0,0 +1,738 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. +import re + +import networkx as nx +import numpy as np +import pandas as pd +import pytest + +from causalnex.structure.dynotears import from_numpy_dynamic, from_pandas_dynamic + + +class TestFromNumpyDynotears: + """Test behaviour of the learn_dynamic_structure of `from_numpy_dynamic`""" + + def test_empty_data_raises_error(self): + """ + Providing an empty data set should result in a Value Error explaining that data must not be empty. + This error is useful to catch and handle gracefully, because otherwise the user would experience + misleading division by zero, or unpacking errors. + """ + + with pytest.raises( + ValueError, match="Input data X is empty, cannot learn any structure" + ): + from_numpy_dynamic(np.empty([0, 5]), np.zeros([5, 5])) + with pytest.raises( + ValueError, match="Input data Xlags is empty, cannot learn any structure" + ): + from_numpy_dynamic(np.zeros([5, 5]), np.empty([0, 5])) + + def test_nrows_data_mismatch_raises_error(self): + """ + Providing input data and lagged data with different number of rows should result in a Value Error. + """ + + with pytest.raises( + ValueError, match="Input data X and Xlags must have the same number of rows" + ): + from_numpy_dynamic(np.zeros([5, 5]), np.zeros([6, 5])) + + def test_ncols_lagged_data_not_multiple_raises_error(self): + """ + Number of columns of lagged data is not a multiple of those of input data should result in a Value Error. + """ + + with pytest.raises( + ValueError, + match="Number of columns of Xlags must be a multiple of number of columns of X", + ): + from_numpy_dynamic(np.zeros([5, 5]), np.zeros([5, 6])) + + def test_single_iter_gets_converged_fail_warnings(self, data_dynotears_p1): + """ + With a single iteration on this dataset, learn_structure fails to converge and should give warnings. + """ + + with pytest.warns( + UserWarning, match=r"Failed to converge\. Consider increasing max_iter." + ): + from_numpy_dynamic( + data_dynotears_p1["X"], data_dynotears_p1["Y"], max_iter=1 + ) + + def test_naming_nodes(self, data_dynotears_p3): + """ + Nodes should have the format {var}_lag{l} + """ + sm = from_numpy_dynamic(data_dynotears_p3["X"], data_dynotears_p3["Y"]) + pattern = re.compile(r"[0-5]_lag[0-3]") + + for node in sm.nodes: + match = pattern.match(node) + assert match + assert match.group() == node + + def test_inter_edges(self, data_dynotears_p3): + """ + inter-slice edges must be {var}_lag{l} -> {var'}_lag0 , l > 0 + """ + + sm = from_numpy_dynamic(data_dynotears_p3["X"], data_dynotears_p3["Y"]) + + for start, end in sm.edges: + if int(start[-1]) > 0: + assert int(end[-1]) == 0 + + def test_expected_structure_learned_p1(self, data_dynotears_p1): + """ + Given a small data set with p=1, find all the intra-slice edges and the majority of the inter-slice ones + """ + + sm = from_numpy_dynamic( + data_dynotears_p1["X"], data_dynotears_p1["Y"], w_threshold=0.2 + ) + w_edges = [ + ("{i}_lag0".format(i=i), "{j}_lag0".format(j=j)) + for i in range(5) + for j in range(5) + if data_dynotears_p1["W"][i, j] != 0 + ] + a_edges = [ + ("{i_1}_lag{i_2}".format(i_1=i % 5, i_2=1 + i // 5), "{j}_lag0".format(j=j)) + for i in range(5) + for j in range(5) + if data_dynotears_p1["A"][i, j] != 0 + ] + + edges_in_sm_and_a = [el for el in sm.edges if el in a_edges] + sm_inter_edges = [el for el in sm.edges if "lag0" not in el[0]] + + assert sorted([el for el in sm.edges if "lag0" in el[0]]) == sorted(w_edges) + assert len(edges_in_sm_and_a) / len(a_edges) > 0.6 + assert len(edges_in_sm_and_a) / len(sm_inter_edges) > 0.9 + + def test_expected_structure_learned_p2(self, data_dynotears_p2): + """ + Given a small data set with p=2, all the intra-slice must be correct, and 90%+ found. + the majority of the inter edges must be found too + """ + + sm = from_numpy_dynamic( + data_dynotears_p2["X"], data_dynotears_p2["Y"], w_threshold=0.25 + ) + w_edges = [ + ("{i}_lag0".format(i=i), "{j}_lag0".format(j=j)) + for i in range(5) + for j in range(5) + if data_dynotears_p2["W"][i, j] != 0 + ] + a_edges = [ + ("{i_1}_lag{i_2}".format(i_1=i % 5, i_2=1 + i // 5), "{j}_lag0".format(j=j)) + for i in range(5) + for j in range(5) + if data_dynotears_p2["A"][i, j] != 0 + ] + + edges_in_sm_and_a = [el for el in sm.edges if el in a_edges] + sm_inter_edges = [el for el in sm.edges if "lag0" not in el[0]] + sm_intra_edges = [el for el in sm.edges if "lag0" in el[0]] + + assert len([el for el in sm_intra_edges if el not in w_edges]) == 0 + assert ( + len([el for el in w_edges if el not in sm_intra_edges]) / len(w_edges) + <= 1.0 + ) + assert len(edges_in_sm_and_a) / len(a_edges) > 0.5 + assert len(edges_in_sm_and_a) / len(sm_inter_edges) > 0.5 + + def test_tabu_parents(self, data_dynotears_p2): + """ + If tabu relationships are set, the corresponding edges must not exist + """ + + sm = from_numpy_dynamic( + data_dynotears_p2["X"], + data_dynotears_p2["Y"], + tabu_parent_nodes=[1], + ) + assert not [el for el in sm.edges if "1_lag" in el[0]] + + def test_tabu_children(self, data_dynotears_p2): + """ + If tabu relationships are set, the corresponding edges must not exist + """ + sm = from_numpy_dynamic( + data_dynotears_p2["X"], + data_dynotears_p2["Y"], + tabu_child_nodes=[4], + ) + assert not ([el for el in sm.edges if "4_lag" in el[1]]) + + sm = from_numpy_dynamic( + data_dynotears_p2["X"], + data_dynotears_p2["Y"], + tabu_child_nodes=[1], + ) + assert not ([el for el in sm.edges if "1_lag" in el[1]]) + + def test_tabu_edges(self, data_dynotears_p2): + """ + Tabu edges must not be in the edges learnt + """ + sm = from_numpy_dynamic( + data_dynotears_p2["X"], + data_dynotears_p2["Y"], + tabu_edges=[(0, 2, 4), (0, 0, 3), (1, 1, 4), (1, 3, 4)], + ) + + assert ("2_lag0", "4_lag0") not in sm.edges + assert ("0_lag0", "3_lag0") not in sm.edges + assert ("1_lag1", "4_lag0") not in sm.edges + assert ("3_lag1", "4_lag0") not in sm.edges + + def test_multiple_tabu(self, data_dynotears_p2): + """ + If tabu relationships are set, the corresponding edges must not exist + """ + sm = from_numpy_dynamic( + data_dynotears_p2["X"], + data_dynotears_p2["Y"], + tabu_edges=[(0, 1, 4), (0, 0, 3), (1, 1, 4), (1, 3, 4)], + tabu_child_nodes=[0, 1], + tabu_parent_nodes=[3], + ) + + assert ("1_lag0", "4_lag0") not in sm.edges + assert ("0_lag0", "3_lag0") not in sm.edges + assert ("1_lag1", "4_lag0") not in sm.edges + assert ("3_lag1", "4_lag0") not in sm.edges + assert not ([el for el in sm.edges if "0_lag" in el[1]]) + assert not ([el for el in sm.edges if "1_lag" in el[1]]) + assert not ([el for el in sm.edges if "3_lag" in el[0]]) + + def test_all_columns_in_structure(self, data_dynotears_p2): + """Every columns that is in the data should become a node in the learned structure""" + sm = from_numpy_dynamic( + data_dynotears_p2["X"], + data_dynotears_p2["Y"], + ) + assert sorted(sm.nodes) == [ + "{var}_lag{l_val}".format(var=var, l_val=l_val) + for var in range(5) + for l_val in range(3) + ] + + def test_isolated_nodes_exist(self, data_dynotears_p2): + """Isolated nodes should still be in the learned structure""" + sm = from_numpy_dynamic( + data_dynotears_p2["X"], data_dynotears_p2["Y"], w_threshold=1 + ) + assert len(sm.edges) == 2 + assert len(sm.nodes) == 15 + + def test_edges_contain_weight(self, data_dynotears_p2): + """Edges must contain the 'weight' from the adjacent table """ + sm = from_numpy_dynamic(data_dynotears_p2["X"], data_dynotears_p2["Y"]) + assert np.all( + [ + isinstance(w, (float, int, np.number)) + for u, v, w in sm.edges(data="weight") + ] + ) + + def test_certain_relationships_get_near_certain_weight(self): + """If a == b always, ther should be an edge a->b or b->a with coefficient close to one """ + + np.random.seed(17) + data = pd.DataFrame( + [[np.sqrt(el), np.sqrt(el)] for el in np.random.choice(100, size=500)], + columns=["a", "b"], + ) + sm = from_numpy_dynamic(data.values[1:], data.values[:-1], w_threshold=0.1) + edge = ( + sm.get_edge_data("1_lag0", "0_lag0") or sm.get_edge_data("0_lag0", "1_lag0") + )["weight"] + + assert 0.99 < edge <= 1.01 + + def test_inverse_relationships_get_negative_weight(self): + """If a == -b always, ther should be an edge a->b or b->a with coefficient close to minus one """ + + np.random.seed(17) + data = pd.DataFrame( + [[el, -el] for el in np.random.choice(100, size=500)], columns=["a", "b"] + ) + sm = from_numpy_dynamic(data.values[1:], data.values[:-1], w_threshold=0.1) + edge = ( + sm.get_edge_data("1_lag0", "0_lag0") or sm.get_edge_data("0_lag0", "1_lag0") + )["weight"] + assert -1.01 < edge <= -0.99 + + def test_no_cycles(self, data_dynotears_p2): + """ + The learned structure should be acyclic + """ + + sm = from_numpy_dynamic( + data_dynotears_p2["X"], data_dynotears_p2["Y"], w_threshold=0.05 + ) + assert nx.algorithms.is_directed_acyclic_graph(sm) + + def test_tabu_edges_on_non_existing_edges_do_nothing(self, data_dynotears_p2): + """If tabu edges do not exist in the original unconstrained network then nothing changes""" + sm = from_numpy_dynamic( + data_dynotears_p2["X"], data_dynotears_p2["Y"], w_threshold=0.2 + ) + + sm_2 = from_numpy_dynamic( + data_dynotears_p2["X"], + data_dynotears_p2["Y"], + w_threshold=0.2, + tabu_edges=[(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3)], + ) + assert set(sm_2.edges) == set(sm.edges) + + +class TestFromPandasDynotears: + """Test behaviour of the learn_dynamic_structure of `from_pandas_dynamic`""" + + def test_empty_data_raises_error(self): + """ + Providing an empty data set should result in a Value Error explaining that data must not be empty. + This error is useful to catch and handle gracefully, because otherwise the user would experience + misleading division by zero, or unpacking errors. + """ + with pytest.raises( + ValueError, match="Input data X is empty, cannot learn any structure" + ): + from_pandas_dynamic(pd.DataFrame(np.empty([2, 5])), p=2) + + with pytest.raises( + ValueError, match="Input data X is empty, cannot learn any structure" + ): + from_pandas_dynamic(pd.DataFrame(np.empty([1, 5])), p=1) + + with pytest.raises( + ValueError, match="Input data X is empty, cannot learn any structure" + ): + from_pandas_dynamic(pd.DataFrame(np.empty([0, 5])), p=1) + + def test_single_iter_gets_converged_fail_warnings(self, data_dynotears_p1): + """ + With a single iteration on this dataset, learn_structure fails to converge and should give warnings. + """ + + with pytest.warns( + UserWarning, match=r"Failed to converge\. Consider increasing max_iter." + ): + from_pandas_dynamic(pd.DataFrame(data_dynotears_p1["X"]), p=1, max_iter=1) + + def test_naming_nodes(self, data_dynotears_p3): + """ + Nodes should have the format {var}_lag{l} + """ + sm = from_pandas_dynamic( + pd.DataFrame(data_dynotears_p3["X"], columns=["a", "b", "c", "d", "e"]), + p=3, + ) + pattern = re.compile(r"[abcde]_lag[0-3]") + + for node in sm.nodes: + match = pattern.match(node) + assert match + assert match.group() == node + + def test_inter_edges(self, data_dynotears_p3): + """ + inter-slice edges must be {var}_lag{l} -> {var'}_lag0 , l > 0 + """ + + sm = from_pandas_dynamic( + pd.DataFrame(data_dynotears_p3["X"], columns=["a", "b", "c", "d", "e"]), + p=3, + ) + for start, end in sm.edges: + if int(start[-1]) > 0: + assert int(end[-1]) == 0 + + def test_expected_structure_learned_p1(self, data_dynotears_p1): + """ + Given a small data set with p=1, find all the intra-slice edges and the majority of the inter-slice ones + """ + df = pd.DataFrame(data_dynotears_p1["X"], columns=["a", "b", "c", "d", "e"]) + df.loc[-1, :] = data_dynotears_p1["Y"][0, :] + df = df.sort_index() + + sm = from_pandas_dynamic( + df, + p=1, + w_threshold=0.2, + ) + map_ = dict(zip(range(5), ["a", "b", "c", "d", "e"])) + w_edges = [ + ("{i}_lag0".format(i=map_[i]), "{j}_lag0".format(j=map_[j])) + for i in range(5) + for j in range(5) + if data_dynotears_p1["W"][i, j] != 0 + ] + a_edges = [ + ( + "{i_1}_lag{i_2}".format(i_1=map_[i % 5], i_2=1 + i // 5), + "{j}_lag0".format(j=map_[j]), + ) + for i in range(5) + for j in range(5) + if data_dynotears_p1["A"][i, j] != 0 + ] + + edges_in_sm_and_a = [el for el in sm.edges if el in a_edges] + sm_inter_edges = [el for el in sm.edges if "lag0" not in el[0]] + assert sorted(el for el in sm.edges if "lag0" in el[0]) == sorted(w_edges) + assert len(edges_in_sm_and_a) / len(a_edges) > 0.6 + assert len(edges_in_sm_and_a) / len(sm_inter_edges) > 0.9 + + def test_expected_structure_learned_p2(self, data_dynotears_p2): + """ + Given a small data set with p=2, all the intra-slice must be correct, and 90%+ found. + the majority of the inter edges must be found too + """ + df = pd.DataFrame(data_dynotears_p2["X"], columns=["a", "b", "c", "d", "e"]) + df.loc[-1, :] = data_dynotears_p2["Y"][0, :5] + df.loc[-2, :] = data_dynotears_p2["Y"][0, 5:10] + + df = df.sort_index() + + sm = from_pandas_dynamic( + df, + p=2, + w_threshold=0.25, + ) + map_ = dict(zip(range(5), ["a", "b", "c", "d", "e"])) + w_edges = [ + ("{i}_lag0".format(i=map_[i]), "{j}_lag0".format(j=map_[j])) + for i in range(5) + for j in range(5) + if data_dynotears_p2["W"][i, j] != 0 + ] + a_edges = [ + ( + "{i_1}_lag{i_2}".format(i_1=map_[i % 5], i_2=1 + i // 5), + "{j}_lag0".format(j=map_[j]), + ) + for i in range(5) + for j in range(5) + if data_dynotears_p2["A"][i, j] != 0 + ] + + edges_in_sm_and_a = [el for el in sm.edges if el in a_edges] + sm_inter_edges = [el for el in sm.edges if "lag0" not in el[0]] + sm_intra_edges = [el for el in sm.edges if "lag0" in el[0]] + + assert len([el for el in sm_intra_edges if el not in w_edges]) == 0 + assert ( + len([el for el in w_edges if el not in sm_intra_edges]) / len(w_edges) + <= 1.0 + ) + assert len(edges_in_sm_and_a) / len(a_edges) > 0.5 + assert len(edges_in_sm_and_a) / len(sm_inter_edges) > 0.5 + + def test_tabu_parents(self, data_dynotears_p3): + """ + If tabu relationships are set, the corresponding edges must not exist + """ + + sm = from_pandas_dynamic( + pd.DataFrame(data_dynotears_p3["X"], columns=["a", "b", "c", "d", "e"]), + p=3, + tabu_parent_nodes=["a"], + ) + assert not [el for el in sm.edges if "a_lag" in el[0]] + + def test_tabu_children(self, data_dynotears_p3): + """ + If tabu relationships are set, the corresponding edges must not exist + """ + sm = from_pandas_dynamic( + pd.DataFrame(data_dynotears_p3["X"], columns=["a", "b", "c", "d", "e"]), + p=3, + tabu_child_nodes=["c", "d"], + ) + assert not ([el for el in sm.edges if "c_lag" in el[1]]) + assert not ([el for el in sm.edges if "d_lag" in el[1]]) + sm = from_pandas_dynamic( + pd.DataFrame(data_dynotears_p3["X"], columns=["a", "b", "c", "d", "e"]), + p=3, + tabu_child_nodes=["a", "b"], + ) + assert not ([el for el in sm.edges if "a_lag" in el[1]]) + assert not ([el for el in sm.edges if "b_lag" in el[1]]) + + def test_tabu_edges(self, data_dynotears_p3): + """ + Tabu edges must not be in the edges learnt + """ + sm = from_pandas_dynamic( + pd.DataFrame(data_dynotears_p3["X"], columns=["a", "b", "c", "d", "e"]), + p=3, + tabu_edges=[(0, "c", "e"), (0, "a", "d"), (1, "b", "e"), (1, "d", "e")], + ) + + assert ("c_lag0", "e_lag0") not in sm.edges + assert ("a_lag0", "d_lag0") not in sm.edges + assert ("b_lag1", "e_lag0") not in sm.edges + assert ("d_lag1", "e_lag0") not in sm.edges + + def test_multiple_tabu(self, data_dynotears_p3): + """ + If tabu relationships are set, the corresponding edges must not exist + """ + sm = from_pandas_dynamic( + pd.DataFrame(data_dynotears_p3["X"], columns=["a", "b", "c", "d", "e"]), + p=3, + tabu_edges=[(0, "a", "e"), (0, "a", "d"), (1, "b", "e"), (1, "d", "e")], + tabu_child_nodes=["a", "b"], + tabu_parent_nodes=["d"], + ) + + assert ("a_lag0", "e_lag0") not in sm.edges + assert ("a_lag0", "d_lag0") not in sm.edges + assert ("b_lag1", "e_lag0") not in sm.edges + assert ("d_lag1", "e_lag0") not in sm.edges + assert not ([el for el in sm.edges if "a_lag" in el[1]]) + assert not ([el for el in sm.edges if "b_lag" in el[1]]) + assert not ([el for el in sm.edges if "d_lag" in el[0]]) + + def test_all_columns_in_structure(self, data_dynotears_p2): + """Every columns that is in the data should become a node in the learned structure""" + sm = from_pandas_dynamic( + pd.DataFrame(data_dynotears_p2["X"], columns=["a", "b", "c", "d", "e"]), + p=2, + w_threshold=0.4, + ) + assert sorted(sm.nodes) == [ + "{var}_lag{l_val}".format(var=var, l_val=l_val) + for var in ["a", "b", "c", "d", "e"] + for l_val in range(3) + ] + + def test_isolated_nodes_exist(self, data_dynotears_p2): + """Isolated nodes should still be in the learned structure""" + df = pd.DataFrame(data_dynotears_p2["X"], columns=["a", "b", "c", "d", "e"]) + df.loc[-1, :] = data_dynotears_p2["Y"][0, :5] + df.loc[-2, :] = data_dynotears_p2["Y"][0, 5:10] + df = df.sort_index() + + sm = from_pandas_dynamic(df, p=2, w_threshold=1) + assert len(sm.edges) == 2 + assert len(sm.nodes) == 15 + + def test_edges_contain_weight(self, data_dynotears_p3): + """Edges must contain the 'weight' from the adjacent table """ + sm = from_pandas_dynamic( + pd.DataFrame(data_dynotears_p3["X"], columns=["a", "b", "c", "d", "e"]), + p=3, + ) + assert np.all( + [ + isinstance(w, (float, int, np.number)) + for u, v, w in sm.edges(data="weight") + ] + ) + + def test_certain_relationships_get_near_certain_weight(self): + """If a == b always, ther should be an edge a->b or b->a with coefficient close to one """ + + np.random.seed(17) + data = pd.DataFrame( + [[np.sqrt(el), np.sqrt(el)] for el in np.random.choice(100, size=500)], + columns=["a", "b"], + ) + sm = from_pandas_dynamic(data, p=1, w_threshold=0.1) + edge = ( + sm.get_edge_data("b_lag0", "a_lag0") or sm.get_edge_data("a_lag0", "b_lag0") + )["weight"] + + assert 0.99 < edge <= 1.01 + + def test_inverse_relationships_get_negative_weight(self): + """If a == -b always, there should be an edge a->b or b->a with coefficient close to minus one """ + + np.random.seed(17) + data = pd.DataFrame( + [[el, -el] for el in np.random.choice(100, size=500)], columns=["a", "b"] + ) + sm = from_pandas_dynamic(data, p=1, w_threshold=0.1) + edge = ( + sm.get_edge_data("b_lag0", "a_lag0") or sm.get_edge_data("a_lag0", "b_lag0") + )["weight"] + assert -1.01 < edge <= -0.99 + + def test_no_cycles(self, data_dynotears_p2): + """ + The learned structure should be acyclic + """ + sm = from_pandas_dynamic( + pd.DataFrame(data_dynotears_p2["X"], columns=["a", "b", "c", "d", "e"]), + p=2, + w_threshold=0.05, + ) + assert nx.algorithms.is_directed_acyclic_graph(sm) + + def test_tabu_edges_on_non_existing_edges_do_nothing(self, data_dynotears_p2): + """If tabu edges do not exist in the original unconstrained network then nothing changes""" + df = pd.DataFrame(data_dynotears_p2["X"], columns=["a", "b", "c", "d", "e"]) + df.loc[-1, :] = data_dynotears_p2["Y"][0, :5] + df.loc[-2, :] = data_dynotears_p2["Y"][0, 5:10] + df = df.sort_index() + + sm = from_pandas_dynamic( + df, + p=2, + w_threshold=0.2, + ) + sm_2 = from_pandas_dynamic( + df, + p=2, + w_threshold=0.2, + tabu_edges=[(0, "a", "a"), (0, "a", "b"), (0, "a", "c"), (0, "a", "d")], + ) + assert set(sm_2.edges) == set(sm.edges) + + def test_list_of_dfs_as_input(self, data_dynotears_p2): + """ + the result when given a list of dataframes should be the same as a single dataframe. + Also, stacking two dataframes should give the same result as well + """ + df = pd.DataFrame(data_dynotears_p2["X"], columns=["a", "b", "c", "d", "e"]) + df.loc[-1, :] = data_dynotears_p2["Y"][0, :5] + df.loc[-2, :] = data_dynotears_p2["Y"][0, 5:10] + + df = df.sort_index() + df_ = df.copy() + df_.index = range(100, 152) + df = pd.concat([df, df_]) + sm = from_pandas_dynamic(df, p=2, w_threshold=0.05) + sm_1 = from_pandas_dynamic([df], p=2, w_threshold=0.05) + sm_2 = from_pandas_dynamic([df, df], p=2, w_threshold=0.05) + + assert list(sm_2.edges) == list(sm_1.edges) + assert list(sm.edges) == list(sm_1.edges) + + weights = np.array([w for _, _, w in sm.edges(data="weight")]) + weights_1 = np.array([w for _, _, w in sm_1.edges(data="weight")]) + weights_2 = np.array([w for _, _, w in sm_2.edges(data="weight")]) + assert np.max(np.abs(weights - weights_1)) < 0.001 + assert np.max(np.abs(weights - weights_2)) < 0.001 + + def test_discondinuity(self): + """ + The result when having a point of discontinuity must be the same as if we cut the df in two (on the discont. + point) and provide the two datasets as input. + + This is because, inside, the algorithm cuts the dfs into continuous datasets + """ + np.random.seed(12) + df = pd.DataFrame(np.random.random([100, 5]), columns=["a", "b", "c", "d", "e"]) + df_2 = pd.DataFrame( + np.random.random([100, 5]), + columns=["a", "b", "c", "d", "e"], + index=np.arange(200, 300), + ) + + sm = from_pandas_dynamic(pd.concat([df, df_2], axis=0), p=2, w_threshold=0.05) + sm_1 = from_pandas_dynamic([df, df_2], p=2, w_threshold=0.05) + + assert [(u, v, round(w, 3)) for u, v, w in sm_1.edges(data="weight")] == [ + (u, v, round(w, 3)) for u, v, w in sm.edges(data="weight") + ] + + def test_incorrect_input_format(self): + with pytest.raises( + ValueError, + match="Provided empty list of time_series." + " At least one DataFrame must be provided", + ): + from_pandas_dynamic([], 1) + + with pytest.raises( + ValueError, + match=r"All columns must have numeric data\. " + r"Consider mapping the following columns to int: \['a'\]", + ): + from_pandas_dynamic(pd.DataFrame([["1"]], columns=["a"]), 1) + + with pytest.raises( + TypeError, + match="Time series entries must be instances of `pd.DataFrame`", + ): + from_pandas_dynamic([np.array([1, 2])], 1) + + with pytest.raises( + ValueError, + match="Index for dataframe must be provided in increasing order", + ): + df = pd.DataFrame(np.random.random([5, 5]), index=[3, 1, 2, 5, 0]) + from_pandas_dynamic(df, 1) + + with pytest.raises( + ValueError, + match="All inputs must have the same columns and same types", + ): + df = pd.DataFrame( + np.random.random([5, 5]), + columns=["a", "b", "c", "d", "e"], + ) + df_2 = pd.DataFrame( + np.random.random([5, 5]), + columns=["a", "b", "c", "d", "f"], + ) + from_pandas_dynamic([df, df_2], 1) + + with pytest.raises( + ValueError, + match="All inputs must have the same columns and same types", + ): + df = pd.DataFrame( + np.random.random([5, 5]), + columns=["a", "b", "c", "d", "e"], + ) + df_2 = pd.DataFrame( + np.random.random([5, 5]), + columns=["a", "b", "c", "d", "e"], + ) + df_2["a"] = df_2["a"].astype(int) + from_pandas_dynamic([df, df_2], 1) + + with pytest.raises( + TypeError, + match="Index must be integers", + ): + df = pd.DataFrame(np.random.random([5, 5]), index=[0, 1, 2, 3.0, 4]) + from_pandas_dynamic(df, 1) diff --git a/tests/structure/test_nonlinear.py b/tests/structure/test_nonlinear.py new file mode 100644 index 0000000..6572943 --- /dev/null +++ b/tests/structure/test_nonlinear.py @@ -0,0 +1,41 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. + +import torch.nn as nn + +from causalnex.structure.pytorch.nonlinear import LocallyConnected + + +class TestLocallyConnected: + def test_bias_true(self): + lc = LocallyConnected(1, 1, 1, bias=True) + assert isinstance(lc.bias, nn.Parameter) + + def test_bias_false(self): + lc = LocallyConnected(1, 1, 1, bias=False) + assert lc.bias is None diff --git a/tests/structure/test_notears.py b/tests/structure/test_notears.py index b978093..a07aa79 100644 --- a/tests/structure/test_notears.py +++ b/tests/structure/test_notears.py @@ -80,6 +80,29 @@ def test_non_numeric_data_raises_error(self): with pytest.raises(ValueError, match="All columns must have numeric data.*"): from_pandas(pd.DataFrame(data=["x"], columns=["a"])) + def test_array_with_nan_raises_error(self): + """ + Providing a data set including nan should result in a Value Error explaining that data contains nan. + This error is useful to catch and handle gracefully, because otherwise the user would have empty structures. + """ + with pytest.raises( + ValueError, + match="Input contains NaN, infinity or a value too large for dtype*", + ): + from_pandas(pd.DataFrame(data=[np.nan, 0], columns=["a"])) + + def test_array_with_inf_raises_error(self): + """ + Providing a data set including infinite values should result in a Value Error explaining that data + contains infinite values. + This error is useful to catch and handle gracefully, because otherwise the user would have empty structures. + """ + with pytest.raises( + ValueError, + match="Input contains NaN, infinity or a value too large for dtype*", + ): + from_pandas(pd.DataFrame(data=[np.inf, 0], columns=["a"])) + def test_single_iter_gets_converged_fail_warnings(self, train_data_idx): """ With a single iteration on this dataset, learn_structure fails to converge and should give warnings. @@ -206,6 +229,29 @@ def test_non_numeric_data_raises_error(self): with pytest.raises(ValueError, match="All columns must have numeric data.*"): from_pandas_lasso(pd.DataFrame(data=["x"], columns=["a"]), 0.1) + def test_array_with_nan_raises_error(self): + """ + Providing a data set including nan should result in a Value Error explaining that data contains nan. + This error is useful to catch and handle gracefully, because otherwise the user would have empty structures. + """ + with pytest.raises( + ValueError, + match="Input contains NaN, infinity or a value too large for dtype*", + ): + from_pandas_lasso(pd.DataFrame(data=[np.nan, 0], columns=["a"]), 0.1) + + def test_array_with_inf_raises_error(self): + """ + Providing a data set including infinite values should result in a Value Error explaining that data + contains infinite values. + This error is useful to catch and handle gracefully, because otherwise the user would have empty structures. + """ + with pytest.raises( + ValueError, + match="Input contains NaN, infinity or a value too large for dtype*", + ): + from_pandas_lasso(pd.DataFrame(data=[np.inf, 0], columns=["a"]), 0.1) + def test_single_iter_gets_converged_fail_warnings(self, train_data_idx): """ With a single iteration on this dataset, learn_structure fails to converge and should give warnings. @@ -369,6 +415,29 @@ def test_empty_data_raises_error(self): with pytest.raises(ValueError): from_numpy(np.empty([0, 5])) + def test_array_with_nan_raises_error(self): + """ + Providing a data set including nan should result in a Value Error explaining that data contains nan. + This error is useful to catch and handle gracefully, because otherwise the user would have empty structures. + """ + with pytest.raises( + ValueError, + match="Input contains NaN, infinity or a value too large for dtype*", + ): + from_numpy(np.array([[0, np.nan]])) + + def test_array_with_inf_raises_error(self): + """ + Providing a data set including infinite values should result in a Value Error explaining that data + contains infinite values. + This error is useful to catch and handle gracefully, because otherwise the user would have empty structures. + """ + with pytest.raises( + ValueError, + match="Input contains NaN, infinity or a value too large for dtype*", + ): + from_numpy(np.array([[0, np.inf]])) + def test_single_iter_gets_converged_fail_warnings(self, train_data_idx): """ With a single iteration on this dataset, learn_structure fails to converge and should give warnings. @@ -489,6 +558,29 @@ def test_empty_data_raises_error(self): with pytest.raises(ValueError): from_numpy_lasso(np.empty([0, 5]), 0.1) + def test_array_with_nan_raises_error(self): + """ + Providing a data set including nan should result in a Value Error explaining that data contains nan. + This error is useful to catch and handle gracefully, because otherwise the user would have empty structures. + """ + with pytest.raises( + ValueError, + match="Input contains NaN, infinity or a value too large for dtype*", + ): + from_numpy_lasso(np.array([[3, np.nan]]), 0.1) + + def test_array_with_inf_raises_error(self): + """ + Providing a data set including infinite values should result in a Value Error explaining that data + contains infinite values. + This error is useful to catch and handle gracefully, because otherwise the user would have empty structures. + """ + with pytest.raises( + ValueError, + match="Input contains NaN, infinity or a value too large for dtype*", + ): + from_numpy_lasso(np.array([[3, np.inf]]), 0.1) + def test_single_iter_gets_converged_fail_warnings(self, train_data_idx): """ With a single iteration on this dataset, learn_structure fails to converge and should give warnings. @@ -609,7 +701,7 @@ def test_f1score_generated(self, adjacency_mat_num_stability): ) train_model = StructureModel(df.values) X = generate_continuous_dataframe( - StructureModel(df), 50, noise_scale=1, seed=20 + StructureModel(df), 100, noise_scale=1, seed=20 ) g = from_numpy_lasso(X[["a", "b", "c", "d", "e"]].values, 0.1, w_threshold=0.1) right_edges = train_model.edges diff --git a/tests/structure/test_pytorch_notears.py b/tests/structure/test_pytorch_notears.py new file mode 100644 index 0000000..6ec5f8d --- /dev/null +++ b/tests/structure/test_pytorch_notears.py @@ -0,0 +1,422 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging + +import networkx as nx +import numpy as np +import pandas as pd +import pytest +import scipy.optimize as sopt +from mock import patch + +from causalnex.structure import StructureModel +from causalnex.structure.data_generators import generate_continuous_dataframe +from causalnex.structure.pytorch.notears import from_numpy, from_pandas + + +class TestFromPandas: + """Test behaviour of the from_pandas method""" + + def test_all_columns_in_structure(self, train_data_idx): + """Every columns that is in the data should become a node in the learned structure""" + + g = from_pandas(train_data_idx) + assert len(g.nodes) == len(train_data_idx.columns) + + def test_isolated_nodes_exist(self, train_data_idx): + """Isolated nodes should still be in the learned structure""" + + g = from_pandas(train_data_idx, w_threshold=1.0) + assert len(g.nodes) == len(train_data_idx.columns) + + def test_expected_structure_learned(self, train_data_idx, train_model): + """Given a small data set that can be examined by hand, the structure should be deterministic""" + + g = from_pandas(train_data_idx, w_threshold=0.25) + assert set(g.edges) == set(train_model.edges) + + def test_empty_data_raises_error(self): + """ + Providing an empty data set should result in a Value Error explaining that data must not be empty. + This error is useful to catch and handle gracefully, because otherwise the user would experience + misleading division by zero, or unpacking errors. + """ + + with pytest.raises(ValueError): + from_pandas(pd.DataFrame(data=[], columns=["a"])) + + def test_non_numeric_data_raises_error(self): + """Only numeric data frames should be supported""" + + with pytest.raises(ValueError, match="All columns must have numeric data.*"): + from_pandas(pd.DataFrame(data=["x"], columns=["a"])) + + def test_single_iter_gets_converged_fail_warnings(self, caplog, train_data_idx): + """ + With a single iteration on this dataset, learn_structure fails to converge and should give warnings. + """ + + with caplog.at_level(logging.WARNING): + from_numpy(train_data_idx.values, max_iter=1) + assert "Failed to converge. Consider increasing max_iter." in caplog.text + + def test_certain_relationships_get_near_certain_weight(self): + """If observations reliably show a==b and !a==!b then the relationship from a->b should be certain""" + + data = pd.DataFrame([[0, 1] for _ in range(10)], columns=["a", "b"]) + g = from_pandas(data) + assert all( + [ + 0.99 <= weight <= 1 + for u, v, weight in g.edges(data="weight") + if u == 0 and v == 1 + ] + ) + + def test_inverse_relationships_get_negative_weight(self): + """If observations indicate a==!b and b==!a then the weight of the relationship from a-> should be negative""" + + data = pd.DataFrame([[0, 1] for _ in range(10)], columns=["a", "b"]) + data.append(pd.DataFrame([[1, 0] for _ in range(10)], columns=["a", "b"])) + g = from_pandas(data) + assert all( + [ + weight < 0 + for u, v, weight in g.edges(data="mean_effect") + if u == 0 and v == 1 + ] + ) + + def test_no_cycles(self, train_data_idx): + """ + The learned structure should be acyclic + """ + + g = from_pandas(train_data_idx, w_threshold=0.25) + assert nx.algorithms.is_directed_acyclic_graph(g) + + def test_tabu_expected_edges(self, train_data_idx): + """Tabu edges should not exist in the network""" + + tabu_e = [("d", "a"), ("b", "c")] + g = from_pandas(train_data_idx, tabu_edges=tabu_e) + assert [e not in g.edges for e in tabu_e] + + def test_tabu_expected_parent_nodes(self, train_data_idx): + """Tabu parent nodes should not have any outgoing edges""" + + tabu_p = ["a", "d", "b"] + g = from_pandas(train_data_idx, tabu_parent_nodes=tabu_p) + assert [p not in [e[0] for e in g.edges] for p in tabu_p] + + def test_tabu_expected_child_nodes(self, train_data_idx): + """Tabu child nodes should not have any ingoing edges""" + + tabu_c = ["a", "d", "b"] + g = from_pandas(train_data_idx, tabu_child_nodes=tabu_c) + assert [c not in [e[1] for e in g.edges] for c in tabu_c] + + def test_multiple_tabu(self, train_data_idx): + """Any edge related to tabu edges/parent nodes/child nodes should not exist in the network""" + + tabu_e = [("d", "a"), ("b", "c")] + tabu_p = ["b"] + tabu_c = ["a", "d"] + g = from_pandas( + train_data_idx, + tabu_edges=tabu_e, + tabu_parent_nodes=tabu_p, + tabu_child_nodes=tabu_c, + ) + assert [e not in g.edges for e in tabu_e] + assert [p not in [e[0] for e in g.edges] for p in tabu_p] + assert [c not in [e[1] for e in g.edges] for c in tabu_c] + + def test_sparsity(self, train_data_idx): + """Structure learnt from larger lambda should be sparser than smaller lambda""" + + g1 = from_pandas(train_data_idx, lasso_beta=10.0, w_threshold=0.25) + g2 = from_pandas(train_data_idx, lasso_beta=1e-6, w_threshold=0.25) + assert len(g1.edges) < len(g2.edges) + + def test_sparsity_against_without_reg(self, train_data_idx): + """Structure learnt from regularisation should be sparser than the one without""" + + g1 = from_pandas(train_data_idx, lasso_beta=10.0, w_threshold=0.25) + g2 = from_pandas(train_data_idx, w_threshold=0.25) + assert len(g1.edges) < len(g2.edges) + + def test_f1_score_fixed(self, train_data_idx, train_model): + """Structure learnt from regularisation should have very high f1 score relative to the ground truth""" + g = from_pandas(train_data_idx, lasso_beta=0.01, w_threshold=0.25) + + n_predictions_made = len(g.edges) + n_correct_predictions = len(set(g.edges).intersection(set(train_model.edges))) + n_relevant_predictions = len(train_model.edges) + + precision = n_correct_predictions / n_predictions_made + recall = n_correct_predictions / n_relevant_predictions + f1_score = 2 * (precision * recall) / (precision + recall) + + assert f1_score > 0.8 + + def test_f1score_generated(self, adjacency_mat_num_stability): + """Structure learnt from regularisation should have very high f1 score relative to the ground truth""" + df = pd.DataFrame( + adjacency_mat_num_stability, + columns=["a", "b", "c", "d", "e"], + index=["a", "b", "c", "d", "e"], + ) + train_model = StructureModel(df) + X = generate_continuous_dataframe(train_model, 50, noise_scale=1, seed=1) + g = from_pandas(X, lasso_beta=0.1, w_threshold=0.25) + right_edges = train_model.edges + + n_predictions_made = len(g.edges) + n_correct_predictions = len(set(g.edges).intersection(set(right_edges))) + n_relevant_predictions = len(right_edges) + + precision = n_correct_predictions / n_predictions_made + recall = n_correct_predictions / n_relevant_predictions + f1_score = 2 * (precision * recall) / (precision + recall) + + assert f1_score > 0.85 + + @pytest.mark.parametrize("data", [[np.nan, 0], [np.inf, 0]]) + def test_check_array(self, data): + """ + Providing a data set including nan or inf should result in a Value Error explaining that data contains nan. + This error is useful to catch and handle gracefully, because otherwise the user would have empty structures. + """ + with pytest.raises( + ValueError, + match="Input contains NaN, infinity or a value too large for dtype*", + ): + from_pandas(pd.DataFrame(data=data, columns=["a"])) + + +class TestFromNumpy: + """Test behaviour of the from_numpy_lasso method""" + + def test_all_columns_in_structure(self, train_data_idx): + """Every columns that is in the data should become a node in the learned structure""" + + g = from_numpy(train_data_idx.values) + assert (len(g.nodes)) == len(train_data_idx.columns) + + def test_isolated_nodes_exist(self, train_data_idx): + """Isolated nodes should still be in the learned structure""" + + g = from_numpy(train_data_idx.values, w_threshold=1.0) + assert len(g.nodes) == len(train_data_idx.columns) + + def test_expected_structure_learned(self, train_data_idx, train_model_idx): + """Given a small data set that can be examined by hand, the structure should be deterministic""" + + g = from_numpy(train_data_idx.values, w_threshold=0.25) + assert set(g.edges) == set(train_model_idx.edges) + + def test_empty_data_raises_error(self): + """ + Providing an empty data set should result in a Value Error explaining that data must not be empty. + This error is useful to catch and handle gracefully, because otherwise the user would experience + misleading division by zero, or unpacking errors. + """ + + with pytest.raises(ValueError): + from_numpy(np.empty([0, 5])) + + def test_single_iter_gets_converged_fail_warnings(self, caplog, train_data_idx): + """ + With a single iteration on this dataset, learn_structure fails to converge and should give warnings. + """ + with caplog.at_level(logging.WARNING): + from_numpy(train_data_idx.values, max_iter=1) + assert "Failed to converge. Consider increasing max_iter." in caplog.text + + def test_certain_relationships_get_near_certain_weight(self): + """If observations reliably show a==b and !a==!b then the relationship from a->b should be certain""" + + data = pd.DataFrame([[1, 2] for _ in range(10)], columns=["a", "b"]) + g = from_numpy(data.values, w_threshold=0.25) + assert set(g.edges) == {(0, 1)} + assert 1.9 <= g.get_edge_data(0, 1)["weight"] <= 2 + + def test_inverse_relationships_get_negative_weight(self): + """If observations indicate a==!b and b==!a then the weight of the relationship from a-> should be negative""" + + data = pd.DataFrame([[1, -2] for _ in range(10)], columns=["a", "b"]) + data.append(pd.DataFrame([[-1, 2] for _ in range(10)], columns=["a", "b"])) + g = from_numpy(data.values, w_threshold=0.25) + assert set(g.edges) == {(0, 1)} + assert -2 <= g.get_edge_data(0, 1)["mean_effect"] <= -1.9 + + def test_no_cycles(self, train_data_idx): + """ + The learned structure should be acyclic + """ + + g = from_numpy(train_data_idx.values, w_threshold=0.25) + assert nx.algorithms.is_directed_acyclic_graph(g) + + def test_tabu_expected_edges(self, train_data_idx): + """Tabu edges should not exist in the network""" + + tabu_e = [(3, 0), (1, 2)] + g = from_numpy(train_data_idx.values, tabu_edges=tabu_e) + assert [e not in g.edges for e in tabu_e] + + def test_tabu_expected_parent_nodes(self, train_data_idx): + """Tabu parent nodes should not have any outgoing edges""" + + tabu_p = [0, 3, 1] + g = from_numpy(train_data_idx.values, tabu_parent_nodes=tabu_p) + assert [p not in [e[0] for e in g.edges] for p in tabu_p] + + def test_tabu_expected_child_nodes(self, train_data_idx): + """Tabu child nodes should not have any ingoing edges""" + + tabu_c = [0, 3, 1] + g = from_numpy(train_data_idx.values, tabu_child_nodes=tabu_c) + assert [c not in [e[1] for e in g.edges] for c in tabu_c] + + def test_multiple_tabu(self, train_data_idx): + """Any edge related to tabu edges/parent nodes/child nodes should not exist in the network""" + + tabu_e = [(3, 0), (1, 2)] + tabu_p = [1] + tabu_c = [0, 3] + g = from_numpy( + train_data_idx.values, + tabu_edges=tabu_e, + tabu_parent_nodes=tabu_p, + tabu_child_nodes=tabu_c, + ) + assert [e not in g.edges for e in tabu_e] + assert [p not in [e[0] for e in g.edges] for p in tabu_p] + assert [c not in [e[1] for e in g.edges] for c in tabu_c] + + def test_sparsity(self, train_data_idx): + """Structure learnt from larger lambda should be sparser than smaller lambda""" + + g1 = from_numpy(train_data_idx.values, lasso_beta=10.0, w_threshold=0.25) + g2 = from_numpy(train_data_idx.values, lasso_beta=1e-6, w_threshold=0.25) + assert len(g1.edges) < len(g2.edges) + + def test_sparsity_against_without_reg(self, train_data_idx): + """Structure learnt from regularisation should be sparser than the one without""" + + g1 = from_numpy(train_data_idx.values, lasso_beta=10.0, w_threshold=0.25) + g2 = from_numpy(train_data_idx.values, w_threshold=0.25) + assert len(g1.edges) < len(g2.edges) + + def test_f1_score_fixed(self, train_data_idx, train_model_idx): + """Structure learnt from regularisation should have very high f1 score relative to the ground truth""" + g = from_numpy(train_data_idx.values, lasso_beta=0.01, w_threshold=0.25) + + n_predictions_made = len(g.edges) + n_correct_predictions = len( + set(g.edges).intersection(set(train_model_idx.edges)) + ) + n_relevant_predictions = len(train_model_idx.edges) + + precision = n_correct_predictions / n_predictions_made + recall = n_correct_predictions / n_relevant_predictions + f1_score = 2 * (precision * recall) / (precision + recall) + + assert f1_score > 0.8 + + def test_f1score_generated(self, adjacency_mat_num_stability): + """Structure learnt from regularisation should have very high f1 score relative to the ground truth""" + df = pd.DataFrame( + adjacency_mat_num_stability, + columns=["a", "b", "c", "d", "e"], + index=["a", "b", "c", "d", "e"], + ) + train_model = StructureModel(df.values) + X = generate_continuous_dataframe(StructureModel(df), 50, noise_scale=1, seed=1) + g = from_numpy( + X[["a", "b", "c", "d", "e"]].values, lasso_beta=0.1, w_threshold=0.25 + ) + right_edges = train_model.edges + + n_predictions_made = len(g.edges) + n_correct_predictions = len(set(g.edges).intersection(set(right_edges))) + n_relevant_predictions = len(right_edges) + precision = n_correct_predictions / n_predictions_made + recall = n_correct_predictions / n_relevant_predictions + f1_score = 2 * (precision * recall) / (precision + recall) + + assert f1_score > 0.85 + + def test_non_negativity_constraint(self, train_data_idx): + """ + The optimisation in notears lasso involves reshaping the initial similarity matrix + into two strictly positive matrixes (w+ and w-) and imposing a non negativity constraint + to the solver. We test here if these two contraints are imposed. + + We check if: + (1) bounds impose non negativity constraint + (2) initial guess obeys non negativity constraint + (3) most importantly: output of sopt obeys the constraint + """ + # using `wraps` to **spy** on the function + with patch( + "causalnex.structure.pytorch.core.sopt.minimize", + wraps=sopt.minimize, + ) as mocked: + from_numpy(train_data_idx.values, lasso_beta=0.1, w_threshold=0.25) + # We iterate over each time `sopt.minimize` was called + for called_arguments in list(mocked.call_args_list): + # These are the arguments with which the `sopt.minimize` was called + func_ = called_arguments[0][0] # positional arg + w_est = called_arguments[0][1] # positional arg + keyword_args = called_arguments[1] + + # check 1: + assert [ + (len(el) == 2) and (el[0] == 0) for el in keyword_args["bounds"] + ] + # check 2: + assert [el >= 0 for el in w_est] + # check 3 + sol = sopt.minimize(func_, w_est, **keyword_args) + assert [el >= 0 for el in sol.x] + + @pytest.mark.parametrize("data", [[np.nan, 0], [np.inf, 0]]) + def test_check_array(self, data): + """ + Providing a data set including nan or inf should result in a Value Error explaining that data contains nan. + This error is useful to catch and handle gracefully, because otherwise the user would have empty structures. + """ + with pytest.raises( + ValueError, + match="Input contains NaN, infinity or a value too large for dtype*", + ): + from_numpy(np.array([data])) diff --git a/tests/structure/test_sklearn.py b/tests/structure/test_sklearn.py new file mode 100644 index 0000000..bbb2a97 --- /dev/null +++ b/tests/structure/test_sklearn.py @@ -0,0 +1,221 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. + +import networkx as nx +import numpy as np +import pandas as pd +import pytest +from IPython.display import Image +from mock import patch +from sklearn.exceptions import NotFittedError +from sklearn.gaussian_process.kernels import RBF +from sklearn.model_selection import KFold, cross_val_score + +from causalnex.structure import data_generators as dg +from causalnex.structure.sklearn import DAGRegressor + + +class TestStructureModel: + @pytest.mark.parametrize( + "val, msg, error", + [ + ({"alpha": "0.0"}, "alpha should be numeric", TypeError), + ({"beta": "0.0"}, "beta should be numeric", TypeError), + ({"fit_intercept": 0}, "fit_intercept should be a bool", TypeError), + ({"threshold": "0.0"}, "threshold should be numeric", TypeError), + ], + ) + def test_input_type_assertion(self, val, msg, error): + with pytest.raises(error, match=msg): + DAGRegressor(**val) + + def test_pandas_fit(self): + reg = DAGRegressor() + X, y = np.random.normal(size=(100, 2)), np.random.normal(size=(100,)) + X, y = pd.DataFrame(X), pd.Series(y) + reg.fit(X, y) + + def test_numpy_fit(self): + reg = DAGRegressor() + X, y = np.random.normal(size=(100, 2)), np.random.normal(size=(100,)) + reg.fit(X, y) + + def test_predict_type(self): + reg = DAGRegressor() + X, y = np.random.normal(size=(100, 2)), np.random.normal(size=(100,)) + reg.fit(X, y) + assert isinstance(reg.predict(X), np.ndarray) + reg = DAGRegressor() + X, y = np.random.normal(size=(100, 2)), np.random.normal(size=(100,)) + X, y = pd.DataFrame(X), pd.Series(y) + reg.fit(X, y) + assert isinstance(reg.predict(X), np.ndarray) + + def test_notfitted_error(self): + reg = DAGRegressor() + X = np.random.normal(size=(100, 2)) + with pytest.raises(NotFittedError): + reg.predict(X) + + @pytest.mark.parametrize("hidden_layer_units", [None, [2], [2, 2]]) + def test_coef(self, hidden_layer_units): + reg = DAGRegressor(hidden_layer_units=hidden_layer_units) + X, y = ( + pd.DataFrame(np.random.normal(size=(100, 1))), + pd.Series(np.random.normal(size=(100,))), + ) + X["true_feat"] = y * -3 + reg.fit(X, y) + assert isinstance(reg.coef_, np.ndarray) + coef_ = pd.Series(reg.coef_, index=X.columns) + # assert that the sign of the coefficient is correct for both nonlinear and linear cases + assert coef_["true_feat"] < 0 + + @pytest.mark.parametrize("hidden_layer_units", [None, [2], [2, 2]]) + def test_feature_importances(self, hidden_layer_units): + reg = DAGRegressor(hidden_layer_units=hidden_layer_units) + X, y = ( + pd.DataFrame(np.random.normal(size=(100, 1))), + pd.Series(np.random.normal(size=(100,))), + ) + X["true_feat"] = y * -3 + reg.fit(X, y) + assert isinstance(reg.feature_importances_, np.ndarray) + coef_ = pd.Series(reg.feature_importances_, index=X.columns) + # assert that the sign of the coefficient is positive for both nonlinear and linear cases + assert coef_["true_feat"] > 0 + + def test_tabu_parent_nodes(self): + X, y = np.random.normal(size=(100, 2)), np.random.normal(size=(100,)) + X, y = pd.DataFrame(X), pd.Series(y, name="test") + + reg = DAGRegressor(dependent_target=True, tabu_parent_nodes=["test"]) + assert "test" in reg.tabu_parent_nodes + + reg = DAGRegressor(dependent_target=True, tabu_parent_nodes=[]) + reg.fit(X, y) + assert "test" not in reg.tabu_parent_nodes + + @pytest.mark.parametrize( + "fit_intercept, equals_zero", [(True, False), (False, True)] + ) + def test_intercept(self, fit_intercept, equals_zero): + reg = DAGRegressor(fit_intercept=fit_intercept) + X, y = np.random.normal(size=(100, 2)), np.random.normal(size=(100,)) + X, y = pd.DataFrame(X), pd.Series(y) + reg.fit(X, y) + # intercept should return zero when fit_intercept == False + assert (reg.intercept_ == 0) is equals_zero + assert isinstance(reg.intercept_, float) + + @pytest.mark.parametrize("enforce_dag", [True, False]) + def test_plot_dag(self, enforce_dag): + reg = DAGRegressor() + X, y = np.random.normal(size=(100, 2)), np.random.normal(size=(100,)) + reg.fit(X, y) + image = reg.plot_dag(enforce_dag=enforce_dag) + assert isinstance(image, Image) + + def test_plot_dag_importerror(self): + with patch.dict("sys.modules", {"IPython.display": None}): + reg = DAGRegressor() + X, y = np.random.normal(size=(100, 2)), np.random.normal(size=(100,)) + reg.fit(X, y) + + with pytest.raises( + ImportError, + match=r"DAGRegressor\.plot_dag method requires IPython installed\.", + ): + reg.plot_dag() + + @pytest.mark.parametrize( + "hidden_layer_units", [None, [], [0], [1], (0,), (1,), [1, 1], (1, 1)] + ) + def test_hidden_layer_units(self, hidden_layer_units): + reg = DAGRegressor(hidden_layer_units=hidden_layer_units) + X, y = np.random.normal(size=(100, 2)), np.random.normal(size=(100,)) + reg.fit(X, y) + + def test_enforce_dag(self): + reg = DAGRegressor(enforce_dag=True) + X, y = np.random.normal(size=(100, 2)), np.random.normal(size=(100,)) + X, y = pd.DataFrame(X), pd.Series(y) + reg.fit(X, y) + assert nx.algorithms.is_directed_acyclic_graph(reg.graph_) + + @pytest.mark.parametrize("standardize", [True, False]) + def test_nonlinear_performance(self, standardize): + np.random.seed(42) + sm = dg.generate_structure(num_nodes=10, degree=3) + sm.threshold_till_dag() + data = dg.generate_continuous_dataframe( + sm, n_samples=1000, intercept=True, seed=42, noise_scale=0.1, kernel=RBF(1) + ) + node = 1 + y = data.iloc[:, node] + X = data.drop(node, axis=1) + + reg = DAGRegressor( + alpha=0.0, + l1_ratio=0.0, + fit_intercept=True, + dependent_target=True, + enforce_dag=False, + hidden_layer_units=[0], + standardize=standardize, + ) + linear_score = cross_val_score( + reg, X, y, cv=KFold(shuffle=True, random_state=42) + ).mean() + + reg = DAGRegressor( + alpha=0.1, + l1_ratio=1.0, + fit_intercept=True, + enforce_dag=False, + hidden_layer_units=[2], + standardize=standardize, + ) + small_nl_score = cross_val_score( + reg, X, y, cv=KFold(shuffle=True, random_state=42) + ).mean() + + reg = DAGRegressor( + alpha=0.1, + l1_ratio=1.0, + fit_intercept=True, + enforce_dag=False, + hidden_layer_units=[4], + standardize=standardize, + ) + medium_nl_score = cross_val_score( + reg, X, y, cv=KFold(shuffle=True, random_state=42) + ).mean() + + assert small_nl_score > linear_score + assert medium_nl_score > small_nl_score diff --git a/tests/structure/test_transformers.py b/tests/structure/test_transformers.py new file mode 100644 index 0000000..4831a05 --- /dev/null +++ b/tests/structure/test_transformers.py @@ -0,0 +1,153 @@ +# Copyright 2019-2020 QuantumBlack Visual Analytics Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +# NONINFRINGEMENT. IN NO EVENT WILL THE LICENSOR OR OTHER CONTRIBUTORS +# BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# The QuantumBlack Visual Analytics Limited ("QuantumBlack") name and logo +# (either separately or in combination, "QuantumBlack Trademarks") are +# trademarks of QuantumBlack. The License does not grant you any right or +# license to the QuantumBlack Trademarks. You may not use the QuantumBlack +# Trademarks or any confusingly similar mark as a trademark for your product, +# or use the QuantumBlack Trademarks in any other manner that might cause +# confusion in the marketplace, including but not limited to in advertising, +# on websites, or on software. +# +# See the License for the specific language governing permissions and +# limitations under the License. + +import re + +import numpy as np +import pandas as pd +import pytest +from sklearn.exceptions import NotFittedError + +from causalnex.structure.transformers import DynamicDataTransformer + + +class TestDynamicDataTransformer: + def test_naming_nodes(self, data_dynotears_p3): + """ + Nodes should have the format {var}_lag{l} + """ + df = pd.DataFrame(data_dynotears_p3["X"], columns=["a", "b", "c", "d", "e"]) + df_dyno = DynamicDataTransformer(p=3).fit_transform(df) + + pattern = re.compile(r"[abcde]_lag[0-3]") + for node in df_dyno.columns: + match = pattern.match(node) + assert match + assert match.group() == node + + def test_all_nodes_in_df(self, data_dynotears_p3): + """ + Nodes should have the format {var}_lag{l} + """ + df = pd.DataFrame(data_dynotears_p3["X"], columns=["a", "b", "c", "d", "e"]) + df_dyno = DynamicDataTransformer(p=3).fit_transform(df) + + assert list(df_dyno.columns) == [ + el + "_lag" + str(i) for i in range(4) for el in ["a", "b", "c", "d", "e"] + ] + + def test_incorrect_input_format(self): + with pytest.raises( + ValueError, + match="Provided empty list of time_series." + " At least one DataFrame must be provided", + ): + DynamicDataTransformer(p=3).fit_transform([]) + + with pytest.raises( + ValueError, + match=r"All columns must have numeric data\. " + r"Consider mapping the following columns to int: \['a'\]", + ): + DynamicDataTransformer(p=1).fit_transform( + pd.DataFrame([["1"]], columns=["a"]) + ) + + with pytest.raises( + TypeError, + match="Time series entries must be instances of `pd.DataFrame`", + ): + DynamicDataTransformer(p=1).fit_transform([np.array([1, 2])]) + + with pytest.raises( + ValueError, + match="Index for dataframe must be provided in increasing order", + ): + df = pd.DataFrame(np.random.random([5, 5]), index=[3, 1, 2, 5, 0]) + DynamicDataTransformer(p=1).fit_transform(df) + + with pytest.raises( + ValueError, + match="All inputs must have the same columns and same types", + ): + df = pd.DataFrame( + np.random.random([5, 5]), + columns=["a", "b", "c", "d", "e"], + ) + df_2 = pd.DataFrame( + np.random.random([5, 5]), + columns=["a", "b", "c", "d", "f"], + ) + DynamicDataTransformer(p=1).fit_transform([df, df_2]) + + with pytest.raises( + ValueError, + match="All inputs must have the same columns and same types", + ): + cols = ["a", "b", "c", "d", "e"] + df = pd.DataFrame(np.random.random([5, 5]), columns=cols) + df_2 = pd.DataFrame(np.random.random([5, 5]), columns=cols) + df_2["a"] = df_2["a"].astype(int) + DynamicDataTransformer(p=1).fit_transform([df, df_2]) + + with pytest.raises( + TypeError, + match="Index must be integers", + ): + df = pd.DataFrame(np.random.random([5, 5]), index=[0, 1, 2, 3.0, 4]) + DynamicDataTransformer(p=1).fit_transform(df) + + def test_not_fitted_transform(self): + """if transform called before fit: raise error""" + with pytest.raises( + NotFittedError, + match=r"This DynamicDataTransformer is not fitted yet\." + " Call `fit` before using this method", + ): + df = pd.DataFrame(np.random.random([5, 5])) + DynamicDataTransformer(p=1).transform(df) + + def test_transform_wrong_input(self): + """If transform df does not have all necessaty columns, raise error""" + with pytest.raises( + ValueError, + match="We should provide all necessary columns in " + r"the time series\. Columns not provided: \[2, 3\]", + ): + df = pd.DataFrame(np.random.random([5, 5])) + ddt = DynamicDataTransformer(p=1).fit(df) + ddt.transform(df.drop([2, 3], axis=1)) + + def test_return_df_true_equivalent_to_false(self): + """Check that the df from `return_df=true` is + equivalent the result if `return_df=false`""" + df = pd.DataFrame(np.random.random([50, 10])) + df_dyno = DynamicDataTransformer(p=3).fit_transform(df, return_df=True) + X, Xlags = DynamicDataTransformer(p=3).fit_transform(df, return_df=False) + assert np.all(df_dyno.values[:, :10] == X) + assert np.all(df_dyno.values[:, 10:] == Xlags) diff --git a/tools/license_and_headers.py b/tools/license_and_headers.py index 3842454..bee6300 100644 --- a/tools/license_and_headers.py +++ b/tools/license_and_headers.py @@ -66,8 +66,11 @@ def files_at_path(path: str): - return [fn for fn in glob.glob(path + '/**/*.py', recursive=True) - if not ('ebaybbn' in fn or 'structure/notears.py' in fn)] + return [ + fn + for fn in glob.glob(path + "/**/*.py", recursive=True) + if not ("ebaybbn" in fn or "structure/notears.py" in fn) + ] def files_missing_substring(file_names, substring):