Package: gies
Greedy Interventional Equivalence Search (GIES) Algorithm for Causal DiscoveryThis is a python implementation of the GIES algorithm from the paper"Characterization and Greedy Learning of Interventional
Markov Equivalence Classes of Directed Acyclic Graphs"by Alain Hauser and Peter Bühlmann.The implementation is an extension of thePython implementationof the GES algorithm.InstallationYou can clone this repo or install the python package via pip:pipinstallgiesTheonlydependency outside the Python Standard Library isnumpy>=1.15.0. Seerequirements.txtfor more details.When you should use this implementationTo the best of our knowledge, the only other public implementation of GIES is in the R packagepcalg. It can be called from Python through an easy-to-use wrapper in theCausal Discovery Toolbox, but given its scope, this library contains many additional dependencies (including PyTorch) and still requires you to haveR.Thus,this implementation might be for you ifyou want a dependency-light implementation in Python (the only dependency outside the Python Standard Library is numpy).Running the algorithmUsing the Gaussian BIC score:gies.fit_bicGIES comes ready to use with theGaussian BIC score, i.e. the l0-penalized Gaussian likelihood score. This is the variant which is commonly found in the literature, and the one which was implemented in the original paper. It is made available under the functiongies.fit_bic.gies.fit_bic(data,I,A0=None,phases=['forward','backward','turning'],iterate=True,debug=0)Parametersdata(np.array): the matrix containing the observations of each variable (each column corresponds to a variable).I(list of lists of ints): the family of intervention targets, with each list being the targets in the corresponding environment.A0(np.array, optional): the initial CPDAG on which GIES will run, where whereA0[i,j] != 0impliesi -> jandA[i,j] != 0 & A[j,i] != 0impliesi - j. Defaults to the empty graph.phases([{'forward', 'backward', 'turning'}*], optional): this controls which phases of the GIES procedure are run, and in which order. Defaults to['forward', 'backward', 'turning'].iterate(boolean, default=True): Indicates whether the algorithm should repeat the given phases more than once, until the score is not improved.debug(int, optional): if larger than 0, debug are traces printed. Higher values correspond to increased verbosity.Returnsestimate(np.array): the adjacency matrix of the estimated CPDAG.total_score(float): the score of the estimate.ExampleHeresempleris used to generate an observational sample from a Gaussian SCM, but this is not a dependency.importgiesimportsemplerimportnumpyasnp# Generate observational data from a Gaussian SCM using semplerA=np.array([[0,0,1,0,0],[0,0,1,0,0],[0,0,0,1,1],[0,0,0,0,1],[0,0,0,0,0],])W=A*np.random.uniform(1,2,A.shape)# sample weightsscm=sempler.LGANM(W,(1,2),(1,2))data=[scm.sample(n=5000),scm.sample(n=5000,do_interventions={2:(0,5)})]# Run GIES with the gaussian BIC scoreinterventions=[[],[2]]estimate,score=gies.fit_bic(data,interventions)print(estimate,score)# Output# [[0. 0. 1. 0. 0.]#  [0. 0. 1. 0. 0.]#  [0. 0. 0. 0. 1.]#  [0. 0. 0. 0. 0.]#  [0. 0. 0. 1. 0.]] -29209.33670496673Using a custom score:gies.fitWhileHauser and Bühlmann (2012)chose the BIC score, any score-equivalent and locally decomposable function is adequate. To run with another score of your choice, you can usegies.fit(score_class,A0=None,phases=['forward','backward','turning'],iterate=True,debug=0)wherescore_classis an instance of the class which implements your score. It should inherit fromgies.scores.DecomposableScore, or define alocal_scorefunction and a few attributes (seedecomposable_score.pyfor more details).Parametersscore_class(gies.scores.DecomposableScore): an instance of a class implementing a locally decomposable score, which inherits fromgies.scores.DecomposableScore. Seedecomposable_score.pyfor more details.A0(np.array, optional): the initial CPDAG on which GIES will run, where whereA0[i,j] != 0impliesi -> jandA[i,j] != 0 & A[j,i] != 0impliesi - j. Defaults to the empty graph.phases([{'forward', 'backward', 'turning'}*], optional): this controls which phases of the GIES procedure are run, and in which order. Defaults to['forward', 'backward', 'turning'].iterate(boolean, default=True): Indicates whether the algorithm should repeat the given phases more than once, until the score is not improved.debug(int, optional): if larger than 0, debug are traces printed. Higher values correspond to increased verbosity.Returnsestimate(np.array): the adjacency matrix of the estimated CPDAG.total_score(float): the score of the estimate.ExampleRunning GIES on a custom defined score (in this case the same Gaussian BIC score implemented ingies.scores.GaussIntL0Pen).importgiesimportgies.scoresimportsemplerimportnumpyasnp# Generate observational data from a Gaussian SCM using semplerA=np.array([[0,0,1,0,0],[0,0,1,0,0],[0,0,0,1,1],[0,0,0,0,1],[0,0,0,0,0],])W=A*np.random.uniform(1,2,A.shape)# sample weightsscm=sempler.LGANM(W,(1,2),(1,2))data=[scm.sample(n=5000),scm.sample(n=5000,do_interventions={2:(0,5)})]# Define the score classinterventions=[[],[2]]score_class=gies.scores.GaussIntL0Pen(data,interventions)# Run GIES with the gaussian BIC scoreestimate,score=gies.fit(score_class)print(estimate,score)# Output# [[0. 0. 1. 0. 0.]#  [0. 0. 1. 0. 0.]#  [0. 0. 0. 1. 1.]#  [0. 0. 0. 0. 1.]#  [0. 0. 0. 1. 0.]] -30310.806829328416Code StructureAll the modules can be found inside thegies/directory. These include:gies.mainwhich is the main module with the calls to start GIES, and contains the implementation of the insert, delete and turn operators.gies.utilscontains auxiliary functions and the logic to transform a PDAG into a CPDAG, used after each application of an operator.gies.scorescontains the modules with the score classes:gies.scores.decomposable_scorecontains the base class for decomposable score classes (see that module for more details).gies.scores.gauss_int_l0_pencontains an implementation of the cached Gaussian BIC score.gies.testcontains the modules with the unit tests and tests comparing against the algorithm's R implementation in the 'pcalg' package.TestsAll components come with unit tests to match, and some property-based tests. The output of the overall procedure has been checked against that of thepcalgimplementation over tens of thousands of random graphs. Of course, this doesn't mean there are no bugs, but hopefully it meansthey are less likely:)The tests can be run withmake test. You can addSUITE=<module_name>to run a particular module only. There are, however, additional dependencies to run the tests. You can find these inrequirements_tests.txtandR_requirements_tests.txt.Test modulesThey are in the sub packagegies.test, in the directoryges/test/:test_decomposable_score.py: tests for the decomposable score base class.test_int_score.py: tests for the Gaussian bic score.test_pdag_to_cpdag.py: tests the conversion from PDAG to CPDAG, which is applied after each application of an operator.test_utils.py: tests the other auxiliary functions.test_vs_R: compares the output of the algorithm to that of the R implementation in thepcalgpackage.FeedbackI hope you find this useful! Feedback and (constructive) criticism is always welcome, just shoot me anemail:)